瞭解 Spring Data JPA

前言

自 JPA 伴隨 Java EE 5 發佈以來,受到了各大廠商及開源社區的追捧,各種商用的和開源的 JPA 框架如雨後春筍般出現,爲開發者提供了豐富的選擇。它一改之前 EJB 2.x 中實體 Bean 笨重且難以使用的形象,充分吸收了在開源社區已經相對成熟的 ORM 思想。另外,它並不依賴於 EJB 容器,可以作爲一個獨立的持久層技術而存在。目前比較成熟的 JPA 框架主要包括 Jboss 的 Hibernate EntityManager、Oracle 捐獻給 Eclipse 社區的 EclipseLink、Apache 的 OpenJPA 等。

Java持久化規範,是從EJB2.x以前的實體Bean(Entity bean)分離出來的,EJB3以後不再有實體bean,而是將實體bean放到JPA中實現。JPA是sun提出的一個對象持久化規範,各JavaEE應用服務器自主選擇具體實現,JPA的設計者是Hibernate框架的作者,因此Hibernate作爲Jboss服務器中JPA的默認實現,Oracle的Weblogic使用EclipseLink(以前叫TopLink)作爲默認的JPA實現,IBM的Websphere和Sun的Glassfish默認使用OpenJPA(Apache的一個開源項目)作爲其默認的JPA實現。
JPA的底層實現是一些流行的開源ORM(對象關係映射)框架,因此JPA其實也就是java實體對象和關係型數據庫建立起映射關係,通過面向對象編程的思想操作關係型數據庫的規範。

Spring 框架對 JPA 的支持

Spring 框架對 JPA 提供的支持主要體現在如下幾個方面:

  • 首先,它使得 JPA 配置變得更加靈活。JPA 規範要求,配置文件必須命名爲 persistence.xml,並存在於類路徑下的 META-INF 目錄中。該文件通常包含了初始化 JPA 引擎所需的全部信息。Spring 提供的 LocalContainerEntityManagerFactoryBean 提供了非常靈活的配置,persistence.xml 中的信息都可以在此以屬性注入的方式提供。

  • 其次,Spring 實現了部分在 EJB 容器環境下才具有的功能,比如對 @PersistenceContext、@PersistenceUnit 的容器注入支持。
  • 第三,也是最具意義的,Spring 將 EntityManager 的創建與銷燬、事務管理等代碼抽取出來,並由其統一管理,開發者不需要關心這些,業務方法中只剩下操作領域對象的代碼,事務管理和 EntityManager 創建、銷燬的代碼都不再需要開發者關心了。

Spring Data JPA 更簡潔

Spring Data JPA 框架,主要針對的就是 Spring 唯一沒有簡化到的業務邏輯代碼,至此,開發者連僅剩的實現持久層業務邏輯的工作都省了,唯一要做的,就只是聲明持久層的接口,其他都交給 Spring Data JPA 來幫你完成!

下面就來了解Spring Data JPA。

1.下載需要的包。

需要先 下載Spring Data JPA 的發佈包(需要同時下載 Spring Data Commons 和 Spring Data JPA 兩個發佈包,Commons 是 Spring Data 的公共基礎包),並把相關的依賴 JAR 文件加入到 CLASSPATH 中。

2.讓持久層接口 Dao(以UserDao)  繼承 Repository 接口

該接口使用了泛型,需要爲其提供兩個類型:第一個爲該接口處理的域對象類型,第二個爲該域對象的主鍵類型。 如下:

Spring Data JPA 風格的持久層接口:

public interface UserDao extends Repository<AccountInfo, Long> { 
    public AccountInfo save(AccountInfo accountInfo); 
 }

不需要UserDao的實現類,框架會爲我們完成業務邏輯。

3.在 Spring 配置文件中啓用掃描並自動創建代理的功能。

 <-- 需要在 <beans> 標籤中增加對 jpa 命名空間的引用 --> 
 <jpa:repositories base-package="footmark.springdata.jpa.dao"
 entity-manager-factory-ref="entityManagerFactory" 
 transaction-manager-ref="transactionManager"/> 

4.測試代碼。

複製代碼
 public interface UserDao extends Repository<AccountInfo, Long> { 

 public AccountInfo save(AccountInfo accountInfo); 

 // 你需要做的,僅僅是新增如下一行方法聲明
 public AccountInfo findByAccountId(Long accountId); 
 } 
複製代碼

5.總結

使用 Spring Data JPA 進行持久層開發大致需要的三個步驟:

1.聲明持久層的接口,該接口繼承 Repository,Repository 是一個標記型接口,它不包含任何方法,當然如果有需要,Spring Data 也提供了若干 Repository 子接口,其中定義了一些常用的增刪改查,以及分頁相關的方法。

