JPA 深入学习
[TOC]

reference

Intro

Java Persistence API 是 Sun 官方提出的 Java 持久化规范。
JPA 和 Hibernate 的关系:JPA 是规范(类似于 JDBC),Hibernate 是 JPA 的实现

annotations

Annotation
Desc
Text
@Entity
表示该类为实体类,将映射到指定的数据库表
@Table(name="table_name")
指定该类映射表的表名
@Id
映射主键
@GeneratedValue(strategy="")
主键生成策略
@Basic
字段的默认注解,如果没有,会自动添加
@Column
指定字段详细属性
@Transient
表示不作映射,可以作用于成员变量和方法
暂时的
@Temporal
指定时间格式
时间的

@GeneratedValue

几个 strategy:
  • IDENTITY:ID 自增长,MySQL,Oracle 不支持
  • AUTO:啥也不填,JPA 自动选择合适的策略,默认选项
  • SEQUENCE:通过序列产生主键,MySQL 不支持
  • TABLE:通过表产生主键

@Basic

@Basic(fetch = FetchType.EAGER, optional = true)
private String email;
如果一个属性没有加任何注解,那么就会默认加上 basic 注解。
两个参数:
  • fetch: 属性读取策略,EAGER vs LAZY ,饿汉式和懒汉式,默认是 EAGER
  • optional:属性是否允许为 null,默认式 null

@Column

几个参数:
  • name: 指定列名
  • length:指定长度,只对字符串有效
  • nullable:是否允许为 null
  • unique:是否 unique
  • columnDefinition:表示该属性在数据库中的实际类型,JPA 无法判断 Date 要转成数据库的 Date,Time 还是 TIMESTAMP
String 类型默认映射成 varchar,如果要映射成特定数据库的 BLOB 或 TEXT,则需要指定 columnDefinition。BigInteger 默认映射成长度为 19 的 decimal,如果想让 JPA 映射更大的数,则需要指定 columnDefinition="decimal(20,2)"

@Temporal

@Temporal(value=)
接受一个枚举类型,可选值有三个:
  • DATE 年月日
  • TIME 年月日 时分秒
  • TIMESTAMP 兼容前两个

jpa-api

Java EE 已经归 Jakarta 组织维护,所以 jakarta 完全兼容 JavaEE,Jakarta Persistence API 就是 JPA。

Persistence

Persistence 的主要作用就是用来创建 EntityManagerFactory 的。
String persistenceUnitName = "default";
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.format_sql", true);
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName, properties);

EntityManagerFactory

jpa 连接池,也是一个接口。主要作用还是创建数据库连接。
EntityManager entityManager = entityManagerFactory.createEntityManager();
// 重载的方法:map 用于提供 entityManager 的属性
EntityManager entityManager = entityManagerFactory.createEntityManager(map);
close() 后,isOpen() 方法测试回返回 false,其他方法不能调用,否则回导致 IllegalStateException 异常。

EntityManager

接口。
临时对象:new 出来的无 id 的对象
游离对象:new 出来的有 id 的对象
懒加载:多次查询,对于不常用的级联数据不取出,等到要用的时候再取出。懒加载都设置了代理类。
Eager加载:一次 join 查询获取所有值
增 persist 删 remove 查 find/getReference

find / getReference / persist / remove / merge

