JPA(7) spring-data-jpa

對於不是使用spring管理的項目,咱們就本身建立對象使用:大概的思路就是①建立dao接口②實現該接口,而且編寫邏輯:java

Dao:web

public interface StudentDao {
public List<Student> findAll();
}

由於是測試,因此咱們就只定義了一個查詢全部記錄的方法,下面是實現:spring

public class StudentDaoImpl implements StudentDao{
@Override
@SuppressWarnings("unchecked")
public List<Student> findAll() {
JPAConnection jc=new JPAConnection("jpa-1");
EntityManager em=jc.getEntityManager();
String sql="select s from Student s";
Query query=em.createQuery(sql);
List<Student> students=query.getResultList();
if(students==null){
students=Lists.newArrayList();
}
jc.destory();
return students;

} 
}

,由於建立EntityManager會重複不少的代碼:因此我就將這些東西給抽出來了sql

public class JPAConnection {

private EntityManagerFactory entityManagerFactory;

private EntityManager entityManager;

private EntityTransaction entityTransaction;

public JPAConnection(){
}

public JPAConnection(String unitName) {

this.entityManagerFactory=Persistence.createEntityManagerFactory(unitName);

this.entityManager=entityManagerFactory.createEntityManager();

this.entityTransaction=entityManager.getTransaction();

entityTransaction.begin();
}
/**
Getter And Setter
**/
public void destory(){

if(this.entityTransaction.isActive()){

this.entityTransaction.commit();
}

if(this.entityManager.isOpen()){
this.entityManager.close();
}
if(this.entityManagerFactory.isOpen()){
this.entityManagerFactory.close();
}
}
}

上面雖然是簡單的實現了查詢的功能,可是對於真正實際的項目仍是遠遠不夠的,對於真是項目中,還要考慮事務等等,這裏的代碼只是簡單的給個jpa實現的思路。數組

Spring管理的項目:框架

對於上面的代碼,以爲最多餘的就是建立EntityManager這部分了,因此固然會使用spring的特性減小這些建立的代碼了,同時操做事務也變得很簡單了,只要添加一些註解就能夠了:dom

下面是spring的配置文件:ide

<context:component-scan base-package="com.hotusm.common"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManager"/>
</bean>
<bean id="entityManager" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"/>

以後代碼就變成這樣了:post

@Repository("userDao")

public class UserDaoImpl implements UserDao{
@PersistenceContext
private EntityManager em;
@Transactional
@Override
@SuppressWarnings("unchecked")
public List<User> findAll() {

String sql="select o from User o";

Query query=em.createQuery(sql);

List<User> users=query.getResultList();

if(users==null){
users=Lists.newArrayList();
}
return users;
}
}

是否是超級爽快?,若是是web項目,在使用的時候,在service中直接注入進去就能夠直接使用,同時使用註解@Transactional便可進行事務操做了。測試

下面總結一下使用 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> 來過濾掉一些不但願被掃描到的接口。具體的使用方法見 Spring參考文檔。
應該繼承哪一個接口?
前面提到,持久層接口繼承 Repository 並非惟一選擇。Repository 接口是 Spring Data 的一個核心接口,它不提供任何方法,開發者須要在本身定義的接口中聲明須要的方法。與繼承 Repository 等價的一種方式,就是在持久層接口上使用 @RepositoryDefinition 註解,併爲其指定 domainClass 和 idClass 屬性。以下兩種方式是徹底等價的:
清單 15. 兩種等價的繼承接口方式示例

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

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

若是持久層接口較多,且每個接口都須要聲明類似的增刪改查方法,直接繼承 Repository 就顯得有些囉嗦,這時能夠繼承 CrudRepository,它會自動爲域對象建立增刪改查方法,供業務層直接使用。開發者只是多寫了 "Crud" 四個字母,即刻便爲域對象提供了開箱即用的十個增刪改查方法。

可是,使用 CrudRepository 也有反作用,它可能暴露了你不但願暴露給業務層的方法。好比某些接口你只但願提供增長的操做而不但願提供刪除的方法。針對這種狀況,開發者只能退回到 Repository 接口,而後到 CrudRepository 中把但願保留的方法聲明覆制到自定義的接口中便可。

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