2.在接口中聲明需要的業務方法。Spring Data 將根據給定的策略來爲其生成實現代碼。

3.在 Spring 配置文件中增加一行聲明,讓 Spring 爲聲明的接口創建代理對象。配置了 <jpa:repositories> 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,爲繼承 Repository 或其子接口的接口創建代理對象,並將代理對象註冊爲 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該對象。

此外,<jpa:repository> 還提供了一些屬性和子標籤,便於做更細粒度的控制。可以在 <jpa:repository> 內部使用 <context:include-filter>、<context:exclude-filter> 來過濾掉一些不希望被掃描到的接口。

接口繼承

持久層接口繼承 Repository 並不是唯一選擇。Repository 接口是 Spring Data 的一個核心接口,它不提供任何方法,開發者需要在自己定義的接口中聲明需要的方法。與繼承 Repository 等價的一種方式,就是在持久層接口上使用 @RepositoryDefinition 註解,併爲其指定 domainClass 和 idClass 屬性。如下兩種方式是完全等價的:

兩種等價的繼承接口方式示例:

public interface UserDao extends Repository<AccountInfo, Long> { …… } 

 @RepositoryDefinition(domainClass = AccountInfo.class, idClass = Long.class) 
 public interface UserDao { …… } 

1.如果持久層接口較多,且每一個接口都需要聲明相似的增刪改查方法,直接繼承 Repository 就顯得有些囉嗦,這時可以繼承 CrudRepository,它會自動爲域對象創建增刪改查方法,供業務層直接使用。開發者只是多寫了 “Crud” 四個字母,即刻便爲域對象提供了開箱即用的十個增刪改查方法。

2.使用 CrudRepository 也有副作用,它可能暴露了你不希望暴露給業務層的方法。比如某些接口你只希望提供增加的操作而不希望提供刪除的方法。針對這種情況,開發者只能退回到 Repository 接口,然後到 CrudRepository 中把希望保留的方法聲明覆制到自定義的接口中即可.

3.分頁查詢和排序是持久層常用的功能,Spring Data 爲此提供了 PagingAndSortingRepository 接口,它繼承自 CrudRepository 接口,在 CrudRepository 基礎上新增了兩個與分頁有關的方法。但是,我們很少會將自定義的持久層接口直接繼承自 PagingAndSortingRepository,而是在繼承 Repository 或 CrudRepository 的基礎上,在自己聲明的方法參數列表最後增加一個 Pageable 或 Sort 類型的參數,用於指定分頁或排序信息即可,這比直接使用 PagingAndSortingRepository 提供了更大的靈活性。

4.JpaRepository 是繼承自 PagingAndSortingRepository 的針對 JPA 技術提供的接口,它在父接口的基礎上,提供了其他一些方法,比如 flush(),saveAndFlush(),deleteInBatch() 等。如果有這樣的需求,則可以繼承該接口。

查詢方式

 1.通過解析方法名創建查詢

框架在進行方法名解析時,會先把方法名多餘的前綴截取掉,比如 find、findBy、read、readBy、get、getBy,然後對剩下部分進行解析。並且如果方法的最後一個參數是 Sort 或者 Pageable 類型,也會提取相關的信息,以便按規則進行排序或者分頁查詢。

在創建查詢時,我們通過在方法名中使用屬性名稱來表達,比如 findByUserAddressZip ()。框架在解析該方法時,首先剔除 findBy,然後對剩下的屬性進行解析,詳細規則如下(此處假設該方法針對的域對象爲 AccountInfo 類型):

  • 先判斷 userAddressZip (根據 POJO 規範,首字母變爲小寫,下同)是否爲 AccountInfo 的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;
  • 從右往左截取第一個大寫字母開頭的字符串(此處爲 Zip),然後檢查剩下的字符串是否爲 AccountInfo 的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左截取;最後假設 user 爲 AccountInfo 的一個屬性;
  • 接着處理剩下部分( AddressZip ),先判斷 user 所對應的類型是否有 addressZip 屬性,如果有,則表示該方法最終是根據 “AccountInfo.user.addressZip” 的取值進行查詢;否則繼續按照步驟 2 的規則從右往左截取,最終表示根據 “AccountInfo.user.address.zip” 的值進行查詢。