package top.wansho.jpa;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import top.wansho.jpa.helloworld.Customer;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import static org.junit.jupiter.api.Assertions.*;
public class EntityManagerTest {
private EntityManagerFactory entityManagerFactory;
private EntityManager entityManager;
private EntityTransaction entityTransaction;
@BeforeEach
public void init(){
entityManagerFactory = Persistence.createEntityManagerFactory("default");
entityManager = entityManagerFactory.createEntityManager();
entityTransaction = entityManager.getTransaction();
// 开启事务
entityTransaction.begin();
}
@AfterEach
public void destroy(){
// 提交事务
entityTransaction.rollback();
System.out.println("entityTransaction is active? " + entityTransaction.isActive()); // true
// entityTransaction.commit();
System.out.println("entityTransaction is active? " + entityTransaction.isActive()); // false
entityManager.close();
entityManagerFactory.close();
}
@Test
public void testFind(){
// 类似于 Hibernate 中的 get 方法,获取 id 为 1 的记录
Customer customer = entityManager.find(Customer.class, 1);
System.out.println("-------------------------");
System.out.println(customer);
// 先打印查询语句,再打印 --,饿汉式
}
@Test
public void testGetReference(){
// 类似于 Hibernate 中的 load 方法
Customer customer = entityManager.getReference(Customer.class, 1); // 只获取引用
System.out.println(customer.getClass().getName()); // top.wansho.jpa.helloworld.Customer$HibernateProxy$GLVOzPIc
System.out.println("-------------------------");
System.out.println(customer);
// 先打印 --,再打印查询语句,懒加载,懒汉式,customer 是一个代理对象
}
@Test
public void testPersist(){
// 类似于 Hibernate 的 save 方法,使对象由临时状态变为持久化状态
// 和 Hibernate 的 save 方法有些许不同,如果对象有 id,则不能执行 insert 操作,而会抛出异常
Customer customer = new Customer();
customer.setAge(13);
customer.setEmail("[email protected]");
customer.setLastName("www");
entityManager.persist(customer);
System.out.println(customer.getId()); // 有 id 了
}
@Test
public void testRemove(){
// 类似于 Hibernate 的 delete 对象,把对象对应的记录从数据库中移除
// 注意:该方法只能移除持久化对象,而 hibernate 的 delete 方法实际上还可以移除游离对象
// Customer customer = new Customer();
// customer.setId(1); // 还是因为代理类的原因,不能删除游离对象
Customer customer = entityManager.find(Customer.class, 1);
entityManager.remove(customer);
}
@Test
public void testMerge1(){
// 1. 测试临时对象
// 如果传入一个临时对象(没有 id),会创建一个新的对象,把临时对象的属性复制到新的对象中,然后对新的对象执行持久化操作
// 所以新的对象会有 id,而临时对象没有 id
Customer customer = new Customer();
customer.setEmail("[email protected]");
customer.setAge(18);
customer.setLastName("ww");
Customer customer2 = entityManager.merge(customer);
System.out.println(customer.getId()); // null
System.out.println(customer2.getId()); // 有 id
}
@Test
public void testMerge2(){
// 2. 测试游离对象
// 2.1 在 EntityManager 缓存中没有该对象
// 2.2 在数据库中也没有对应的记录
// 2.3 JPA 会创建一个新的对象,然后把当前游离对象的属性复制到新创建的对象中
// 2.4 对新创建的对象执行 insert 操作
Customer customer = new Customer();
customer.setEmail("[email protected]");
customer.setAge(18);
customer.setLastName("ww");
customer.setId(20);
Customer customer2 = entityManager.merge(customer);
System.out.println(customer.getId()); // 20
System.out.println(customer2.getId()); // 6,新对象 的 id 自增,和写入的 id 不一样
}
@Test
public void testMerge3(){
// 3. 测试游离对象
// 3.1 在 EntityManager 缓存中没有该对象
// 3.2 在数据库中有对应的记录
// 3.3 JPA 会查询对应的记录,然后返回该记录对应的对象,然后再把游离对象的属性复制到查询到的对象中
// 3.4 对查询到的对象进行 update
Customer customer = new Customer();
customer.setEmail("[email protected]");
customer.setAge(18);
customer.setLastName("ww");
customer.setId(6);
Customer customer2 = entityManager.merge(customer);
System.out.println(customer2 == customer); // false
System.out.println(customer.getId()); // 6
System.out.println(customer2.getId()); // 6,新对象 的 id 自增,和写入的 id 不一样
}
@Test
public void testMerge4(){
// 4. 测试游离对象
// 4.1 在 EntityManager 缓存有该对象
// 4.2 JPA 会把游离对象的属性直接复制给缓存中的对象
// 4.3 对缓存中的对象执行 update
Customer customer = new Customer();
customer.setEmail("[email protected]");
customer.setAge(18);
customer.setLastName("ww");
customer.setId(6);
Customer customer2 = entityManager.find(Customer.class, 6);
entityManager.merge(customer);
System.out.println(customer2 == customer);
}
@Test
public void testFlush(){
// 同 Hibernate 中 session 的 flush 方法,强制写入数据库
Customer customer = entityManager.find(Customer.class, 1);
customer.setLastName("dd");
entityManager.flush();
}
@Test
public void testRefresh(){
// 从数据库中读最新的数据,强制刷新
Customer customer = entityManager.find(Customer.class, 2); // select 1
customer = entityManager.find(Customer.class, 2); // jpa 有一级缓存,这条语句没有去数据库中查询
entityManager.refresh(customer); // select 2
// 一共执行了两条查询语句
}
}

