通過上一篇筆記的,我們掌握了SpringData的相關概念及簡單的用法。但上一篇筆記主要講的是Dao層接口直接繼承Repository接口,然后再自己定義方法。主要闡述了自定義方法時的一些規則及SpringData是如何來解析這些方法的。實際上,一些常用的方法SpringData已經幫我們定義好了,我們只需要定義Dao層接口時繼承Repository的有相關功能子接口就ok了。本文主要講的是Repository各個子接口有什么功能,了解了子接口的功能后,后續開發dao層就方便了。
這里再強調一下使用SpringData開發Dao層的步驟,這個步驟很關鍵。下面這段話是從上一篇筆記中copy過來的
Spring Data JPA 進行持久層(即Dao)開發一般分三個步驟:
- 聲明持久層的接口,該接口繼承 Repository(或Repository的子接口,其中定義了一些常用的增刪改查,以及分頁相關的方法)。
- 在接口中聲明需要的業務方法。Spring Data 將根據給定的策略生成實現代碼。
- 在 Spring 配置文件中增加一行聲明,讓 Spring 為聲明的接口創建代理對象。配置了 <jpa:repositories> 后,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,為繼承 Repository 或其子接口的接口創建代理對象,並將代理對象注冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該對象。
一、Repository子接口相關概述
先來看下面一張圖,大概能了解Repository接口的繼承體系

下面闡述下常用的Repository的子接口
- CrudRepository:繼承Repository接口,新增了一組CRUD相關的方法
- PagingAndSortingRepository:繼承CrudRepository接口,新增了一組分頁排序的相關方法
- JpaRepository:繼承PagingAndSortRepository接口,新增了一組JPA規范的方法
有個不屬於Repository繼承體系的接口,也比較常用,它就是
JpaSpecificationExecutor
- JpaSpecificationExecutor:不屬於Repository繼承體系,有一組JPA Criteria查詢相關方法
二、CrudRepository接口介紹
首先我們看下這個接口的代碼,代碼如下,它定義好了一組CRUD的方法,共11個。
@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);//批量保存
T findOne(ID id); //根據id查詢一個對象
boolean exists(ID id); //判斷對象是否存在
Iterable<T> findAll(); //查詢所有的對象
Iterable<T> findAll(Iterable<ID> ids);//根據id列表查詢所有的對象
long count(); //計算對象的總個數
void delete(ID id); //根據id刪除
void delete(T entity); //刪除對象
void delete(Iterable<? extends T> entities);//批量刪除
void deleteAll(); //刪除所有
}
14
1
2
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
3
<S extends T> S save(S entity); //保存
4
<S extends T> Iterable<S> save(Iterable<S> entities);//批量保存
5
T findOne(ID id); //根據id查詢一個對象
6
boolean exists(ID id); //判斷對象是否存在
7
Iterable<T> findAll(); //查詢所有的對象
8
Iterable<T> findAll(Iterable<ID> ids);//根據id列表查詢所有的對象
9
long count(); //計算對象的總個數
10
void delete(ID id); //根據id刪除
11
void delete(T entity); //刪除對象
12
void delete(Iterable<? extends T> entities);//批量刪除
13
void deleteAll(); //刪除所有
14
}
下面還是通過一個案例來介紹下這個接口里面方法的使用,項目的搭建就不介紹了,還是使用上一篇筆記搭建好的項目。
- 為了和上一篇筆記的代碼區分開,這里新建一個實體類User。這個實體類的屬性如下圖

- Dao層接口定義如下,直接繼承CrudRepository接口即可