在查詢時,通常需要同時根據多個屬性進行查詢,且查詢的條件也格式各樣(大於某個值、在某個範圍等等),Spring Data JPA 爲此提供了一些表達條件查詢的關鍵字,大致如下:

  • And — 等價於 SQL 中的 and 關鍵字,比如 findByUsernameAndPassword(String user, Striang pwd);
  • Or — 等價於 SQL 中的 or 關鍵字,比如 findByUsernameOrAddress(String user, String addr);
  • Between — 等價於 SQL 中的 between 關鍵字,比如 findBySalaryBetween(int max, int min);
  • LessThan — 等價於 SQL 中的 “<”,比如 findBySalaryLessThan(int max);
  • GreaterThan — 等價於 SQL 中的”>”,比如 findBySalaryGreaterThan(int min);
  • IsNull — 等價於 SQL 中的 “is null”,比如 findByUsernameIsNull();
  • IsNotNull — 等價於 SQL 中的 “is not null”,比如 findByUsernameIsNotNull();
  • NotNull — 與 IsNotNull 等價;
  • Like — 等價於 SQL 中的 “like”,比如 findByUsernameLike(String user);
  • NotLike — 等價於 SQL 中的 “not like”,比如 findByUsernameNotLike(String user);
  • OrderBy — 等價於 SQL 中的 “order by”,比如 findByUsernameOrderBySalaryAsc(String user);
  • Not — 等價於 SQL 中的 “! =”,比如 findByUsernameNot(String user);
  • In — 等價於 SQL 中的 “in”,比如 findByUsernameIn(Collection<String> userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;
  • NotIn — 等價於 SQL 中的 “not in”,比如 findByUsernameNotIn(Collection<String> userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;

2.使用 @Query 創建查詢

@Query 註解的使用非常簡單,只需在聲明的方法上面標註該註解,同時提供一個 JP QL 查詢語句即可,如下所示:

複製代碼
 public interface UserDao extends Repository<AccountInfo, Long> { 

 @Query("select a from AccountInfo a where a.accountId = ?1") 
 public AccountInfo findByAccountId(Long accountId); 

    @Query("select a from AccountInfo a where a.balance > ?1") 
 public Page<AccountInfo> findByBalanceGreaterThan( 
 Integer balance,Pageable pageable); 
 } 
複製代碼

很多開發者在創建 JP QL 時喜歡使用命名參數來代替位置編號,@Query 也對此提供了支持。JP QL 語句中通過”: 變量”的格式來指定參數,同時在方法的參數前面使用 @Param 將方法參數與 JP QL 中的命名參數對應,示例如下:

複製代碼
public interface UserDao extends Repository<AccountInfo, Long> { 

 public AccountInfo save(AccountInfo accountInfo); 

 @Query("from AccountInfo a where a.accountId = :id") 
 public AccountInfo findByAccountId(@Param("id")Long accountId); 

   @Query("from AccountInfo a where a.balance > :balance") 
   public Page<AccountInfo> findByBalanceGreaterThan( 
 @Param("balance")Integer balance,Pageable pageable); 
 } 
複製代碼

此外,開發者也可以通過使用 @Query 來執行一個更新操作,爲此,我們需要在使用 @Query 的同時,用 @Modifying 來將該操作標識爲修改查詢,這樣框架最終會生成一個更新的操作,而非查詢。如下所示:

 @Modifying 
 @Query("update AccountInfo a set a.salary = ?1 where a.salary < ?2") 
 public int increaseSalary(int after, int before); 

3.通過調用 JPA 命名查詢語句創建查詢

命名查詢是 JPA 提供的一種將查詢語句從方法體中獨立出來,以供多個方法共用的功能。Spring Data JPA 對命名查詢也提供了很好的支持。用戶只需要按照 JPA 規範在 orm.xml 文件或者在代碼中使用 @NamedQuery(或 @NamedNativeQuery)定義好查詢語句,唯一要做的就是爲該語句命名時,需要滿足”DomainClass.methodName()”的命名規則。假設定義瞭如下接口:

public interface UserDao extends Repository<AccountInfo, Long> { 
 ...... 
 public List<AccountInfo> findTop5(); 
 } 

如果希望爲 findTop5() 創建命名查詢,並與之關聯,我們只需要在適當的位置定義命名查詢語句,並將其命名爲 “AccountInfo.findTop5”,框架在創建代理類的過程中,解析到該方法時,優先查找名爲 “AccountInfo.findTop5” 的命名查詢定義,如果沒有找到,則嘗試解析方法名,根據方法名字創建查詢。

Spring Data JPA 對事務的支持

默認情況下,Spring Data JPA 實現的方法都是使用事務的。針對查詢類型的方法,其等價於 @Transactional(readOnly=true);增刪改類型的方法,等價於 @Transactional。可以看出,除了將查詢的方法設爲只讀事務外,其他事務屬性均採用默認值。

如果用戶覺得有必要,可以在接口方法上使用 @Transactional 顯式指定事務屬性,該值覆蓋 Spring Data JPA 提供的默認值。同時,開發者也可以在業務層方法上使用 @Transactional 指定事務屬性,這主要針對一個業務層方法多次調用持久層方法的情況。持久層的事務會根據設置的事務傳播行爲來決定是掛起業務層事務還是加入業務層的事務。具體 @Transactional 的使用可以參考Spring的參考文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章