基於JPA的Repository使用詳解

Spring Data JPA

Spring Data是Spring提供的操作數據的框架,Spring Data JPA是Spring Data的一個模塊,通過Spring data 基於jpa標準操作數據的模塊。

Spring Data的核心能力,就是基於JPA操作數據,並且可以簡化操作持久層的代碼。

它使用一個叫作Repository的接口類為基礎,它被定義為訪問底層數據模型的超級接口。而對於某種具體的數據訪問操作,則在其子接口中定義。

Spring Data可以讓我們隻定義接口,隻要遵循spring data的規范,就無需寫實現類,不用寫sql語句直接查詢數據。

Repository

在這裡插入圖片描述

  • Repository

提供瞭findBy + 屬性 方法

  • CrudRepository

繼承瞭Repository 提供瞭對數據的增刪改查

  • PagingAndSortRepository

繼承瞭CrudRepository 提供瞭對數據的分頁和排序,缺點是隻能對所有的數據進行分頁或者排序,不能做條件判斷

  • JpaRepository: 繼承瞭PagingAndSortRepository

開發中經常使用的接口,主要繼承瞭PagingAndSortRepository,對返回值類型做瞭適配

  • JpaSpecificationExecutor

提供多條件查詢

CrudRepository

CrudRepository繼承Repository,添加瞭一組對數據的增刪改查的方法

在這裡插入圖片描述

PagingAndSortingRepository

PagingAndSortingRepository繼承CrudRepository,添加瞭一組分頁排序相關的方法

在這裡插入圖片描述

JpaRepository

JpaRepository繼承PagingAndSortingRepository,添加瞭一組JPA規范相關的方法。對繼承父接口中方法的返回值進行瞭適配,因為在父類接口中通常都返回迭代器,需要我們自己進行強制類型轉化。而在JpaRepository中,直接返回瞭List。

開發中最常用JpaRepository

在這裡插入圖片描述

JpaSpecificationExecutor

這個接口比較特殊,單獨存在,沒有繼承以上接口。主要提供瞭多條件查詢的支持,並且可以在查詢中添加分頁和排序。因為這個接口單獨存在,因此需要配合以上說的接口使用。

在這裡插入圖片描述

JpaRepository查詢功能

Jpa方法命名規則

JpaRepository支持接口規范方法名查詢。意思是如果在接口中定義的查詢方法符合它的命名規則,就可以不用寫實現,目前支持的關鍵字如下。

Keyword Sample JPQL snippet
And findByNameAndPwd where name= ? and pwd =?
Or findByNameOrSex where name= ? or sex=?
Is,Equals findById,findByIdEquals where id= ?
Between findByIdBetween where id between ? and ?
LessThan findByIdLessThan where id < ?
LessThanEquals findByIdLessThanEquals where id <= ?
GreaterThan findByIdGreaterThan where id > ?
GreaterThanEquals findByIdGreaterThanEquals where id > = ?
After findByIdAfter where id > ?
Before findByIdBefore where id < ?
IsNull findByNameIsNull where name is null
isNotNull,NotNull findByNameNotNull where name is not null
Like findByNameLike where name like ?
NotLike findByNameNotLike where name not like ?
StartingWith findByNameStartingWith where name like ‘?%’
EndingWith findByNameEndingWith where name like ‘%?’
Containing findByNameContaining where name like ‘%?%’
OrderBy findByIdOrderByXDesc where id=? order by x desc
Not findByNameNot where name <> ?
In findByIdIn(Collection<?> c) where id in (?)
NotIn findByIdNotIn(Collection<?> c) where id not in (?)
True findByAaaTue where aaa = true
False findByAaaFalse where aaa = false
IgnoreCase findByNameIgnoreCase where UPPER(name)=UPPER(?)
top findTop10 top 10/where ROWNUM <=10

使用方法

使用時自定義接口繼承JpaRepository,傳入泛型,第一個參數為要操作的實體類,第二個參數為該實體類的主鍵類型

public interface SpuRepository extends JpaRepository<Spu, Long> {
    Spu findOneById(Long id);
    Page<Spu> findByCategoryIdOrderByCreateTimeDesc(Long cid, Pageable pageable);
    Page<Spu> findByRootCategoryIdOrderByCreateTime(Long cid, Pageable pageable);
}

