目录

JPA - Java 持久化 API

Java Persistence API

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;

    // ...
}