-
測試類代碼如下,首先我測試了批量保存方法,向數據庫插入了26條數據。后面又測試了保存方法,發現這個保存方法可以起更新的作用的,類似於JPA中EntityManage的merge方法
/** 測試CrudRepository的批量save方法 */
@Test
public void testCrudRepositorySaveMethod(){
UserDao dao = ctx.getBean(UserDao.class);
List<User> list = new ArrayList<>();
for (int i = 'A'; i <= 'Z'; i++) {
User u = new User();
u.setName((char)i + "" + (char)i); // AA,BB這種
u.setGender(true);
u.setAge(i + 1);
u.setEmail(u.getName() + "@163.com");
list.add(u);
}
// 調用dao的批量保存
dao.save(list);
}
/** 測試CrudRepository的save */
@Test
public void testCrudRepositoryUpdate(){
UserDao dao = ctx.getBean(UserDao.class);
// 從數據庫查出來
User user = dao.findOne(1);
// 修改名字
user.setName("Aa");
dao.save(user); // 經過測試發現,有id時是更新,但不是絕對的;類似jpa的merge方法
}
27
1
/** 測試CrudRepository的批量save方法 */
2
3
public void testCrudRepositorySaveMethod(){
4
UserDao dao = ctx.getBean(UserDao.class);
5
List<User> list = new ArrayList<>();
6
for (int i = 'A'; i <= 'Z'; i++) {
7
User u = new User();
8
u.setName((char)i + "" + (char)i); // AA,BB這種
9
u.setGender(true);
10
u.setAge(i + 1);
11
u.setEmail(u.getName() + "@163.com");
12
list.add(u);
13
}
14
// 調用dao的批量保存
15
dao.save(list);
16
}
17
18
/** 測試CrudRepository的save */
19
20
public void testCrudRepositoryUpdate(){
21
UserDao dao = ctx.getBean(UserDao.class);
22
// 從數據庫查出來
23
User user = dao.findOne(1);
24
// 修改名字
25
user.setName("Aa");
26
dao.save(user); // 經過測試發現,有id時是更新,但不是絕對的;類似jpa的merge方法
27
}
三、PagingAndSortingRepository接口介紹
先通過觀察源碼可知,這個接口繼承
CrudRepository接口,它新增了一組分頁和排序的方法。源碼如下
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort); // 不帶分頁的排序
Page<T> findAll(Pageable pageable); // 帶分頁的排序
}
5
1
2
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
3
Iterable<T> findAll(Sort sort); // 不帶分頁的排序
4
Page<T> findAll(Pageable pageable); // 帶分頁的排序
5
}
下面通過一個案例講解這個接口中方法的使用
- 首先Dao層改一下,把繼承的接口改為PagingAndSortingRepository

- 單元測試的代碼如下,這里只測試了帶分頁和排序的那個方法,不帶分頁的那個方法就不測試了;這里的重點是參數怎么傳。而且springdata的分頁時,頁碼是從0開始的,這點要特別注意。
/** 測試PagingAndSortingRepositoryd的分頁且排序方法 */
@Test
public void testPagingAndSortingRepository() {
UserDao userDao = ctx.getBean(UserDao.class);
/* 需求:查詢第3頁的數據,每頁5條 */
int page = 3 - 1; //由於springdata默認的page是從0開始,所以減1
int size = 5;
//Pageable 接口通常使用的其 PageRequest 實現類. 其中封裝了需要分頁的信息
//排序相關的. Sort 封裝了排序的信息
//Order 是具體針對於某一個屬性進行升序還是降序.
Order order1 = new Order(Direction.DESC, "id");//按id降序
Order order2 = new Order(Direction.ASC, "age");//按age升序
Sort sort = new Sort(order1,order2);
Pageable pageable = new PageRequest(page, size,sort);
Page<User> result = userDao.findAll(pageable);
System.out.println("總記錄數: " + result.getTotalElements());
System.out.println("當前第幾頁: " + (result.getNumber() + 1));
System.out.println("總頁數: " + result.getTotalPages());
System.out.println("當前頁面的 List: " + result.getContent());
System.out.println("當前頁面的記錄數: " + result.getNumberOfElements());
System.out.println("當前的user對象的結果如下:");
for (User user : result.getContent()) {
System.out.println(user.getId() + " == " + user.getAge());
}
}
27
1
/** 測試PagingAndSortingRepositoryd的分頁且排序方法 */
2
3
public void testPagingAndSortingRepository() {
4
UserDao userDao = ctx.getBean(UserDao.class);
5
/* 需求:查詢第3頁的數據,每頁5條 */
6
int page = 3 - 1; //由於springdata默認的page是從0開始,所以減1
7
int size = 5;
8
//Pageable 接口通常使用的其 PageRequest 實現類. 其中封裝了需要分頁的信息
9
//排序相關的. Sort 封裝了排序的信息
10
//Order 是具體針對於某一個屬性進行升序還是降序.
11
Order order1 = new Order(Direction.DESC, "id");//按id降序
12
Order order2 = new Order(Direction.ASC, "age");//按age升序
13
Sort sort = new Sort(order1,order2);
14
15
Pageable pageable = new PageRequest(page, size,sort);
16
Page<User> result = userDao.findAll(pageable);
17
18
System.out.println("總記錄數: " + result.getTotalElements());
19
System.out.println("當前第幾頁: " + (result.getNumber() + 1));
20
System.out.println("總頁數: " + result.getTotalPages());
21
System.out.println("當前頁面的 List: " + result.getContent());
22
System.out.println("當前頁面的記錄數: " + result.getNumberOfElements());
23
System.out.println("當前的user對象的結果如下:");
24
for (User user : result.getContent()) {
25
System.out.println(user.getId() + " == " + user.getAge());
26
}
27
}
- 運行測試方法,生成的sql和結果如下圖


四、JpaRepository接口介紹
這個接口繼承了
PagingAndSortingRepository接口,所以開發中一般都會繼承它,它功能多一點。源碼如下
@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll(); //查詢方法
List<T> findAll(Sort sort); //查詢方法,帶排序
List<T> findAll(Iterable<ID> ids); //查詢方法,參數為id集合
<S extends T> List<S> save(Iterable<S> entities); //批量保存
void flush(); //刷新
<S extends T> S saveAndFlush(S entity); //保存並刷新,類似merge方法
void deleteInBatch(Iterable<T> entities);
void deleteAllInBatch();
T getOne(ID id);
<S extends T> List<S> findAll(Example<S> example); //根據“example”查找,參考:http://www.cnblogs.com/rulian/p/6533109.html
<S extends T> List<S> findAll(Example<S> example, Sort sort); // 根據“example”查找並排序
}
16
1
2
public interface JpaRepository<T, ID extends Serializable>
3
extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
4
5
List<T> findAll(); //查詢方法
6
List<T> findAll(Sort sort); //查詢方法,帶排序
7
List<T> findAll(Iterable<ID> ids); //查詢方法,參數為id集合
8
<S extends T> List<S> save(Iterable<S> entities); //批量保存
9
void flush(); //刷新
10
<S extends T> S saveAndFlush(S entity); //保存並刷新,類似merge方法
11
void deleteInBatch(Iterable<T> entities);
12
void deleteAllInBatch();
13
T getOne(ID id);
14
<S extends T> List<S> findAll(Example<S> example); //根據“example”查找,參考:http://www.cnblogs.com/rulian/p/6533109.html
15
<S extends T> List<S> findAll(Example<S> example, Sort sort); // 根據“example”查找並排序
16
}
下面還是通過一個案例來實踐一下這個接口的部分方法。
- dao層,把繼承的接口修改一下就ok,見下圖