解析過程

1. Spring Data JPA框架在進行方法名解析時

會先把方法名多餘的前綴截取掉,比如find,findBy,read,readBy,get,getBy,然後對剩下的部分進行解析。

2. 假設創建如下查詢findByCategoryId()

框架在解析該方法時,首先剔除findBy,然後對剩下的屬性進行解析,假設查詢實體為Spu

(1) 先判斷categoryId(根據POJO 規范,首字母變為小寫)是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;

(2) 從右往左截取第一個大寫字母開頭的字符串此處為Id),然後檢查剩下的字符串是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;

如果沒有該屬性,則重復第二步,繼續從右往左截取;最後假設user為查詢實體的一個屬性;

(3) 接著處理剩下部分(CategoryId),先判斷用戶所對應的類型是否有categoryId屬性,如果有,則表示該方法最終是根據”Spu.categoryId”的取值進行查詢;

否則繼續按照步驟2的規則從右往左截取。

(4) 可能會存在一種特殊情況,比如Spu包含一個categoryId 的屬性,也有一個 rootCategoryId屬性,此時會存在混淆。可以明確在屬性之間加上”_” 以顯式表達意圖,比如 “findByRoot_CategoryId()”

3. 特殊參數

可以直接在方法的參數上加入分頁或排序的參數,比如:

Page<Spu> findByCategoryId(Long cid, Pageable pageable);
List<Spu> findByCategoryId(Long cid, Sort sort);

4. JPA的@NamedQueries

(1) 在實體類上使用@NamedQuery

@NamedQuery(name = "Spu.findByRootCategoryId",query = "select s from Spu s where s.rootCategoryId >= ?1")

(2) 在自己實現的DAO的Repository接口裡面定義一個同名的方法,示例如下:

public List<Spu> findByRootCategoryId(Long rootCategoryId);

(3) 然後Spring會先找是否有同名的NamedQuery,如果有,那麼就不會按照接口定義的方法來解析。

5. 使用@Query

在方法上標註@Query來指定本地查詢

參數nativeQuery默認為false,nativeQuery=false時,value參數寫的是JPQL,JPQL是用來操作model對象的

@Query(value="select s from Spu s where s.title like %?1" )
public List<Spu> findByTitle(String title);

nativeQuery=true時,value參數寫的是原生sql

@Query(value="select * from spu s where s.title like %?1",nativeQuery=true )
public List<Spu> findByTitle(String title);

6. 使用@Param命名化參數

@Query(value = "select s from Spu s where s.title in (:titles)")
List<Spu> findByTitle(@Param("titles") List<String> titles);

JPA自定義Repository方法

如果不使用SpringData的方法,想要自己實現,該怎麼辦呢?

定義一個接口:聲明要添加的, 並自實現的方法

提供該接口的實現類:類名需在要聲明的 Repository 後添加 Impl, 並實現方法

聲明 Repository 接口, 並繼承 1) 聲明的接口

註意:默認情況下, Spring Data 會在 base-package 中查找 “接口名Impl” 作為實現類. 也可以通過 repository-impl-postfix 聲明後綴.

這張圖是類與接口之間的關系

下面是具體的實現

包結構

類與接口之間的關系代碼

public interface PersonRepositoiry extends JpaRepository<Person, Integer> ,PersonDao{
public interface PersonDao { 
 void test(); 
}
 
@Repository
public class PersonRepositoiryImpl implements PersonDao{ 
 @PersistenceContext
 private EntityManager em;
 
 @Override
 public void test() {
  //隻是用來測試
  Person person = em.find(Person.class, 1);
  System.out.println(person);
 } 
}

測試代碼

@Test
 public void testCustomerRepositoryMethod() {
  personRepositoiry.test();
 }

經過實踐發現

XXXRepositoryImpl 與XXXRepository前面的名字必須相同,後面的也需要按照規則寫

若將XXXRepositoryImpl與XXXRepository接口放在同意包下,XXXRepositoryImpl不需要添加@Repository註解,但是當XXXRepositoryImpl與XXXRepository接口不在同一包下,需要在在XXXRepositoryImpl類上加@Repository註解進行修飾

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: