【SpringData&JPA从入门到精通】03-SpringData-Repository 子接口
Repository 子接口
1、Repository 子接口
在之前 01-Repository 中我们介绍过 Repository 的几个子接口,现在重新回顾下这几个子接口的知识
CrudRepository
:继承于 Repository,实现了一组 CRUD 相关的方法PagingAndSortingRepository
:继承于 CrudRepository,实现了一组分页排序相关的方法JpaRepository
:继承于 PagingAndSortingRepository,实现了一组 JPA 规范相关的方法
Repository (org.springframework.data.repository)
----CrudRepository (org.springframework.data.repository)
--------PagingAndSortingRepository (org.springframework.data.repository)
------------JpaRepository (org.springframework.data.jpa.repository)
另外,还介绍了
自定义的 XxxRepository
:需要继承 JpaRepository,这样的XxxRepository
接口就具备了通用的数据访问控制层的能力JpaSpecificationExecutor
:不属于 Repository 体系,实现一组 JPA Criteria 查询相关的方法
2、CrudRepository 接口
CrudRepository
方法一览
CrudRepository
源码解析
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
// 保存实体
<S extends T> S save(S entity);
// 保存实体集
<S extends T> Iterable<S> save(Iterable<S> entities);
// 通过 id 查找实体
T findOne(ID id);
// 判断指定 id 的实体是否存在
boolean exists(ID id);
// 返回该类型的所有实例
Iterable<T> findAll();
// 返回指定 ID 类型的所有实例
Iterable<T> findAll(Iterable<ID> ids);
// 返回可用实体数
long count();
// 删除指定 ID 的实体
void delete(ID id);
// 删除实体
void delete(T entity);
// 删除实体集
void delete(Iterable<? extends T> entities);
// 删除所有实体
void deleteAll();
}
测试方法
PersonCrudRepository 类
public interface PersonCrudRepository extends CrudRepository<Person, Integer> {}
PersonService 类
@Service
public class PersonService
{
@Autowired
private PersonCrudRepository personCrudRepository;
@Transactional
public void savePersons(List<Person> personList) {
personCrudRepository.save(personList);
}
}
Test 类
@Test
public void testSavePersons() {
List<Person> personList = new ArrayList<>();
Person person;
for (int i = 'a'; i < 'z'; i++) {
person = new Person();
person.setAddressId(i);
person.setBirthDay(new Date());
person.setLastName((char) i + "" + (char) i);
person.setEmail((char) i + "" + (char) i + "@qq.com");
personList.add(person);
}
service.savePersons(personList);
}
日志信息
Hibernate:
insert
into
jpa_persons
(add_id, address_id, birth_day, email, last_name)
values
(?, ?, ?, ?, ?)
# ...
数据表 JPA_PERSONS
数据
3、PagingAndSortingRepository 接口
PagingAndSortingRepository
方法一览
PagingAndSortingRepository
源码解析
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
// 返回按给定选项排序的所有实体
Iterable<T> findAll(Sort sort);
// 返回一个满足分页限制的分页对象
Page<T> findAll(Pageable pageable);
}
测试方法
PersonPagingAndSortingRepository 类
public interface PersonPagingAndSortingRepository extends PagingAndSortingRepository<Person, Integer> {}
Test 类
@Test
public void testPagingAndSorting() {
int pageNo = 2; // 从 0 开始
int pageSize = 10;
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC, "id"), new Sort.Order(Sort.Direction.DESC, "id"));
Pageable pageable = new PageRequest(pageNo, pageSize, sort);
Page<Person> page = repository.findAll(pageable);
// 格式化输出
PrintPersonUtil.print(page);
}
为了更直观且美观地显示 Person 信息,这里我封装了一个输出 Person 信息的类
public class PrintPersonUtil
{
public static void print(Page<Person> page) {
printPageInfo(page);
printPersonInfo(page.getContent());
}
private static void printPageInfo(Page<Person> page) {
System.out.println("总计:" + page.getTotalElements() + "条");
System.out.println("总页数:" + page.getTotalPages() + "页");
System.out.println("当前页:第" + (page.getNumber() + 1) + "页");
System.out.println("当前页容量:" + page.getSize() + "条");
System.out.println("当前页实际数:" + page.getNumberOfElements() + "条");
System.out.println("当前页内容:");
}
private static void printPersonInfo(List<Person> personList) {
AsciiTable at = new AsciiTable();
at.addRule();
at.addRow(Person.ID, Person.ADDRESSID, Person.LASTNAME, Person.EMAIL);
at.addRule();
for (Person person : personList) {
at.addRow(person.getId(), person.getAddressId(), person.getLastName(), person.getEmail());
at.addRule();
}
System.out.println(at.render());
}
}
此类引入了一个如下依赖
<dependency>
<groupId>de.vandermeer</groupId>
<artifactId>asciitable</artifactId>
<version>0.3.2</version>
</dependency>
日志信息
Hibernate:
select
count(*) as col_0_0_
from
jpa_persons person0_
Hibernate:
select
person0_.id as id1_1_,
person0_.add_id as add_id6_1_,
person0_.address_id as address_2_1_,
person0_.birth_day as birth_da3_1_,
person0_.email as email4_1_,
person0_.last_name as last_nam5_1_
from
jpa_persons person0_
order by
person0_.id asc,
person0_.id desc limit ?,
?
总计:25条
总页数:3页
当前页:第3页
当前页容量:10条
当前页实际数:5条
当前页内容:
┌───────────────────┬───────────────────┬───────────────────┬──────────────────┐
│id │addressId │lastName │email │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│223 │110 │nn │nn@qq.com │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│224 │120 │xx │xx@qq.com │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│225 │117 │uu │uu@qq.com │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│316 │116 │tt │tt@qq.com │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│322 │107 │kk │kk@qq.com │
└───────────────────┴───────────────────┴───────────────────┴──────────────────┘
4、JpaRepository 接口
JpaRepository
方法一览
JpaRepository
源码解析
@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
List<T> findAll();
List<T> findAll(Sort sort);
List<T> findAll(Iterable<ID> ids);
<S extends T> List<S> save(Iterable<S> entities);
// 刷新对数据库的所有挂起更改
void flush();
// 保存实体并立即刷新更改
T saveAndFlush(T entity);
// 批量删除给定的实体
void deleteInBatch(Iterable<T> entities);
// 删除批量调用中的所有实体
void deleteAllInBatch();
}
测试方法 1
PersonRepository 类
public interface PersonJpaRepository extends JpaRepository<Person, Integer> {}
PersonService 类
@Service
public class PersonService
{
@Autowired
private PersonJpaRepository personJpaRepository;
@Transactional
public Person saveAndFlushPerson(Person person) {
return personJpaRepository.saveAndFlush(person);
}
}
Test 类
@Test
public void testSaveAndFlush() {
Person person = new Person();
person.setLastName("xy");
person.setEmail("xy@qq.com");
person.setBirthDay(new Date());
service.saveAndFlushPerson(person);
}
日志信息
Hibernate:
insert
into
jpa_persons
(add_id, address_id, birth_day, email, last_name)
values
(?, ?, ?, ?, ?)
数据表 JPA_PERSONS
数据
测试方法 2
Test 类
@Test
public void testSaveAndFlush() {
Person person = new Person();
person.setLastName("xyyy");
person.setEmail("xyyy@qq.com");
person.setBirthDay(new Date());
person.setId(324);
Person person2 = service.saveAndFlushPerson(person);
System.out.println("person == person2: " + (person == person2));
}
日志信息
Hibernate:
select
person0_.id as id1_1_0_,
person0_.add_id as add_id6_1_0_,
person0_.address_id as address_2_1_0_,
person0_.birth_day as birth_da3_1_0_,
person0_.email as email4_1_0_,
person0_.last_name as last_nam5_1_0_
from
jpa_persons person0_
where
person0_.id=?
Hibernate:
update
jpa_persons
set
add_id=?,
address_id=?,
birth_day=?,
email=?,
last_name=?
where
id=?
person == person2: false
数据表 JPA_PERSONS
数据
小结:
JpaRepository
接口中saveAndFlush
方法相当于 JPA API 中 EntityManager 对象中的 merge 方法
5、JpaSpecificationExecutor 接口
不属于 Repository 体系,实现一组 JPA Criteria 查询相关的方法
JpaSpecificationExecutor
方法一览
JpaSpecificationExecutor
源码解析
public interface JpaSpecificationExecutor<T> {
// 返回满足匹配条件的单个实体
T findOne(Specification<T> spec);
// 返回满足匹配条件的所有实体
List<T> findAll(Specification<T> spec);
// 返回满足匹配条件的Page对象
Page<T> findAll(Specification<T> spec, Pageable pageable);
// 返回满足匹配条件和排序的实体集
List<T> findAll(Specification<T> spec, Sort sort);
// 返回满足匹配条件的实例数
long count(Specification<T> spec);
}
其中 Specification
:封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象
public interface Specification<T> {
// 根据给定的 Criteria 创建一个 WHERE 子句,返回一个 Predicate 作为查询条件
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
测试方法
目标:实现带查询条件的分页,id > ? 的条件
调用 JpaSpecificationExecutor 的
Page<T> findAll(Specification<T> spec, Pageable pageable);
方法
Specification
:封装了 JPA Criteria 的查询条件
Pageable
:封装了请求分页的信息,例如 pageNo,pageSize,Sort
PersonJpaSpecificationExecutor 类
public interface PersonJpaSpecificationExecutor extends JpaRepository<Person, Integer>, JpaSpecificationExecutor<Person> {}
踩坑:这里注意不能只继承
JpaSpecificationExecutor
接口,还要至少实现 Repository 接口或其子接口才行
Test 类
@Test
public void testJpaSpecificationExecutor() {
// specification:通常使用其匿名内部类
Specification<Person> specification = new Specification<Person>()
{
/**
* @param *root:代表查询到的类
* @param query:可以从中获取 Root 对象,即告知 JPA Criteria 要查询哪一个实体类。
* 还可以来添加查询条件,还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象。
* @param *cb:CriteriaBuilder 对象。用于创建 Criteria 相关对象的工厂,从中获取 Predicate 对象
* @return *Predicate类型,代表一个查询条件
*/
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Integer> path = root.get("id");
Predicate predicate = cb.gt(path, 210);
return predicate;
}
};
// pageable
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC, Person.ADDRESSID),
new Sort.Order(Sort.Direction.ASC, Person.ID));
Pageable pageable = new PageRequest(2, 5, sort);
// page
Page<Person> page = executor.findAll(specification, pageable);
// 格式化输出
PrintPersonUtil.print(page);
}
其中 root 就是 导航树 的 根(即查询到的类),由此可以 导航 到其 分支(即类的属性)
日志信息
Hibernate:
select
count(*) as col_0_0_
from
jpa_persons person0_
where
person0_.id>210
Hibernate:
select
person0_.id as id1_1_,
person0_.add_id as add_id6_1_,
person0_.address_id as address_2_1_,
person0_.birth_day as birth_da3_1_,
person0_.email as email4_1_,
person0_.last_name as last_nam5_1_
from
jpa_persons person0_
where
person0_.id>210
order by
person0_.address_id asc,
person0_.id asc limit ?,
?
总计:15条
总页数:3页
当前页:第3页
当前页容量:5条
当前页实际数:5条
当前页内容:
┌───────────────────┬───────────────────┬───────────────────┬──────────────────┐
│id │addressId │lastName │email │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│222 │112 │pp │pp@qq.com │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│221 │114 │rr │rr@qq.com │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│316 │116 │tt │tt@qq.com │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│225 │117 │uu │uu@qq.com │
├───────────────────┼───────────────────┼───────────────────┼──────────────────┤
│224 │120 │xx │xx@qq.com │
└───────────────────┴───────────────────┴───────────────────┴──────────────────┘
踩坑:本来打算对匿名内部类使用 lambda 表达式进行简化,但执行时报错了,才知道 lambda 表达式是有前提条件的,即:接口必须是函数式接口
函数式接口 需要满足两个条件:
- 使用
@FunctionalInterface
注解修饰- 接口中有且仅有一个抽象方法
而
Specification
虽然有且仅有一个抽象方法,但并没有使用@FunctionalInterface
注解修饰,所以不能进行 lambda 简化行为
6、自定义 Repository 方法
- 为某个 Repository 添加自定义方法
- 为所有 Repository 添加自定义方法(用的不多,略)
为某个 Repository 添加自定义方法
步骤:
- 1)定义一个接口:声明要添加的,并自实现的方法
- 2)提供该接口的实现类:类名需在要声明的 Repository 后添加 Impl,并实现方法
- 3)声明 Repository 接口,并继承 1)声明的接口
- 4)使用
注意:默认情况下,Spring Data 会在 base-package
中查找“接口名Impl”作为实现类,也可以通过 repository-impl-postfix
声明后缀
测试方法
PersonCustomDao 类
interface PersonCustomDao
{
Person findById(Integer id);
}
PersonCustomDaoImpl 类
class PersonCustomDaoImpl implements PersonCustomDao
{
@PersistenceContext
private EntityManager entityManager;
@Override
public Person findById(Integer id) {
return entityManager.find(Person.class, id);
}
}
PersonCustomRepository 类
public interface PersonCustomRepository
extends JpaRepository<Person, Integer>, JpaSpecificationExecutor<Person>, PersonCustomDao {}
注意:这里除了实现自定义接口 PersonCustomDao 之外,
JpaRepository
接口也是必不可少的(前面 踩坑 提到过)那为什么又要实现
JpaSpecificationExecutor
呢?其实这个并不是必须的,但是自定义接口目的是在基本的 CRUD、分页等行为仍不满足要求时进行弥补。另外,同时继承JpaRepository
和JpaSpecificationExecutor
也是很常用的写法,使该接口具备通用的数据访问和操作能力
Test 类
@Test
public void testCustomRepository() {
Person person = repository.findById(221);
System.out.println(person);
}
日志信息
Hibernate:
select
person0_.id as id1_1_,
person0_.add_id as add_id6_1_,
person0_.address_id as address_2_1_,
person0_.birth_day as birth_da3_1_,
person0_.email as email4_1_,
person0_.last_name as last_nam5_1_
from
jpa_persons person0_
where
person0_.id=?
Person{id=221, addressId=114, lastName='rr', email='rr@qq.com', birthDay=2022-05-26 22:34:33.0}
总结
本节主要掌握 Repository 的几个子接口
CrudRepository
:增删改查方法PagingAndSortingRepository
:分页查询方法JpaRepository
:JPA 规范方法
另外,还有两个比较特殊的接口
JpaSpecificationExecutor
:带查询的分页- 自定义的 Repository:自实现接口方法
附上导图,仅供参考