flush / setFlushMode / getFlushMode

强制将内存中的数据写入数据库,进行持久化。

refresh

强制刷新。将数据库中的持久化对象的值同步到实体对象上,即更新实例的属性值。

clear

Clear the persistence context, causing all managed entities to become detached.
清除持久上下文环境,断开所有关联的实体。

isOpen / getTransaction / close

createQuery

类似的接口都是用来做 jpql 查询的。

EntityTransaction

@BeforeEach
public void init(){
entityManagerFactory = Persistence.createEntityManagerFactory("default");
entityManager = entityManagerFactory.createEntityManager();
entityTransaction = entityManager.getTransaction();
// 开启事务
entityTransaction.begin();
}
@AfterEach
public void destroy(){
entityTransaction.rollback(); // 回滚事务
// entityTransaction.commit(); // 提交事务
System.out.println("entityTransaction is active? " + entityTransaction.isActive()); // false
entityManager.close();
entityManagerFactory.close();
}
事务,要么提交,要么回滚,不能同时存在。提交或回滚后,事务就 isNotActive 了。

springdata-repositories

The central interface in the Spring Data repository abstraction is Repository. It takes the domain class to manage as well as the ID type of the domain class as type arguments. This interface acts primarily as a marker interface to capture the types to work with and to help you to discover interfaces that extend this one. The CrudRepository interface provides sophisticated CRUD functionality for the entity class that is being managed.

CrudRepository

public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity); // Saves the given entity.
Optional<T> findById(ID primaryKey); //
Iterable<T> findAll();
long count();
void delete(T entity);
boolean existsById(ID primaryKey);
// … more functionality omitted.
}

PagingAndSortingRepository

支持分页。
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));

JpaRepository