- 單元測試代碼如下
/** 測試JpaRepository的SaveAndFlush */
@Test
public void testJpaRepositorySaveAndFlush() {
UserDao userDao = ctx.getBean(UserDao.class);
User user = new User();
user.setId(30); // id為30的話,不在數據庫中。如果在數據庫中,下面則是更新
user.setAge(27);
user.setName("testSaveAndFlush");
User user2 = userDao.saveAndFlush(user);
System.out.println("user==user2:" + (user == user2));
System.out.println("user.id=" + user.getId());
System.out.println("user2.id=" + user2.getId());
}
15
1
/** 測試JpaRepository的SaveAndFlush */
2
3
public void testJpaRepositorySaveAndFlush() {
4
UserDao userDao = ctx.getBean(UserDao.class);
5
User user = new User();
6
user.setId(30); // id為30的話,不在數據庫中。如果在數據庫中,下面則是更新
7
user.setAge(27);
8
user.setName("testSaveAndFlush");
9
10
User user2 = userDao.saveAndFlush(user);
11
System.out.println("user==user2:" + (user == user2));
12
System.out.println("user.id=" + user.getId());
13
System.out.println("user2.id=" + user2.getId());
14
15
}
- 運行結果得好好說一下。運行該方法,發現id為30的記錄不在數據庫中,則新增一條記錄,把屬性值copy過去,保存到數據庫。截圖如下所示,從sql語句可以看出是先查詢然后新增

- 當我們把測試代碼中按下圖修改下,再運行。則發現id在數據庫中存在,就更新。測試代碼及結果見下圖


總結下
saveAndFlush方法:
- 功能和jpa的EntityManage的merge方法是一致的,如果對象的id在數據庫存在,則是更新;如果不存在則是保存
- 返回的對象和原來的對象之所以不相等時因為返回的是持久化對象的引用。也就是返回的是一級緩存中對象的引用。
- 欲實現user等於user2的話,則可以這樣玩:
- 在service層定義一方法,開始事務;
- 方法第一步從數據庫查詢出id為27的user實體,然后修改一個屬性,這個user實體此時是在一級緩存中的。
- 方法的第二步是saveAndFlush這個user實體,返回一個user2實體,這個user2實體就是一級緩存中的那個user實體。
- 比較user和user2的地址,你會發現結果是true;但是我們一般也不會去刻意比較這2個對象。之所以在這個解釋這個返回值,可能也是由於筆者有強迫症。
五、JpaSpecificationExecutor接口介紹
這個接口不屬於
Repository體系,定義了一組
JPA Criteria查詢的方法,其源碼如下所示。
public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable); //條件查詢,且支持分頁
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> spec);
}
x
1
public interface JpaSpecificationExecutor<T> {
2
3
T findOne(Specification<T> spec);
4
List<T> findAll(Specification<T> spec);
5
Page<T> findAll(Specification<T> spec, Pageable pageable); //條件查詢,且支持分頁
6
List<T> findAll(Specification<T> spec, Sort sort);
7
long count(Specification<T> spec);
8
}
下面還是通過一個案例來實踐一下這個接口中的部分方法
- dao層做了小小的改動,繼承JpaRepository的同時也繼承JpaSpecificationExecutor,具體見下圖