JpaRepository 是繼承自 PagingAndSortingRepository 的針對 JPA 技術提供的接口,它在父接口的基礎上,提供了其餘一些方法,好比 flush(),saveAndFlush(),deleteInBatch() 等。若是有這樣的需求,則能夠繼承該接口。


上述四個接口,開發者到底該如何選擇?其實依據很簡單,根據具體的業務需求,選擇其中之一。筆者建議在一般狀況下優先選擇 Repository 接口。由於 Repository 接口已經能知足平常需求,其餘接口能作到的在 Repository 中也能作到,彼此之間並不存在功能強弱的問題。只是 Repository 須要顯示聲明須要的方法,而其餘則可能已經提供了相關的方法,不須要再顯式聲明,但若是對 Spring Data JPA 不熟悉,別人在檢視代碼或者接手相關代碼時會有疑惑,他們不明白爲何明明在持久層接口中聲明瞭三個方法,而在業務層使用該接口時,卻發現有七八個方法可用,從這個角度而言,應該優先考慮使用 Repository 接口。
前面提到,Spring Data JPA 在後臺爲持久層接口建立代理對象時,會解析方法名字,並實現相應的功能。除了經過方法名字之外,它還能夠經過以下兩種方式指定查詢語句:
Spring Data JPA 能夠訪問 JPA 命名查詢語句。開發者只須要在定義命名查詢語句時,爲其指定一個符合給定格式的名字,Spring Data JPA 便會在建立代理對象時,使用該命名查詢語句來實現其功能。
開發者還能夠直接在聲明的方法上面使用 @Query 註解,並提供一個查詢語句做爲參數,Spring Data JPA 在建立代理對象時,便以提供的查詢語句來實現其功能。
下面咱們分別講述三種建立查詢的方式。
經過解析方法名建立查詢
  經過前面的例子,讀者基本上對解析方法名建立查詢的方式有了一個大體的瞭解,這也是 Spring Data JPA 吸引開發者的一個很重要的因素。該功能其實並不是 Spring Data JPA 獨創,而是源自一個開源的 JPA 框架 Hades,該框架的做者 Oliver Gierke 自己又是 Spring Data JPA 項目的 Leader,因此把 Hades 的優點引入到 Spring Data JPA 也就是瓜熟蒂落的了。
  框架在進行方法名解析時,會先把方法名多餘的前綴截取掉,好比 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" 的值進行查詢。
可能會存在一種特殊狀況,好比 AccountInfo 包含一個 user 的屬性,也有一個 userAddress 屬性,此時會存在混淆。讀者能夠明確在屬性之間加上 "_" 以顯式表達意圖,好比 "findByUser_AddressZip()" 或者 "findByUserAddress_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 類型,也能夠是數組或者不定長參數;
使用 @Query 建立查詢
@Query 註解的使用很是簡單,只需在聲明的方法上面標註該註解,同時提供一個 JP QL 查詢語句便可,以下所示:
清單 16. 使用 @Query 提供自定義查詢語句示例

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 中的命名參數對應,示例以下:
清單 17. @Query 支持命名參數示例

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 來將該操做標識爲修改查詢,這樣框架最終會生成一個更新的操做,而非查詢。以下所示:
清單 18. 使用 @Modifying 將查詢標識爲修改查詢

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

 


經過調用 JPA 命名查詢語句建立查詢
命名查詢是 JPA 提供的一種將查詢語句從方法體中獨立出來,以供多個方法共用的功能。Spring Data JPA 對命名查詢也提供了很好的支持。用戶只須要按照 JPA 規範在 orm.xml 文件或者在代碼中使用 @NamedQuery(或 @NamedNativeQuery)定義好查詢語句,惟一要作的就是爲該語句命名時,須要知足」DomainClass.methodName()」的命名規則。假設定義了以下接口:
清單 19. 使用 JPA 命名查詢時,聲明接口及方法時不須要什麼特殊處理