继承自 PagingAndSortingRepository 接口,支持分页。
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAllById(Iterable<ID> var1);
<S extends T> List<S> saveAll(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

Query Methods

创建 Query Methods 的四个步骤:
  1. 1.
    Declare an interface extending Repository or one of its subinterfaces and type it to the domain class and ID type that it should handle, as shown in the following example:
    interface PersonRepository extends Repository<Person, Long> {}
  2. 2.
    Declare query methods on the interface.
    interface PersonRepository extends Repository<Person, Long> {
    List<Person> findByLastname(String lastname);
    }
  3. 3.
    Set up Spring to create proxy instances for those interfaces, either with JavaConfig or with XML configuration.
    import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    @EnableJpaRepositories
    class Config {}
  4. 4.
    Inject the repository instance and use it, as shown in the following example:
    class SomeClient {
    private final PersonRepository repository;
    SomeClient(PersonRepository repository) {
    this.repository = repository;
    }
    void doSomething() {
    List<Person> persons = repository.findByLastname("Matthews");
    }
    }

Defining Repository Interfaces

如何定义 repository 接口
To define a repository interface, you first need to define a domain class-specific repository interface. The interface must extend Repository and be typed to the domain class and an ID type. If you want to expose CRUD methods for that domain type, extend CrudRepository instead of Repository.

Defining Query Methods

有两种定义 Query Methods 的方法
The repository proxy has two ways to derive a store-specific query from the method name:
  • By deriving the query from the method name directly. 采用函数名的方式
  • By using a manually defined query. 写 JPQL

Query 的查找策略

With XML configuration, you can configure the strategy at the namespace through the query-lookup-strategy attribute. For Java configuration, you can use the queryLookupStrategy attribute of the Enable${store}Repositories annotation. Some strategies may not be supported for particular datastores.

Query Demos

interface PersonRepository extends Repository<Person, Long> {
// 注意,此处的参数就是方法名用到的参数
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
/***
* Limiting the result size of a query with Top and First
*/
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
}
Parsing query method names is divided into subject and predicate. The first part (find…By, exists…By) defines the subject of the query, the second part forms the predicate.
The first By acts as a delimiter to indicate the start of the actual criteria predicate. At a very basic level, you can define conditions on entity properties and concatenate them with And and Or.
JPA 根据函数名构造 SQL 查询:
Keyword
Sample
JPQL snippet
Distinct
findDistinctByLastnameAndFirstname
select distinct … where x.lastname = ?1 and x.firstname = ?2
And
findByLastnameAndFirstname
… where x.lastname = ?1 and x.firstname = ?2
Or
findByLastnameOrFirstname
… where x.lastname = ?1 or x.firstname = ?2
Is, Equals
findByFirstname,findByFirstnameIs,findByFirstnameEquals
… where x.firstname = ?1
Between
findByStartDateBetween
… where x.startDate between ?1 and ?2
LessThan
findByAgeLessThan
… where x.age < ?1
LessThanEqual
findByAgeLessThanEqual
… where x.age <= ?1
GreaterThan
findByAgeGreaterThan
… where x.age > ?1
GreaterThanEqual
findByAgeGreaterThanEqual
… where x.age >= ?1
After
findByStartDateAfter
… where x.startDate > ?1
Before
findByStartDateBefore
… where x.startDate < ?1
IsNull, Null
findByAge(Is)Null
… where x.age is null
IsNotNull, NotNull
findByAge(Is)NotNull
… where x.age not null
Like
findByFirstnameLike
… where x.firstname like ?1
NotLike
findByFirstnameNotLike
… where x.firstname not like ?1
StartingWith
findByFirstnameStartingWith
… where x.firstname like ?1 (parameter bound with appended %)
EndingWith
findByFirstnameEndingWith
… where x.firstname like ?1 (parameter bound with prepended %)
Containing
findByFirstnameContaining
… where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy
findByAgeOrderByLastnameDesc
… where x.age = ?1 order by x.lastname desc
Not
findByLastnameNot
… where x.lastname <> ?1
根据属性的属性进行查找
x.address.zipCode
List<Person> findByAddress_ZipCode(ZipCode zipCode);
list 作为参数
@Query("select c from Concept c where c.id in ?1")
List<Concept> findByIds(List idList);

Pageable / Page

Demo:
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
Page:
Page 中封装了当前页的内容,和总页数。
public interface Page<T> extends Slice<T> {
static <T> Page<T> empty() {
return empty(Pageable.unpaged());
}
static <T> Page<T> empty(Pageable pageable) {
return new PageImpl(Collections.emptyList(), pageable, 0L);
}
int getTotalPages();
long getTotalElements();
<U> Page<U> map(Function<? super T, ? extends U> var1);
}
Pageable:
Pageable 中封装了分页的规则,Pageable 的构建 PageRequest.of
private Pageable getPageable(PageBean pageBean){
// 默认根据 name 进行 desc 排序
Sort sort = Sort.by(Sort.Direction.DESC, DEFAULT_SORT_FIELD);
if(StringUtils.isNotBlank(pageBean.getSortField())) {
if (StringUtils.isNotBlank(pageBean.getSortOrder()) && pageBean.getSortOrder().equalsIgnoreCase("desc")) {
sort = Sort.by(Sort.Direction.DESC, pageBean.getSortField());
} else {
sort = Sort.by(Sort.Direction.ASC, pageBean.getSortField());
}
}
// 索引页从 0 开始,故需要 - 1
int page = (int)pageBean.getCurrentPage() - 1;
int size = (int)pageBean.getPageSize();
return PageRequest.of(page, size, sort);
}

Sort

Defining sort expressions:
Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());
Defining sort expressions by using the type-safe API:
TypedSort<Person> person = Sort.sort(Person.class);
Sort sort = person.by(Person::getFirstname).ascending()
.and(person.by(Person::getLastname).descending());

null 处理

As of Spring Data 2.0, repository CRUD methods that return an individual aggregate instance use Java 8’s Optional to indicate the potential absence of a value.
Repository methods returning collections, collection alternatives, wrappers, and streams are guaranteed never to return null but rather the corresponding empty representation. See “Repository query return types” for details.
三个常见的 null 注解:
  • @NonNullApi: Used on the package level to declare that the default behavior for parameters and return values is, respectively, neither to accept nor to produce null values.
  • @NonNull: Used on a parameter or return value that must not be null (not needed on a parameter and return value where @NonNullApi applies).
  • @Nullable: Used on a parameter or return value that can be null.