- 測試代碼如下,這個測試的是 Page<T> findAll(Specification<T> spec, Pageable pageable) 這個方法
/**
* 目標: 實現帶查詢條件的分頁. id > 3 的條件
* 調用 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> spec, Pageable pageable);
* Specification: 封裝了 JPA Criteria 查詢的查詢條件
* Pageable: 封裝了請求分頁的信息: 例如 pageNo, pageSize, Sort
*/
@Test
public void testJapSpecificationExecutor() {
// 目標:查詢id>3 的第3頁的數據,頁大小為5
UserDao userDao = ctx.getBean(UserDao.class);
Pageable pageable = new PageRequest(3 - 1, 5);
//通常使用 Specification 的匿名內部類
Specification<User> spec = new Specification<User>() {
/**
* @param root: 代表查詢的實體類.
* @param query: 可以從中可到 Root 對象, 即告知 JPA Criteria 查詢要查詢哪一個實體類. 還可以
* 來添加查詢條件, 還可以結合 EntityManager 對象得到最終查詢的 TypedQuery 對象.
* @param cb: CriteriaBuilder 對象. 用於創建 Criteria 相關對象的工廠. 當然可以從中獲取到 Predicate 對象
* @return: Predicate 類型, 代表一個查詢條件.
*/
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// 一般用root和cb就ok了
return cb.gt(root.get("id"), 3);
// 多條件查詢的案例
/*List<Predicate> predicates = new ArrayList<>();
Predicate p1 = cb.notEqual(root.get("id"), 15);
Predicate p2 = cb.like(root.get("email"),"%163.com");
predicates.add(p1);
predicates.add(p2);
// 即使predicates集合里面沒元素,也能查詢,就變成了查全部
return cb.and(predicates.toArray(new Predicate[predicates.size()]));*/
}
};
Page<User> list = userDao.findAll(spec, pageable);
for (User user : list) {
System.out.println(user);
}
}
1
/**
2
* 目標: 實現帶查詢條件的分頁. id > 3 的條件
3
* 調用 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> spec, Pageable pageable);
4
* Specification: 封裝了 JPA Criteria 查詢的查詢條件
5
* Pageable: 封裝了請求分頁的信息: 例如 pageNo, pageSize, Sort
6
*/
7
8
public void testJapSpecificationExecutor() {
9
// 目標:查詢id>3 的第3頁的數據,頁大小為5
10
UserDao userDao = ctx.getBean(UserDao.class);
11
Pageable pageable = new PageRequest(3 - 1, 5);
12
//通常使用 Specification 的匿名內部類
13
Specification<User> spec = new Specification<User>() {
14
/**
15
* @param root: 代表查詢的實體類.
16
* @param query: 可以從中可到 Root 對象, 即告知 JPA Criteria 查詢要查詢哪一個實體類. 還可以
17
* 來添加查詢條件, 還可以結合 EntityManager 對象得到最終查詢的 TypedQuery 對象.
18
* @param cb: CriteriaBuilder 對象. 用於創建 Criteria 相關對象的工廠. 當然可以從中獲取到 Predicate 對象
19
* @return: Predicate 類型, 代表一個查詢條件.
20
*/
21
22
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
23
// 一般用root和cb就ok了
24
return cb.gt(root.get("id"), 3);
25
// 多條件查詢的案例
26
/*List<Predicate> predicates = new ArrayList<>();
27
Predicate p1 = cb.notEqual(root.get("id"), 15);
28
Predicate p2 = cb.like(root.get("email"),"%163.com");
29
predicates.add(p1);
30
predicates.add(p2);
31
// 即使predicates集合里面沒元素,也能查詢,就變成了查全部
32
return cb.and(predicates.toArray(new Predicate[predicates.size()]));*/
33
}
34
};
35
36
Page<User> list = userDao.findAll(spec, pageable);
37
for (User user : list) {
38
System.out.println(user);
39
}
40
}
- 運行后生成的sql語句和結果如下圖

通過
JpaSpecificationExecutor接口中的方法,就可以實現帶分頁的條件查詢了。彌補了JpaRepository中的分頁方法中不能帶條件查詢的缺點。
六、本文小結
寫完本文,感覺自己寫作水平真的是有待提高,很多東西沒描述清楚。本文主要概述了
Repository各個子接口有什么功能。通過了解這些子接口的功能,在開發dao層時就可以根據你所需要的功能繼承有相應功能的
Repository的子接口。省去了自己去定義方法這一步,提高了開發效率。本文還介紹了一個不是
Repository繼承體系的接口
JpaSpecificationExecutor,該接口彌補了分頁查詢時不能條件查詢的缺點。
一般在開發中,dao層的接口都是直接繼承JpaRepository這個接口,這個接口已經有了開發中常用的功能。
下一篇spring-data-jpa的筆記應該是講怎么自定義dao層方法的實現,但由於時間關系,暫時不往下寫了。待我在工作中實際用到了那部分知識時再撰寫該筆記,
未完待續。。。