Spring Data JPA 学习笔记(01)
为什么需要 Spring Data JPA
Java 是一门面向对象的编程语言,数据以对象的形式组织在内存当中。如何将这些数据存储在常见的关系型数据库中是我们要解决的问题。
2001 年,一款名为 Hibernate 的 ORM 框架为我们提供了一种解决方案。ORM 即 object-relational mapping,中文翻译为对象-关系映射。该框架主要负责维护 Java 类和数据库表两者间的映射关系。Hibernate 提供了很多强大的特性和方便的功能,在众多框架中脱颖而出。
可是这跟 Spring Data JPA 有什么关系?
Java 一个很好的特性就是:对于一个常见的需求,大家坐下来讨论讨论,先把标准(接口)定下来,之后你们这些框架怎么写我不管,只要实现标准就好。该标准即 Java Persistence API,缩写为 JPA。而 Spring Data JPA 是对 JPA 提供的接口进行更进一步的封装,程序员只需要关注程序设计,不再需要耗费大量精力在框架的配置和数据的获取上。而 Hibernate 的设计十分优秀,时至今日,JPA 的默认实现仍是 Hibernate。三者关系如下:
┌─────────────────────────┐
│ │
│ Spring Data JPA │
│ │
└────────────┬────────────┘
│
│ 封装 JPA 提供的接口
│
│
┌────────────▼────────────┐
│ │
│ JPA │
│ │
└────────────▲────────────┘
│
│
│ 实现 JPA 规范
│
┌────────────┴────────────┐
│ │
│ Hibernate │
│ │
└─────────────────────────┘
Persistence 一词中文翻译为「持续存在」,「持久化」。如何理解?
因为 Java 对象存在于内存当中,内存断电后数据就消失了,而将该对象存储在外部(如数据库中、或直接序列化写到文件中)则数据可以持久保存,所以将对象存储在外部的这个过程称之为持久化。
Hello Spring Data JPA
在 Spring Initializr 新建 Spring Boot 项目,主要依赖配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
其中 H2 是一个基于内存的数据库,通常用来测试持久化功能。
application.properties 配置如下:
# 配置开启终端彩色输出
spring.output.ansi.enabled=ALWAYS
# 配置在标准输出中显示编译的 SQL 语句
logging.level.org.hibernate.SQL=DEBUG
# 配置显示执行的 SQL 语句中参数的值
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# 开启 H2 数据库的终端,这样可以查看代码对数据库的影响
spring.h2.console.enabled=true
# 配置用户名和密码,用来登录 H2 终端
spring.datasource.username=sa
spring.datasource.password=password
# 先配置,之后再讲解原因
spring.jpa.open-in-view=false
接下来配置我们要向数据库中存储的类,就叫他 Person 好了。
package com.cvuuhk.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
// @Entity 是 JPA 提供的注解,表示所修饰的类是一个实体
@Entity
public class Person {
// @Id 是 JPA 提供的注解,表示所修饰的字段作为该实体的主键
@Id
// @GeneratedValue 是 JPA 提供的注解,用来自动生成主键
@GeneratedValue
private Long id;
private String name;
// JPA 规范要求实体类必须有一个至少是 protected 修饰的无参构造函数
public Person(){}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
「实体」是什么?
可以简单理解为实体就是我们要向数据库中存储的对象,就现阶段来说,一个实体类对应数据库的一张表。
有了实体类,怎么把他存放到数据库中呢?或者说,我应该调用什么方法来操作数据库呢?
我们使用 Spring Data JPA 提供的 Repository 接口。
package com.cvuuhk.jpa;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PersonRepository extends JpaRepository<Person, Long> {
}
如果你使用 IDE 提供的工具点进 JpaRepository 接口的定义中,会发现该接口定义了许多 save,find 之类的方法,这就是我们要用的。
现在我们模拟真实环境中的写法:写一个 service 类,该类实现各种业务逻辑。先写一个增加用户的吧。
package com.cvuuhk.jpa;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class PersonService {
private final Logger logger = LoggerFactory.getLogger(PersonService.class);
private final PersonRepository repository;
public PersonService(PersonRepository repository) {
this.repository = repository;
}
public void addPerson(String name) {
Person person = new Person(name);
repository.save(person);
logger.info(person + " saved.");
}
}
最后,我们希望程序启动时执行 addPerson
方法,新建 Startup 类配置启动时执行的指令:
package com.cvuuhk.jpa;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Startup {
private final PersonService service;
public Startup(PersonService service) {
this.service = service;
}
@Bean
public CommandLineRunner executeWhenStartup(){
return args -> {
service.addPerson("疏蒿君");
};
}
}
目录结构如下:
src
├── main
│ ├── java
│ │ └── com
│ │ └── cvuuhk
│ │ └── jpa
│ │ ├── Application.java
│ │ ├── Person.java
│ │ ├── PersonRepository.java
│ │ ├── PersonService.java
│ │ └── Startup.java
│ └── resources
│ └── application.properties
└── test
└── java
└── com
└── cvuuhk
└── jpa
└── ApplicationTests.java
OK,现在启动 Spring Boot,日志输出大致如下:
2022-08-14 13:06:58.546 INFO 681729 --- [ main] com.cvuuhk.jpa.Application : No active profile set, falling back to 1 default profile: "default"
2022-08-14 13:06:59.104 INFO 681729 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-08-14 13:06:59.139 INFO 681729 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 29 ms. Found 1 JPA repository interfaces.
2022-08-14 13:06:59.558 INFO 681729 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-08-14 13:06:59.566 INFO 681729 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-08-14 13:06:59.566 INFO 681729 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.65]
2022-08-14 13:06:59.642 INFO 681729 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-08-14 13:06:59.642 INFO 681729 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1041 ms
2022-08-14 13:06:59.681 INFO 681729 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-08-14 13:06:59.839 INFO 681729 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2022-08-14 13:06:59.848 INFO 681729 --- [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:a8c0d009-ab4b-4d5a-8d6e-69b76ea317b5'
2022-08-14 13:06:59.967 INFO 681729 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-08-14 13:07:00.000 INFO 681729 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.10.Final
2022-08-14 13:07:00.108 INFO 681729 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-08-14 13:07:00.190 INFO 681729 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2022-08-14 13:07:00.543 DEBUG 681729 --- [ main] org.hibernate.SQL : drop table if exists person CASCADE
2022-08-14 13:07:00.546 DEBUG 681729 --- [ main] org.hibernate.SQL : drop sequence if exists hibernate_sequence
2022-08-14 13:07:00.548 DEBUG 681729 --- [ main] org.hibernate.SQL : create sequence hibernate_sequence start with 1 increment by 1
2022-08-14 13:07:00.550 DEBUG 681729 --- [ main] org.hibernate.SQL : create table person (id bigint not null, name varchar(255), primary key (id))
2022-08-14 13:07:00.556 INFO 681729 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2022-08-14 13:07:00.562 INFO 681729 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2022-08-14 13:07:01.101 INFO 681729 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-08-14 13:07:01.109 INFO 681729 --- [ main] com.cvuuhk.jpa.Application : Started Application in 2.989 seconds (JVM running for 3.356)
2022-08-14 13:07:01.124 DEBUG 681729 --- [ main] org.hibernate.SQL : call next value for hibernate_sequence
2022-08-14 13:07:01.161 DEBUG 681729 --- [ main] org.hibernate.SQL : insert into person (name, id) values (?, ?)
2022-08-14 13:07:01.163 TRACE 681729 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [疏蒿君]
2022-08-14 13:07:01.164 TRACE 681729 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1]
2022-08-14 13:07:01.168 INFO 681729 --- [ main] com.cvuuhk.jpa.PersonService : Person{id=1, name='疏蒿君'} saved.
我将其中比较重要的信息摘出来:
2022-08-14 13:06:59.558 INFO 681729 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-08-14 13:06:59.848 INFO 681729 --- [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:a8c0d009-ab4b-4d5a-8d6e-69b76ea317b5'
2022-08-14 13:07:00.548 DEBUG 681729 --- [ main] org.hibernate.SQL : create sequence hibernate_sequence start with 1 increment by 1
2022-08-14 13:07:00.550 DEBUG 681729 --- [ main] org.hibernate.SQL : create table person (id bigint not null, name varchar(255), primary key (id))
2022-08-14 13:07:01.124 DEBUG 681729 --- [ main] org.hibernate.SQL : call next value for hibernate_sequence
2022-08-14 13:07:01.161 DEBUG 681729 --- [ main] org.hibernate.SQL : insert into person (name, id) values (?, ?)
2022-08-14 13:07:01.163 TRACE 681729 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [疏蒿君]
2022-08-14 13:07:01.164 TRACE 681729 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1]
2022-08-14 13:07:01.168 INFO 681729 --- [ main] com.cvuuhk.jpa.PersonService : Person{id=1, name='疏蒿君'} saved.
根据这些日志,我们得到以下信息:
- 服务端口为 8080
- H2 数据库开了一个终端,地址为
jdbc:h2:mem:a8c0d009-ab4b-4d5a-8d6e-69b76ea317b5
- 创建了一个名为
hibernate_sequence
的序列,这个序列干什么用的?我们之前在实体类中配置了自动生成主键,主键的值就是根据这个序列来生成的,能看到该序列起始值为 1,递增值为 1 - 创建了一个名为
person
的数据库表 - 从序列
hibernate_sequence
中获取 id 的值 - 向数据库表
person
中插入值,其中 id 为1
,name 为疏蒿君
- 根据代码,最后一行日志表示
addPerson
方法执行完成
接下来访问 http://localhost:8080/h2-console
,将刚才日志中打印的终端地址填入 JDBC URL 输入框中,之后再填入 application.properties 文件中配置的用户名和密码。登录后执行 SELECT * FROM PERSON
得到结果如下:
+-------------+
| ID | NAME |
+----+--------+
| 1 | 疏蒿君 |
+ ---+--------+
我们成功地将数据存到了数据库中,其他的操作使用接口提供的其他方法即可。
Q&A
建序列,建表,插入数据的这些 SQL 语句哪来的?
Hibernate 生成的。
我的 PersonRepository 是个接口,save() 方法没实现为什么能调用?
Spring Data JPA 帮我们实现的这个接口,具体的之后再写。
结束语
本篇是我的第一篇博客,内容比较简单,一方面是尝试把自己所学所想表达出来。另一方面也是为了督促自己不断学习。之后会聚焦于内容,尝试写一些更有深度的文章。
怎么说呢……
心有所向,日复一日,必有精进。
——刻晴
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。