public interface UserDao extends Repository<AccountInfo, Long> {

...... 

public List<AccountInfo> findTop5(); 
}

 

若是但願爲 findTop5() 建立命名查詢,並與之關聯,咱們只須要在適當的位置定義命名查詢語句,並將其命名爲 "AccountInfo.findTop5",框架在建立代理類的過程當中,解析到該方法時,優先查找名爲 "AccountInfo.findTop5" 的命名查詢定義,若是沒有找到,則嘗試解析方法名,根據方法名字建立查詢。
建立查詢的順序
Spring Data JPA 在爲接口建立代理對象時,若是發現同時存在多種上述狀況可用,它該優先採用哪一種策略呢?爲此,<jpa:repositories> 提供了 query-lookup-strategy 屬性,用以指定查找的順序。它有以下三個取值:
create --- 經過解析方法名字來建立查詢。即便有符合的命名查詢,或者方法經過 @Query 指定的查詢語句,都將會被忽略。
create-if-not-found --- 若是方法經過 @Query 指定了查詢語句,則使用該語句實現查詢;若是沒有,則查找是否認義了符合條件的命名查詢,若是找到,則使用該命名查詢;若是二者都沒有找到,則經過解析方法名字來建立查詢。這是 query-lookup-strategy 屬性的默認值。
use-declared-query --- 若是方法經過 @Query 指定了查詢語句,則使用該語句實現查詢;若是沒有,則查找是否認義了符合條件的命名查詢,若是找到,則使用該命名查詢;若是二者都沒有找到,則拋出異常。
Spring Data JPA 對事務的支持
默認狀況下,Spring Data JPA 實現的方法都是使用事務的。針對查詢類型的方法,其等價於 @Transactional(readOnly=true);增刪改類型的方法,等價於 @Transactional。能夠看出,除了將查詢的方法設爲只讀事務外,其餘事務屬性均採用默認值。
若是用戶以爲有必要,能夠在接口方法上使用 @Transactional 顯式指定事務屬性,該值覆蓋 Spring Data JPA 提供的默認值。同時,開發者也能夠在業務層方法上使用 @Transactional 指定事務屬性,這主要針對一個業務層方法屢次調用持久層方法的狀況。持久層的事務會根據設置的事務傳播行爲來決定是掛起業務層事務仍是加入業務層的事務。具體 @Transactional 的使用,請參考 Spring的參考文檔。
爲接口中的部分方法提供自定義實現
有些時候,開發者可能須要在某些方法中作一些特殊的處理,此時自動生成的代理對象不能徹底知足要求。爲了享受 Spring Data JPA 帶給咱們的便利,同時又可以爲部分方法提供自定義實現,咱們能夠採用以下的方法:
將須要開發者手動實現的方法從持久層接口(假設爲 AccountDao )中抽取出來,獨立成一個新的接口(假設爲 AccountDaoPlus ),並讓 AccountDao 繼承 AccountDaoPlus;
爲 AccountDaoPlus 提供自定義實現(假設爲 AccountDaoPlusImpl );
將 AccountDaoPlusImpl 配置爲 Spring Bean;
在 <jpa:repositories> 中按清單 19 的方式進行配置。
清單 20. 指定自定義實現類

<jpa:repositories base-package="footmark.springdata.jpa.dao"> 
<jpa:repository id="accountDao" repository-impl-ref=" accountDaoPlus " /> 
</jpa:repositories>

<bean id="accountDaoPlus" class="......."/>

 

此外,<jpa:repositories > 提供了一個 repository-impl-postfix 屬性,用以指定實現類的後綴。假設作了以下配置:清單 21. 設置自動查找時默認的自定義實現類命名規則 <jpa:repositories base-package="footmark.springdata.jpa.dao" repository-impl-postfix="Impl"/>則在框架掃描到 AccountDao 接口時,它將嘗試在相同的包目錄下查找 AccountDaoImpl.java,若是找到,便將其中的實現方法做爲最終生成的代理類中相應方法的實現。

相關文章
相關標籤/搜索