[TOC]
- Jakarta Persistence 3.0 Specification Document
- Jakarta Persistence 3.0 Javadoc
- Spring Data JPA - Reference Documentation
- Java Persistence API - Oracle Software Downloads
- Advanced Spring Data JPA - Specifications and Querydsl
- JPA Criteria Queries
- Using the Criteria API and Metamodel API to Create Basic Typesafe Queries
- @JoinColumn Annotation Explained
- 廖雪峰 jpa 介绍
Java Persistence API 是 Sun 官方提出的 Java 持久化规范。
JPA 和 Hibernate 的关系:JPA 是规范(类似于 JDBC),Hibernate 是 JPA 的实现
Annotation | Desc | |
---|---|---|
@Entity |
表示该类为实体类,将映射到指定的数据库表 | |
@Table(name="table_name") |
指定该类映射表的表名 | |
@Id |
映射主键 | |
@GeneratedValue(strategy="") |
主键生成策略 | |
@Basic |
字段的默认注解,如果没有,会自动添加 | |
@Column |
指定字段详细属性 | |
@Transient |
表示不作映射,可以作用于成员变量和方法 | 暂时的 |
@Temporal |
指定时间格式 | 时间的 |
几个 strategy:
- IDENTITY:ID 自增长,MySQL,Oracle 不支持
- AUTO:啥也不填,JPA 自动选择合适的策略,默认选项
- SEQUENCE:通过序列产生主键,MySQL 不支持
- TABLE:通过表产生主键
@Basic(fetch = FetchType.EAGER, optional = true)
private String email;
如果一个属性没有加任何注解,那么就会默认加上 basic 注解。
两个参数:
- fetch: 属性读取策略,EAGER vs LAZY ,饿汉式和懒汉式,默认是 EAGER
- optional:属性是否允许为 null,默认式 null
几个参数:
- 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(value=)
接受一个枚举类型,可选值有三个:
- DATE 年月日
- TIME 年月日 时分秒
- TIMESTAMP 兼容前两个
Java EE 已经归 Jakarta 组织维护,所以 jakarta 完全兼容 JavaEE,Jakarta Persistence API 就是 JPA。
Persistence 的主要作用就是用来创建 EntityManagerFactory 的。
String persistenceUnitName = "default";
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.format_sql", true);
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName, properties);
jpa 连接池,也是一个接口。主要作用还是创建数据库连接。
EntityManager entityManager = entityManagerFactory.createEntityManager();
// 重载的方法:map 用于提供 entityManager 的属性
EntityManager entityManager = entityManagerFactory.createEntityManager(map);
close()
后,isOpen()
方法测试回返回 false,其他方法不能调用,否则回导致 IllegalStateException 异常。
接口。
临时对象:new 出来的无 id 的对象
游离对象:new 出来的有 id 的对象
懒加载:多次查询,对于不常用的级联数据不取出,等到要用的时候再取出。懒加载都设置了代理类。
Eager加载:一次 join 查询获取所有值
增 persist 删 remove 查 find/getReference
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
// 一共执行了两条查询语句
}
}
强制将内存中的数据写入数据库,进行持久化。
强制刷新。将数据库中的持久化对象的值同步到实体对象上,即更新实例的属性值。
Clear the persistence context, causing all managed entities to become detached.
清除持久上下文环境,断开所有关联的实体。
类似的接口都是用来做 jpql 查询的。
@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 了。
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.
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.
}
支持分页。
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));
继承自 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 的四个步骤:
-
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> { … }
-
Declare query methods on the interface.
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
-
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 { … }
-
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"); } }
如何定义 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
.
有两种定义 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
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.
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 |
In |
findByAgeIn(Collection<Age> ages) |
… where x.age in ?1 |
NotIn |
findByAgeNotIn(Collection<Age> ages) |
… where x.age not in ?1 |
True |
findByActiveTrue() |
… where x.active = true |
False |
findByActiveFalse() |
… where x.active = false |
IgnoreCase |
findByFirstnameIgnoreCase |
… where UPPER(x.firstname) = UPPER(?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);
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);
}
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());
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 producenull
values.@NonNull
: Used on a parameter or return value that must not benull
(not needed on a parameter and return value where@NonNullApi
applies).@Nullable
: Used on a parameter or return value that can benull
.
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
}
- The repository resides in a package (or sub-package) for which we have defined non-null behavior
- Throws an
EmptyResultDataAccessException
when the query does not produce a result. Throws anIllegalArgumentException
when theemailAddress
handed to the method isnull
. - Returns
null
when the query does not produce a result. Also acceptsnull
as the value foremailAddress
. - Returns
Optional.empty()
when the query does not produce a result. Throws anIllegalArgumentException
when theemailAddress
handed to the method isnull
.
@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
创建 Repository 对象
-
直接在 config 中批量注入
@Configuration @EnableJpaRepositories("com.acme.repositories") // 指定 repostory 类的位置 class ApplicationConfiguration { @Bean EntityManagerFactory entityManagerFactory() { // … } }
-
单独创建
RepositoryFactorySupport factory = … // Instantiate factory here UserRepository repository = factory.getRepository(UserRepository.class);
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;
}
}
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 from method names
public interface UserRepository extends Repository<User, Long> { List<User> findByEmailAddressAndLastname(String emailAddress, String lastname); }
-
@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);
}
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
- Valid
Sort
expression pointing to property in domain model. - Invalid
Sort
containing function call. Throws Exception. - Valid
Sort
containing explicitly unsafeOrder
. - Valid
Sort
expression pointing to aliased function.
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);
}
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);
}
代码:https://github.com/wansho/jpa
注意
- 双向 OneToMany 和 双向 ManyToOne 是一样的
- 双向的列名外键的 name 要保持一致
- 如果在 1 的一端
@OneToMany
中使用了mapperBy
属性,则@OneToMany
端就不能再使用@JoinColumn
属性
必须指定一个关系维护端(Owner Side 中间表),可以通过 @ManyToMany
注释中指定 mappedBy
属性来标识其为关系维护端。
Interface used to control query execution.
Query 接口封装了执行数据库查询的相关方法。
获取 Query 对象的方法,是调用 EntityManager 的 createQuery,createNamedQuery,createNativeQuery 方法获取查询对象。
详细的 Query Language 语法和例子,参考 [Jakarta Persistence] 的第四章,Query Language。
A select statement is a string which consists of the following clauses:
• a SELECT clause, which determines the type of the objects or values to be selected.
• a FROM clause, which provides declarations that designate the domain to which the expressions
specified in the other clauses of the query apply.
• an optional WHERE clause, which may be used to restrict the results that are returned by the
query.
• an optional GROUP BY clause, which allows query results to be aggregated in terms of groups.
• an optional HAVING clause, which allows filtering over aggregated groups.
• an optional ORDER BY clause, which may be used to order the results that are returned by the
query.
The basic semantics of a Criteria query consists of a SELECT
clause, a FROM
clause, and an optional WHERE
clause, similar to a JPQL query. Criteria queries set these clauses by using Java programming language objects, so the query can be created in a typesafe manner.
The Jakarta Persistence Criteria API is used to define queries through the construction of object-based query definition objects, rather than use of the string-based approach of the Jakarta Persistence query language. 动态查询机制。
只需要 root 和 criteriabuilder 两个就够了,root 用来构造属性,criteriabuilder 用来构造查询。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.2.Final</version>
</dependency>
查询的构造器,封装了很多查询条件,用于构造查询条件
The javax.persistence.criteria.CriteriaBuilder
interface is used to construct
- Criteria queries
- Selections
- Expressions
- Predicates
- Ordering
init CriteriaBuilder:
EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
顶层查询对象,自定义查询方式(了解,一般不用)
get CriteriaQuery:
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
代表查询的根对象(查询的任何属性都可以从根对象中获取)
For a particular CriteriaQuery
object, the root entity of the query, from which all navigation originates, is called the query root. It is similar to the FROM
clause in a JPQL query.
Create the query root by calling the from
method on the CriteriaQuery
instance. The argument to the from
method is either the entity class or an EntityType<T>
instance for the entity.
The following code sets the query root to the Pet
entity:
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
The following code sets the query root to the Pet
class by using an EntityType<T>
instance:
EntityManager em = ...;
Metamodel m = em.getMetamodel();
EntityType<Pet> Pet_ = m.entity(Pet.class);
Root<Pet> pet = cq.from(Pet_);
public class Item implements Serializable {
private Integer itemId;
private String itemName;
private String itemDescription;
private Integer itemPrice;
// standard setters and getters
}
Session session = HibernateUtil.getHibernateSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Item> cr = cb.createQuery(Item.class);
Root<Item> root = cr.from(Item.class);
cr.select(root);
Query<Item> query = session.createQuery(cr);
List<Item> results = query.getResultList();
To get items having a price of more than 1000:
cr.select(root).where(cb.gt(root.get("itemPrice"), 1000));
Next, getting items having itemPrice less than 1000:
cr.select(root).where(cb.lt(root.get("itemPrice"), 1000));
Items having itemName contain Chair:
cr.select(root).where(cb.like(root.get("itemName"), "%chair%"));
Records having itemPrice in between 100 and 200:
cr.select(root).where(cb.between(root.get("itemPrice"), 100, 200));
Items having itemName in Skate Board, Paint and Glue:
cr.select(root).where(root.get("itemName").in("Skate Board", "Paint", "Glue"));
To check if the given property is null:
cr.select(root).where(cb.isNull(root.get("itemDescription")));
To check if the given property is not null:
cr.select(root).where(cb.isNotNull(root.get("itemDescription")));
chain expressions:
Predicate[] predicates = new Predicate[2];
predicates[0] = cb.isNull(root.get("itemDescription"));
predicates[1] = cb.like(root.get("itemName"), "chair%");
cr.select(root).where(predicates);
To add two expressions with logical operations:
Predicate greaterThanPrice = cb.gt(root.get("itemPrice"), 1000);
Predicate chairItems = cb.like(root.get("itemName"), "Chair%");
Items with the above-defined conditions joined with Logical OR:
cr.select(root).where(cb.or(greaterThanPrice, chairItems));
To get items matching with the above-defined conditions joined with Logical AND:
cr.select(root).where(cb.and(greaterThanPrice, chairItems));
cr.orderBy(
cb.asc(root.get("itemName")),
cb.desc(root.get("itemPrice")));
count
CriteriaQuery<Long> cr = cb.createQuery(Long.class);
Root<Item> root = cr.from(Item.class);
cr.select(cb.count(root));
Query<Long> query = session.createQuery(cr);
List<Long> itemProjected = query.getResultList();
Aggregate function for Average
CriteriaQuery<Double> cr = cb.createQuery(Double.class);
Root<Item> root = cr.from(Item.class);
cr.select(cb.avg(root.get("itemPrice")));
Query<Double> query = session.createQuery(cr);
List avgItemPriceList = query.getResultList();
Other useful aggregate methods that are available are sum(), max(), min() , count() etc.
Specification 是 Spring 框架的一部分,是 Spring 对 Criteria API (root,criteria)的封装
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.
The power of specifications really shines when you combine them to create new Specification
objects.
Specification
offers some “glue-code” default methods to chain and combine Specification
instances. These methods let you extend your data access layer by creating new Specification
implementations and combining them with already existing implementations.
Although this approach(直接按照规则写接口,不实现的方式) is really convenient (you don’t even have to write a single line of implementation code to get the queries executed) it has two drawbacks: first, the number of query methods might grow for larger applications because of - and that’s the second point - the queries define a fixed set of criterias. To avoid these two drawbacks, wouldn’t it be cool if you could come up with a set of atomic predicates that you could combine dynamically to build your query?
想要引入 Specifications 特性,必须继承 JpaSpecificationExecutor
接口。
Specification 是一个函数式接口
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}
Demo: Specifications for a Customer
public class CustomerSpecs {
public static Specification<Customer> isLongTermCustomer() {
return (root, query, builder) -> {
LocalDate date = LocalDate.now().minusYears(2);
return builder.lessThan(root.get(Customer_.createdAt), date);
};
}
public static Specification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
return (root, query, builder) -> {
// build query here
};
}
}
// Using a simple Specification
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
// Combined Specifications
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
isLongTermCustomer().or(hasSalesOfMoreThan(amount)));
Another Demo:
private Specification<EntityGraph> getSpecification(PageBean pageBean) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicateList = new ArrayList<>();
if(StringUtils.isNotEmpty(pageBean.getFilterValue())){
predicateList.add(criteriaBuilder.equal(root.get(DEFAULT_FILTER_FIELD), pageBean.getFilterValue()));
}
return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
};
}
root 负责取成员变量,query 是 CriteriaQuery 类型,负责生成 root,criteriaBuilder 是 CriteriaBuilder 类型
常见配置:
jpa:
database: mysql
show-sql: true
properties:
hibernate:
hbm2ddl:
# 自动更新维护表结构
auto: update
dialect: org.hibernate.dialect.MySQL55Dialect
format_sql: true # 对 sql 进行格式化
open-in-view: false
-
@LastModifiedDate 不起作用的解决方法
在实体中添加注解 @EntityListeners(AuditingEntityListener.class)监听实体变化
在自动更新时间戳字段增加 @LastModifiedDate
在Spring boot启动类增加注解 @EnableJpaAuditing启用JPA审计(自动填充默认值)