Spring-data-jpa 學習筆記(二)

        經過上一篇筆記的,咱們掌握了SpringData的相關概念及簡單的用法。但上一篇筆記主要講的是Dao層接口直接繼承Repository接口,而後再本身定義方法。主要闡述了自定義方法時的一些規則及SpringData是如何來解析這些方法的。實際上,一些經常使用的方法SpringData已經幫咱們定義好了,咱們只須要定義Dao層接口時繼承Repository的有相關功能子接口就ok了。本文主要講的是Repository各個子接口有什麼功能,瞭解了子接口的功能後,後續開發dao層就方便了。
        這裏再強調一下使用SpringData開發Dao層的步驟,這個步驟很關鍵。下面這段話是從上一篇筆記中copy過來的

Spring Data JPA 進行持久層(即Dao)開發通常分三個步驟:html

    • 聲明持久層的接口,該接口繼承 Repository(或Repository的子接口,其中定義了一些經常使用的增刪改查,以及分頁相關的方法)。
    • 在接口中聲明須要的業務方法。Spring Data 將根據給定的策略生成實現代碼。
    • 在 Spring 配置文件中增長一行聲明,讓 Spring 爲聲明的接口建立代理對象。配置了 <jpa:repositories> 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,爲繼承 Repository 或其子接口的接口建立代理對象,並將代理對象註冊爲 Spring Bean,業務層即可以經過 Spring 自動封裝的特性來直接使用該對象。

1、Repository子接口相關概述

        先來看下面一張圖,大概能瞭解Repository接口的繼承體系
         
        下面闡述下經常使用的Repository的子接口
    • CrudRepository:繼承Repository接口,新增了一組CRUD相關的方法
    • PagingAndSortingRepository:繼承CrudRepository接口,新增了一組分頁排序的相關方法
    • JpaRepository:繼承PagingAndSortRepository接口,新增了一組JPA規範的方法

        有個不屬於Repository繼承體系的接口,也比較經常使用,它就是 JpaSpecificationExecutor
    • JpaSpecificationExecutor:不屬於Repository繼承體系,有一組JPA Criteria查詢相關方法

2、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();                    //刪除全部
}
        
        下面仍是經過一個案例來介紹下這個接口裏面方法的使用,項目的搭建就不介紹了,仍是使用上一篇筆記搭建好的項目。
    • 爲了和上一篇筆記的代碼區分開,這裏新建一個實體類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方法
}

3、PagingAndSortingRepository接口介紹

        先經過觀察源碼可知,這個接口繼承 CrudRepository接口,它新增了一組分頁和排序的方法。源碼以下

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
	Iterable<T> findAll(Sort sort);        // 不帶分頁的排序
	Page<T> findAll(Pageable pageable);    // 帶分頁的排序
}
       
         下面經過一個案例講解這個接口中方法的使用
    • 首先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());
	}
}

    • 運行測試方法,生成的sql和結果以下圖
                              

4、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」查找並排序
}
        
        下面仍是經過一個案例來實踐一下這個接口的部分方法。
    • 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());

}
    • 運行結果得好好說一下。運行該方法,發現id爲30的記錄不在數據庫中,則新增一條記錄,把屬性值copy過去,保存到數據庫。截圖以下所示,從sql語句能夠看出是先查詢而後新增
                             

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

    總結下 saveAndFlush方法:
    • 功能和jpa的EntityManagemerge方法是一致的,若是對象的id在數據庫存在,則是更新;若是不存在則是保存
    • 返回的對象和原來的對象之因此不相等時由於返回的是持久化對象的引用。也就是返回的是一級緩存中對象的引用。
    • 欲實現user等於user2的話,則能夠這樣玩:
      • 在service層定義一方法,開始事務;
      • 方法第一步從數據庫查詢出id爲27的user實體,而後修改一個屬性,這個user實體此時是在一級緩存中的。
      • 方法的第二步是saveAndFlush這個user實體,返回一個user2實體,這個user2實體就是一級緩存中的那個user實體。
      • 比較user和user2的地址,你會發現結果是true;可是咱們通常也不會去刻意比較這2個對象。之因此在這個解釋這個返回值,可能也是因爲筆者有強迫症。

5、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);
}
       
        下面仍是經過一個案例來實踐一下這個接口中的部分方法
    • 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);
	}
}

    • 運行後生成的sql語句和結果以下圖
                        

   經過 JpaSpecificationExecutor接口中的方法,就能夠實現帶分頁的條件查詢了。彌補了JpaRepository中的分頁方法中不能帶條件查詢的缺點。

6、本文小結

        寫完本文,感受本身寫做水平真的是有待提升,不少東西沒描述清楚。本文主要概述了 Repository各個子接口有什麼功能。經過了解這些子接口的功能,在開發dao層時就能夠根據你所須要的功能繼承有相應功能的 Repository的子接口。省去了本身去定義方法這一步,提升了開發效率。本文還介紹了一個不是 Repository繼承體系的接口 JpaSpecificationExecutor,該接口彌補了分頁查詢時不能條件查詢的缺點。
        通常在開發中,dao層的接口都是直接繼承JpaRepository這個接口,這個接口已經有了開發中經常使用的功能。
 

        下一篇spring-data-jpa的筆記應該是講怎麼自定義dao層方法的實現,但因爲時間關係,暫時不往下寫了。待我在工做中實際用到了那部分知識時再撰寫該筆記,  未完待續。。。
相關文章
相關標籤/搜索