Using different nullability constraints Demo:
package com.acme; // 1
import org.springframework.lang.Nullable;
interface UserRepository extends Repository<User, Long> {
User getByEmailAddress(EmailAddress emailAddress); // 2
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress); // 3
Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); // 4
}
  1. 1.
    The repository resides in a package (or sub-package) for which we have defined non-null behavior
  2. 2.
    Throws an EmptyResultDataAccessException when the query does not produce a result. Throws an IllegalArgumentException when the emailAddress handed to the method is null.
  3. 3.
    Returns null when the query does not produce a result. Also accepts null as the value for emailAddress.
  4. 4.
    Returns Optional.empty() when the query does not produce a result. Throws an IllegalArgumentException when the emailAddress handed to the method is null.

Streaming Query Results

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

Creating Repository Instances

创建 Repository 对象
  1. 1.
    直接在 config 中批量注入
    @Configuration
    @EnableJpaRepositories("com.acme.repositories") // 指定 repostory 类的位置
    class ApplicationConfiguration {
    @Bean
    EntityManagerFactory entityManagerFactory() {
    // …
    }
    }
  2. 2.
    单独创建
    RepositoryFactorySupport factory =// Instantiate factory here
    UserRepository repository = factory.getRepository(UserRepository.class);

JPA Repositories

This chapter points out the specialties for repository support for JPA.

配置

@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
class ApplicationConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.acme.domain");
factory.setDataSource(dataSource());
return factory;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}

persist

Saving an entity can be performed with the CrudRepository.save(…) method. It persists or merges the given entity by using the underlying JPA EntityManager. If the entity has not yet been persisted, Spring Data JPA saves the entity with a call to the entityManager.persist(…) method. Otherwise, it calls the entityManager.merge(…) method.

Query creation

create query

  1. 1.
    Query creation from method names
    public interface UserRepository extends Repository<User, Long> {
    List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
    }
  2. 2.
    @Query
    public interface UserRepository extends JpaRepository<User, Long> {
    @Query("select u from User u where u.firstname like %?1")
    List<User> findByFirstnameEndsWith(String firstname);
    }
    2.1 native query
    public interface UserRepository extends JpaRepository<User, Long> {
    @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
    User findByEmailAddress(String emailAddress);
    }
Demo: declare native count queries for pagination at the query method by using @Query
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}

sort

public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.lastname like ?1%")
List<User> findByAndSort(String lastname, Sort sort);
@Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%")
List<Object[]> findByAsArrayAndSort(String lastname, Sort sort);
}
repo.findByAndSort("lannister", Sort.by("firstname")); //1
repo.findByAndSort("stark", Sort.by("LENGTH(firstname)")); //2
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); //3
repo.findByAsArrayAndSort("bolton", Sort.by("fn_len")); //4
  1. 1.
    Valid Sort expression pointing to property in domain model.
  2. 2.
    Invalid Sort containing function call. Throws Exception.
  3. 3.
    Valid Sort containing explicitly unsafe Order.
  4. 4.
    Valid Sort expression pointing to aliased function.

Using Named Parameters

public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname,
@Param("firstname") String firstname);
}

Specifications

JPA 2 introduces a criteria API that you can use to build queries programmatically. By writing a criteria, you define the where clause of a query for a domain class. Taking another step back, these criteria can be regarded as a predicate over the entity that is described by the JPA criteria API constraints.
Spring Data JPA takes the concept of a specification from Eric Evans' book, “Domain Driven Design”, following the same semantics and providing an API to define such specifications with the JPA criteria API. To support specifications, you can extend your repository interface with the JpaSpecificationExecutor interface, as follows:
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
}
The additional interface has methods that let you run specifications in a variety of ways. For example, the findAll method returns all entities that match the specification, as shown in the following example:
List<T> findAll(Specification<T> spec);
The Specification interface is defined as follows:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}

foreign key

代码:https://github.com/wansho/jpa

OneToOne

单向 ManyToOne

单向 OneToMany

双向 OneToMany

注意
  • 双向 OneToMany 和 双向 ManyToOne 是一样的
  • 双向的列名外键的 name 要保持一致