Java 持久化 API (JPA)是一个 Java 应用程序接口规范,描述了使用 Java标准版平台(Java SE) 和 Java企业版平台(Java EE)的应用中的 关系数据 的管理。
JPA充当面向对象的域模型和关系数据库系统之间的桥梁。由于JPA只是一个规范, 因此它本身不会执行任何操作。它需要一个实现。因此, 诸如Hibernate,MyBatis之类的ORM工具实现了JPA规范以实现数据持久性。
持久化,在这里包括三个层面的意思:
- API 本身,定义在 javax.persistence 包内
- Java持久化查询语言 (JPQL)
- 对象/关系 元数据
持久化实体是一个轻量级的 Java 类,其状态通常持久地保存到关系数据库的表中。 这种实体的实例对应于表中的各个行。 实体之间通常有关系,这些关系通过对象/关系元数据表示。 可以在实体类文件中直接使用注释来指定这种关系,也可以在随应用程式分发的单独XML描述文件中指定。
Java持久化查询语言 (JPQL)对存储在关系数据库中的实体进行查询。查询在语法上类似于SQL查询,但是操作的是实体对象而不是直接对数据库表进行操作。
Hibernate为Java提供了一个开源的对象关系映射框架。版本3.2及更高版本提供了Java 持久化 API的实现。
Spring Data JPA 抽象存储库的实现是Java应用程式框架Spring的领域驱动设计的关键构建块。透明地支持所有可用的JPA实现,并支持CRUD操作以及方便地执行数据库查询。
实体映射类型
- 一对一映射:一对一映射表示单值关联, 其中一个实体的实例与另一个实体的实例关联。在这种类型的关联中, 源实体的一个实例可以与目标实体的最多一个实例进行映射。
- 一对多映射:一对多映射属于集合值关联的类别, 其中一个实体与其他实体的集合相关联。在这种类型的关联中, 一个实体的实例可以与另一个实体的任意数量的实例进行映射。
- 多对一映射表示单值关联, 其中实体的集合可以与相似实体关联。在关系数据库中, 一个实体的多个行可以引用另一个实体的同一行。
- 多对多映射多对多映射表示集合值关联, 其中任何数量的实体都可以与其他实体的集合关联。在关系数据库中, 一个实体的多于一行可以引用另一实体的多于一行。
1
2
3
4
5
|
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.2.Final</version>
</dependency>
|
JPQL
JPQL代表 Java 持久性查询语言。Spring Data 提供了多种创建和执行查询的方法,JPQL 就是其中之一。它使用 Spring 中的@Query注释定义查询,以执行 JPQL 和本机 SQL 查询。查询定义默认使用 JPQL。
1
2
3
4
|
@Query(value = "SELECT e FROM Employee e")
List<Employee> findAllEmployees(Sort sort);
@Query("SELECT e FROM Employee e WHERE e.name = ?1 and e.salary = ?2")
Employee findEmployeeByNameAndSalary(String name, Long salary);
|
使用 JPQL 原生查询, 将nativeQuery属性的值设置为true以定义原生 SQL 查询。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Query(
value = "SELECT * FROM Employee e WHERE e.salary = ?1",
nativeQuery = true)
Employee findEmployeeBySalaryNative(Long salary);
// 使用命名参数使查询更易于阅读,并且在重构的情况下不易出错。
@Query("SELECT e FROM Employee e WHERE e.name = :name and e.salary = :salary")
Employee findEmployeeByNameAndSalaryNamedParameters(
@Param("name") String name,
@Param("salary") Long salary);
@Query(value = "SELECT * FROM Employee e WHERE e.name = :name and e.salary = :salary",
nativeQuery = true)
Employee findUserByNameAndSalaryNamedParamsNative(
@Param("name") String name,
@Param("salary") Long salary);
|
Criteria API
Criteria API是一种规范, 提供使用Java编程语言API编写的类型安全和可移植的条件查询。它是构造实体及其持久状态查询的最常用方法之一。它只是定义JPA查询的另一种方法。 Criteria API定义了独立于平台的标准查询, 以Java编程语言编写。它是在JPA 2.0中引入的。其背后的主要目的是提供一种类型安全的方式来表达查询。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
@Entity
public class DeptEmployee {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String title;
@ManyToOne
private Department department;
}
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
@OneToMany(mappedBy="department")
private List<DeptEmployee> employees;
}
CriteriaQuery<DeptEmployee> criteriaQuery =
criteriaBuilder.createQuery(DeptEmployee.class);
Root<DeptEmployee> root = criteriaQuery.from(DeptEmployee.class);
In<String> inClause = criteriaBuilder.in(root.get("title"));
for (String title : titles) {
inClause.value(title);
}
criteriaQuery.select(root).where(inClause);
// or
criteriaQuery.select(root)
.where(root.get("title")
.in(titles));
Subquery<Department> subquery = criteriaQuery.subquery(Department.class);
Root<Department> dept = subquery.from(Department.class);
subquery.select(dept)
.distinct(true)
.where(criteriaBuilder.like(dept.get("name"), "%" + searchKey + "%"));
criteriaQuery.select(emp)
.where(criteriaBuilder.in(emp.get("department")).value(subquery));
|
批量插入更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
@Entity
public class School {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
@OneToMany(mappedBy = "school")
private List<Student> students;
// Getters and setters...
}
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
@ManyToOne
private School school;
// Getters and setters...
}
// 跟踪 SQL 查询
private static class ProxyDataSourceInterceptor implements MethodInterceptor {
private final DataSource dataSource;
public ProxyDataSourceInterceptor(final DataSource dataSource) {
this.dataSource = ProxyDataSourceBuilder.create(dataSource)
.name("Batch-Insert-Logger")
.asJson().countQuery().logQueryToSysOut().build();
}
// Other methods...
}
|
配置 Hibernate 以启用批处理。为此,我们应该将hibernate.jdbc.batch_size属性设置为大于 0 的数字。
如果是 Spring Boot 配置
spring.jpa.properties.hibernate.jdbc.batch_size=5
1
2
3
4
5
6
7
8
|
@Transactional
@Test
public void whenInsertingSingleTypeOfEntity_thenCreatesSingleBatch() {
for (int i = 0; i < 10; i++) {
School school = createSchool(i);
entityManager.persist(school);
}
}
|
当我们持久化一个实体时,Hibernate 将它存储在持久化上下文中。例如,如果我们在一个事务中持久化 100,000 个实体,我们最终将在内存中拥有 100,000 个实体实例,这可能会导致OutOfMemoryException。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 批处理操作期间优化内存使用
@Transactional
@Test
public void whenFlushingAfterBatch_ThenClearsMemory() {
for (int i = 0; i < 10; i++) {
if (i > 0 && i % BATCH_SIZE == 0) {
entityManager.flush();
entityManager.clear();
}
School school = createSchool(i);
entityManager.persist(school);
}
}
|
调用EntityManager.flush()也会触发事务同步。其次,持久化上下文作为实体缓存,也称为一级缓存。要清除持久化上下文中的实体,我们可以调用EntityManager.clear()。
多表批量插入,Hibernate 会为每种实体类型创建不同的批次。这是因为一个批次中只能有一种类型的实体。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Transactional
@Test
public void whenThereAreMultipleEntities_ThenCreatesNewBatch() {
for (int i = 0; i < 10; i++) {
if (i > 0 && i % BATCH_SIZE == 0) {
entityManager.flush();
entityManager.clear();
}
School school = createSchool(i);
entityManager.persist(school);
Student firstStudent = createStudent(school);
Student secondStudent = createStudent(school);
entityManager.persist(firstStudent);
entityManager.persist(secondStudent);
}
}
|
要批处理相同实体类型的所有插入语句,我们应该配置hibernate.order_inserts属性。
spring.jpa.properties.hibernate.order_inserts=true
批量更新
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.batch_versioned_data=true
1
2
3
4
5
6
7
8
9
10
|
@Transactional
@Test
public void whenUpdatingEntities_thenCreatesBatch() {
TypedQuery<School> schoolQuery =
entityManager.createQuery("SELECT s from School s", School.class);
List<School> allSchools = schoolQuery.getResultList();
for (School school : allSchools) {
school.setName("Updated_" + school.getName());
}
}
|
如果我们的实体使用GenerationType.IDENTITY标识符生成器,Hibernate 将静默禁用批量插入/更新。
1
2
3
|
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
|
Many-to-Many
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@Entity
class Student {
@Id
Long id;
@ManyToMany
@JoinTable(
name = "course_like",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
Set<Course> likedCourses;
// additional properties
// standard constructors, getters, and setters
}
@Entity
class Course {
@Id
Long id;
@ManyToMany(mappedBy = "likedCourses")
Set<Student> likes;
// additional properties
// standard constructors, getters, and setters
}
|
多对多关系在数据库中没有所有者,我们可以在Course类中配置连接表并从Student类中引用它。
使用复合键的多对多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
@Embeddable
class CourseRatingKey implements Serializable {
@Column(name = "student_id")
Long studentId;
@Column(name = "course_id")
Long courseId;
// standard constructors, getters, and setters
// hashcode and equals implementation
}
@Entity
class CourseRating {
@EmbeddedId
CourseRatingKey id;
@ManyToOne
@MapsId("studentId")
@JoinColumn(name = "student_id")
Student student;
@ManyToOne
@MapsId("courseId")
@JoinColumn(name = "course_id")
Course course;
int rating;
// standard constructors, getters, and setters
}
class Student {
// ...
@OneToMany(mappedBy = "student")
Set<CourseRating> ratings;
// ...
}
class Course {
// ...
@OneToMany(mappedBy = "course")
Set<CourseRating> ratings;
// ...
}
|
新实体的多对多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
@Entity
class CourseRegistration {
@Id
Long id;
@ManyToOne
@JoinColumn(name = "student_id")
Student student;
@ManyToOne
@JoinColumn(name = "course_id")
Course course;
LocalDateTime registeredAt;
int grade;
// additional properties
// standard constructors, getters, and setters
}
class Student {
// ...
@OneToMany(mappedBy = "student")
Set<CourseRegistration> registrations;
// ...
}
class Course {
// ...
@OneToMany(mappedBy = "course")
Set<CourseRegistration> registrations;
// ...
}
|