使用 Spring Data JPA 簡化 JPA 開發(fā)

簡介: 本文由淺入深地講述了使用 Spring Data JPA 需要關(guān)注的各個方面渔呵,為讀者了解和使用該框架提供
了指導(dǎo)聚请,可以作為 Spring Data JPA 的學(xué)習(xí)指南荠雕。

                         ## 從一個簡單的 JPA 示例開始 ##

本文主要講述 Spring Data JPA,但是為了不至于給 JPA 和 Spring 的初學(xué)者造成較大的學(xué)習(xí)曲線驶赏,我們首先從 JPA 開始炸卑,簡單介紹一個 JPA 示例;接著重構(gòu)該示例煤傍,并引入 Spring 框架盖文,這兩部分不會涉及過多的篇幅,如果希望能夠深入學(xué)習(xí) Spring 和 JPA蚯姆,可以根據(jù)本文最后提供的參考資料進(jìn)一步學(xué)習(xí)五续。

自 JPA 伴隨 Java EE 5 發(fā)布以來,受到了各大廠商及開源社區(qū)的追捧龄恋,各種商用的和開源的 JPA 框架如雨后春筍般出現(xiàn)疙驾,為開發(fā)者提供了豐富的選擇。它一改之前 EJB 2.x 中實體 Bean 笨重且難以使用的形
象郭毕,充分吸收了在開源社區(qū)已經(jīng)相對成熟的 ORM 思想它碎。另外,它并不依賴于 EJB 容器显押,可以作為一個獨
立的持久層技術(shù)而存在扳肛。目前比較成熟的 JPA 框架主要包括 Jboss 的 Hibernate EntityManager、Oracle 捐獻(xiàn)給 Eclipse 社區(qū)的 EclipseLink乘碑、Apache 的 OpenJPA 等挖息。

本文的示例代碼基于 Hibernate EntityManager 開發(fā),但是讀者幾乎不用修改任何代碼蝉仇,便可以非常容易地切換到其他 JPA 框架旋讹,因為代碼中使用到的都是 JPA 規(guī)范提供的接口 / 類,并沒有使用到框架本身的私有特性轿衔。示例主要涉及七個文件沉迹,但是很清晰:業(yè)務(wù)層包含一個接口和一個實現(xiàn);持久層包含一個接口害驹、一個實現(xiàn)鞭呕、一個實體類;另外加上一個 JPA 配置文件和一個測試類宛官。

相關(guān)類 / 接口代碼如下:

清單 1. 實體類 AccountInfo.java

@Entity
@Table(name = "t_accountinfo")
public class AccountInfo implements Serializable {
private Long accountId;
private Integer balance;
// 此處省略 getter 和 setter 方法葫松。
}

清單 2. 業(yè)務(wù)層接口 UserService.java

public interface UserService {
public AccountInfo createNewAccount(String user, String pwd, Integer init);
}

清單 3. 業(yè)務(wù)層的實現(xiàn)類 UserServiceImpl.java

public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
public AccountInfo createNewAccount(String user, String pwd, Integer init){
// 封裝域?qū)ο?AccountInfo accountInfo = new AccountInfo();
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
userInfo.setPassword(password);
accountInfo.setBalance(initBalance);
accountInfo.setUserInfo(userInfo);
// 調(diào)用持久層瓦糕,完成數(shù)據(jù)的保存
return userDao.save(accountInfo);
}
}

清單 4. 持久層接口

public interface UserDao {
public AccountInfo save(AccountInfo accountInfo);
}

清單 5. 持久層的實現(xiàn)類

public class UserDaoImpl implements UserDao {
public AccountInfo save(AccountInfo accountInfo) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("SimplePU");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(accountInfo);
em.getTransaction().commit();
emf.close();
return accountInfo;
}
}

清單 6. JPA 標(biāo)準(zhǔn)配置文件 persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="SimplePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>footmark.springdata.jpa.domain.UserInfo</class>
<class>footmark.springdata.jpa.domain.AccountInfo</class>
<properties>
<property name="hibernate.connection.driver_class"
value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.url"
value="jdbc:mysql://10.40.74.197:3306/zhangjp"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="root"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.MySQL5Dialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="false"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>

清單 7. 本文使用如下的 main 方法進(jìn)行開發(fā)者測試

public class SimpleSpringJpaDemo {
public static void main(String[] args) {
new UserServiceImpl().createNewAccount("ZhangJianPing", "123456", 1);
}
}

接下來我們引入 Spring,以展示 Spring 框架對 JPA 的支持腋么。業(yè)務(wù)層接口 UserService 保持不變咕娄,UserServiceImpl 中增加了三個注解,以讓 Spring 完成依賴注入珊擂,因此不再需要使用 new 操作符創(chuàng)建UserDaoImpl 對象了圣勒。同時我們還使用了 Spring 的聲明式事務(wù):

清單 8. 配置為 Spring Bean 的業(yè)務(wù)層實現(xiàn)

@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Transactional
public AccountInfo createNewAccount(
String name, String pwd, Integer init) { …… }
}

對于持久層,UserDao 接口也不需要修改摧扇,只需修改 UserDaoImpl 實現(xiàn)圣贸,修改后的代碼如下:

清單 9. 配置為 Spring Bean 的持久層實現(xiàn)

@Repository("userDao")
public class UserDaoImpl implements UserDao {
@PersistenceContext
private EntityManager em;
@Transactional
public Long save(AccountInfo accountInfo) {
em.persist(accountInfo);
return accountInfo.getAccountId();
}
}

清單 10. Spring 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans...>
<context:component-scan base-package="footmark.springdata.jpa"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
</bean>
</beans>

清單 11. 改造后的基于 Spring 的開發(fā)者測試代碼

public class SimpleSpringJpaDemo{
public static void main(String[] args){
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("spring-demo-cfg.xml");
UserDao userDao = ctx.getBean("userDao", UserDao.class);
userDao.createNewAccount("ZhangJianPing", "123456", 1);
}
}

通過對比重構(gòu)前后的代碼,可以發(fā)現(xiàn) Spring 對 JPA 的簡化已經(jīng)非常出色了扛稽,我們可以大致總結(jié)一下Spring 框架對 JPA 提供的支持主要體現(xiàn)在如下幾個方面:

  1. 首先吁峻,它使得 JPA 配置變得更加靈活。JPA 規(guī)范要求在张,配置文件必須命名為 persistence.xml用含,并存在于類路徑下的 META-INF 目錄中。該文件通常包含了初始化 JPA 引擎所需的全部信息瞧掺。Spring 提供的 LocalContainerEntityManagerFactoryBean 提供了非常靈活的配置耕餐,persistence.xml 中的信息都可以在此以屬性注入的方式提供凡傅。

  2. 其次辟狈,Spring 實現(xiàn)了部分在 EJB 容器環(huán)境下才具有的功能,比如對@PersistenceContext夏跷、@PersistenceUnit 的容器注入支持哼转。

  3. 第三,也是最具意義的槽华,Spring 將 EntityManager 的創(chuàng)建與銷毀壹蔓、事務(wù)管理等代碼抽取出來,并由其統(tǒng)一管理猫态,開發(fā)者不需要關(guān)心這些佣蓉,如前面的代碼所示,業(yè)務(wù)方法中只剩下操作領(lǐng)域?qū)ο蟮拇a亲雪,事
    務(wù)管理和 EntityManager 創(chuàng)建勇凭、銷毀的代碼都不再需要開發(fā)者關(guān)心了。

通過前面的分析可以看出义辕,Spring 對 JPA 的支持已經(jīng)非常強大虾标,開發(fā)者只需關(guān)心核心業(yè)務(wù)邏輯的實現(xiàn)代碼,無需過多關(guān)注 EntityManager 的創(chuàng)建灌砖、事務(wù)處理等 JPA 相關(guān)的處理璧函,這基本上也是作為一個開發(fā)框架而言所能做到的極限了傀蚌。然而,Spring 開發(fā)小組并沒有止步蘸吓,他們再接再厲善炫,于最近推出了 Spring Data
JPA 框架,主要針對的就是 Spring 唯一沒有簡化到的業(yè)務(wù)邏輯代碼库继,至此销部,開發(fā)者連僅剩的實現(xiàn)持久層業(yè)
務(wù)邏輯的工作都省了,唯一要做的制跟,就只是聲明持久層的接口舅桩,其他都交給 Spring Data JPA 來幫你完成!

至此雨膨,讀者可能會存在一個疑問擂涛,框架怎么可能代替開發(fā)者實現(xiàn)業(yè)務(wù)邏輯呢?畢竟聊记,每一個應(yīng)用的持久層
業(yè)務(wù)甚至領(lǐng)域?qū)ο蠖疾槐M相同撒妈,框架是怎么做到的呢?其實這背后的思想并不復(fù)雜排监,比如狰右,當(dāng)你看到
UserDao.findUserById() 這樣一個方法聲明,大致應(yīng)該能判斷出這是根據(jù)給定條件的 ID 查詢出滿足條件的User 對象舆床。Spring Data JPA 做的便是規(guī)范方法的名字棋蚌,根據(jù)符合規(guī)范的名字來確定方法需要實現(xiàn)什么樣的邏輯指孤。

接下來我們針對前面的例子進(jìn)行改造筋岛,讓 Spring Data JPA 來幫助我們完成業(yè)務(wù)邏輯沥匈。在著手寫代碼之前螃概,
開發(fā)者需要先 下載Spring Data JPA 的發(fā)布包(需要同時下載 Spring Data Commons 和 Spring Data JPA 兩
個發(fā)布包鸣峭,Commons 是 Spring Data 的公共基礎(chǔ)包)绘面,并把相關(guān)的依賴 JAR 文件加入到 CLASSPATH
中蹭秋。

首先凿将,讓持久層接口 UserDao 繼承 Repository 接口腾夯。該接口使用了泛型颊埃,需要為其提供兩個類型:第一個
為該接口處理的域?qū)ο箢愋停诙€為該域?qū)ο蟮闹麈I類型蝶俱。修改后的 UserDao 如下:

清單 12. Spring Data JPA 風(fēng)格的持久層接口

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

然后刪除 UserDaoImpl 類班利,因為我們前面說過,框架會為我們完成業(yè)務(wù)邏輯跷乐。最后肥败,我們需要在 Spring
配置文件中增加如下配置,以使 Spring 識別出需要為其實現(xiàn)的持久層接口:

清單 13. 在 Spring 配置文件中啟用掃描并自動創(chuàng)建代理的功能

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

至此便大功告成了!執(zhí)行一下測試代碼馒稍,然后看一下數(shù)據(jù)庫皿哨,新的數(shù)據(jù)已經(jīng)如我們預(yù)期的添加到表中了。
如果要再增加新的持久層業(yè)務(wù)纽谒,比如希望查詢出給 ID 的 AccountInfo 對象证膨,該怎么辦呢?很簡單鼓黔,在
UserDao 接口中增加一行代碼即可:

清單 14. 修改后的持久層接口央勒,增加一個方法聲明

public interface UserDao extends Repository<AccountInfo, Long> {
public AccountInfo save(AccountInfo accountInfo);
// 你需要做的,僅僅是新增如下一行方法聲明
public AccountInfo findByAccountId(Long accountId);
}

下面總結(jié)一下使用 Spring Data JPA 進(jìn)行持久層開發(fā)大致需要的三個步驟:

  1. 聲明持久層的接口澳化,該接口繼承 Repository崔步,Repository 是一個標(biāo)記型接口,它不包含任何方法缎谷,當(dāng)
    然如果有需要井濒,Spring Data 也提供了若干 Repository 子接口,其中定義了一些常用的增刪改查列林,以
    及分頁相關(guān)的方法瑞你。
  2. 在接口中聲明需要的業(yè)務(wù)方法。Spring Data 將根據(jù)給定的策略(具體策略稍后講解)來為其生成實現(xiàn)
    代碼希痴。
  3. 在 Spring 配置文件中增加一行聲明者甲,讓 Spring 為聲明的接口創(chuàng)建代理對象。配置了
    <jpa:repositories后砌创,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄虏缸,為繼承
    Repository 或其子接口的接口創(chuàng)建代理對象,并將代理對象注冊為 Spring Bean纺铭,業(yè)務(wù)層便可以通過
    Spring 自動封裝的特性來直接使用該對象寇钉。

此外,<jpa:repository還提供了一些屬性和子標(biāo)簽舶赔,便于做更細(xì)粒度的控制∏恚可以在 <jpa:repository內(nèi)部
使用 <context:include-filter>竟纳、<context:exclude-filter來過濾掉一些不希望被掃描到的接口。具體的使用方
法見 Spring參考文檔疚鲤。

應(yīng)該繼承哪個接口锥累?

前面提到,持久層接口繼承 Repository 并不是唯一選擇集歇。Repository 接口是 Spring Data 的一個核心接口桶略,
它不提供任何方法,開發(fā)者需要在自己定義的接口中聲明需要的方法。與繼承 Repository 等價的一種方
式际歼,就是在持久層接口上使用 @RepositoryDefinition 注解惶翻,并為其指定 domainClass 和 idClass 屬性。如
下兩種方式是完全等價的:

清單 15. 兩種等價的繼承接口方式示例

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

如果持久層接口較多鹅心,且每一個接口都需要聲明相似的增刪改查方法吕粗,直接繼承 Repository 就顯得有些啰
嗦,這時可以繼承 CrudRepository旭愧,它會自動為域?qū)ο髣?chuàng)建增刪改查方法颅筋,供業(yè)務(wù)層直接使用。開發(fā)者只
是多寫了 "Crud" 四個字母输枯,即刻便為域?qū)ο筇峁┝碎_箱即用的十個增刪改查方法议泵。

但是,使用 CrudRepository 也有副作用桃熄,它可能暴露了你不希望暴露給業(yè)務(wù)層的方法肢簿。比如某些接口你只
希望提供增加的操作而不希望提供刪除的方法。針對這種情況蜻拨,開發(fā)者只能退回到 Repository 接口池充,然后
到 CrudRepository 中把希望保留的方法聲明復(fù)制到自定義的接口中即可。

分頁查詢和排序是持久層常用的功能缎讼,Spring Data 為此提供了 PagingAndSortingRepository 接口收夸,它繼承
自 CrudRepository 接口,在 CrudRepository 基礎(chǔ)上新增了兩個與分頁有關(guān)的方法血崭。但是卧惜,我們很少會將自
定義的持久層接口直接繼承自 PagingAndSortingRepository,而是在繼承 Repository 或 CrudRepository 的
基礎(chǔ)上夹纫,在自己聲明的方法參數(shù)列表最后增加一個 Pageable 或 Sort 類型的參數(shù)咽瓷,用于指定分頁或排序信
息即可,這比直接使用 PagingAndSortingRepository 提供了更大的靈活性舰讹。

JpaRepository 是繼承自 PagingAndSortingRepository 的針對 JPA 技術(shù)提供的接口茅姜,它在父接口的基礎(chǔ)上,
提供了其他一些方法月匣,比如 flush()钻洒,saveAndFlush(),deleteInBatch() 等锄开。如果有這樣的需求素标,則可以繼承
該接口。

上述四個接口萍悴,開發(fā)者到底該如何選擇头遭?其實依據(jù)很簡單寓免,根據(jù)具體的業(yè)務(wù)需求,選擇其中之一计维。筆者建
議在通常情況下優(yōu)先選擇 Repository 接口袜香。因為 Repository 接口已經(jīng)能滿足日常需求,其他接口能做到的
在 Repository 中也能做到享潜,彼此之間并不存在功能強弱的問題困鸥。只是 Repository 需要顯示聲明需要的方
法,而其他則可能已經(jīng)提供了相關(guān)的方法剑按,不需要再顯式聲明疾就,但如果對 Spring Data JPA 不熟悉,別人在
檢視代碼或者接手相關(guān)代碼時會有疑惑艺蝴,他們不明白為什么明明在持久層接口中聲明了三個方法猬腰,而在業(yè)
務(wù)層使用該接口時,卻發(fā)現(xiàn)有七八個方法可用猜敢,從這個角度而言姑荷,應(yīng)該優(yōu)先考慮使用 Repository 接口。

前面提到缩擂,Spring Data JPA 在后臺為持久層接口創(chuàng)建代理對象時鼠冕,會解析方法名字,并實現(xiàn)相應(yīng)的功能胯盯。
除了通過方法名字以外懈费,它還可以通過如下兩種方式指定查詢語句:

  1. Spring Data JPA 可以訪問 JPA 命名查詢語句。開發(fā)者只需要在定義命名查詢語句時博脑,為其指定一個
    符合給定格式的名字憎乙,Spring Data JPA 便會在創(chuàng)建代理對象時,使用該命名查詢語句來實現(xiàn)其功能叉趣。
  2. 開發(fā)者還可以直接在聲明的方法上面使用 @Query 注解泞边,并提供一個查詢語句作為參數(shù),Spring Data
    JPA 在創(chuàng)建代理對象時疗杉,便以提供的查詢語句來實現(xiàn)其功能

下面我們分別講述三種創(chuàng)建查詢的方式阵谚。

通過解析方法名創(chuàng)建查詢

通過前面的例子,讀者基本上對解析方法名創(chuàng)建查詢的方式有了一個大致的了解乡数,這也是 Spring Data JPA
吸引開發(fā)者的一個很重要的因素椭蹄。該功能其實并非 Spring Data JPA 首創(chuàng),而是源自一個開源的 JPA 框架
Hades净赴,該框架的作者 Oliver Gierke 本身又是 Spring Data JPA 項目的 Leader,所以把 Hades 的優(yōu)勢引入
到 Spring Data JPA 也就是順理成章的了罩润。

框架在進(jìn)行方法名解析時玖翅,會先把方法名多余的前綴截取掉,比如 find、findBy金度、read应媚、readBy、get猜极、
getBy中姜,然后對剩下部分進(jìn)行解析。并且如果方法的最后一個參數(shù)是 Sort 或者 Pageable 類型跟伏,也會提取相
關(guān)的信息丢胚,以便按規(guī)則進(jìn)行排序或者分頁查詢。

在創(chuàng)建查詢時受扳,我們通過在方法名中使用屬性名稱來表達(dá)携龟,比如 findByUserAddressZip ()】备撸框架在解析該
方法時峡蟋,首先剔除 findBy,然后對剩下的屬性進(jìn)行解析华望,詳細(xì)規(guī)則如下(此處假設(shè)該方法針對的域?qū)ο鬄?br> AccountInfo 類型):

l 先判斷 userAddressZip (根據(jù) POJO 規(guī)范蕊蝗,首字母變?yōu)樾懀峦┦欠駷?AccountInfo 的一個屬
性赖舟,如果是蓬戚,則表示根據(jù)該屬性進(jìn)行查詢;如果沒有該屬性建蹄,繼續(xù)第二步碌更;

l 從右往左截取第一個大寫字母開頭的字符串(此處為 Zip),然后檢查剩下的字符串是否為
AccountInfo 的一個屬性洞慎,如果是痛单,則表示根據(jù)該屬性進(jìn)行查詢;如果沒有該屬性劲腿,則重復(fù)第二步旭绒,
繼續(xù)從右往左截取焦人;最后假設(shè) user 為 AccountInfo 的一個屬性挥吵;

l 接著處理剩下部分( AddressZip ),先判斷 user 所對應(yīng)的類型是否有 addressZip 屬性花椭,如果有忽匈,則
表示該方法最終是根據(jù) "AccountInfo.user.addressZip" 的取值進(jìn)行查詢;否則繼續(xù)按照步驟 2 的規(guī)則
從右往左截取矿辽,最終表示根據(jù) "AccountInfo.user.address.zip" 的值進(jìn)行查詢丹允。

可能會存在一種特殊情況郭厌,比如 AccountInfo 包含一個 user 的屬性,也有一個 userAddress 屬性雕蔽,此時會
存在混淆折柠。讀者可以明確在屬性之間加上 "_" 以顯式表達(dá)意圖,比如 "findByUser_AddressZip()" 或者
findByUserAddress_Zip()"批狐。

在查詢時扇售,通常需要同時根據(jù)多個屬性進(jìn)行查詢,且查詢的條件也格式各樣(大于某個值嚣艇、在某個范圍等
等)承冰,Spring Data JPA 為此提供了一些表達(dá)條件查詢的關(guān)鍵字,大致如下:

l And --- 等價于 SQL 中的 and 關(guān)鍵字髓废,比如 findByUsernameAndPassword(String user, Striang pwd)巷懈;

l Or --- 等價于 SQL 中的 or 關(guān)鍵字,比如 findByUsernameOrAddress(String user, String addr)慌洪;

l Between --- 等價于 SQL 中的 between 關(guān)鍵字顶燕,比如 findBySalaryBetween(int max, int min);

l LessThan --- 等價于 SQL 中的 "<"冈爹,比如 findBySalaryLessThan(int max)涌攻;

l GreaterThan --- 等價于 SQL 中的">",比如 findBySalaryGreaterThan(int min)频伤;

l IsNull --- 等價于 SQL 中的 "is null"恳谎,比如 findByUsernameIsNull();

l IsNotNull --- 等價于 SQL 中的 "is not null"憋肖,比如 findByUsernameIsNotNull()因痛;

l NotNull --- 與 IsNotNull 等價;

l Like --- 等價于 SQL 中的 "like"岸更,比如 findByUsernameLike(String user)鸵膏;

l NotLike --- 等價于 SQL 中的 "not like",比如 findByUsernameNotLike(String user)怎炊;

l OrderBy --- 等價于 SQL 中的 "order by"谭企,比如 findByUsernameOrderBySalaryAsc(String
user);

l Not --- 等價于 SQL 中的 "评肆! ="债查,比如 findByUsernameNot(String user);

l In --- 等價于 SQL 中的 "in"瓜挽,比如 findByUsernameIn(Collection<StringuserList) 盹廷,方法的參數(shù)可以
是 Collection 類型,也可以是數(shù)組或者不定長參數(shù)久橙;

l NotIn --- 等價于 SQL 中的 "not in"速和,比如 findByUsernameNotIn(Collection<StringuserList) 歹垫,方法的
參數(shù)可以是 Collection 類型剥汤,也可以是數(shù)組或者不定長參數(shù)颠放;

使用 @Query 創(chuàng)建查詢

@Query 注解的使用非常簡單,只需在聲明的方法上面標(biāo)注該注解吭敢,同時提供一個 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);
}

很多開發(fā)者在創(chuàng)建 JP QL 時喜歡使用命名參數(shù)來代替位置編號,@Query 也對此提供了支持鹿驼。JP QL 語句
中通過": 變量"的格式來指定參數(shù)欲低,同時在方法的參數(shù)前面使用 @Param 將方法參數(shù)與 JP QL 中的命名參
數(shù)對應(yīng),示例如下:

清單 17. @Query 支持命名參數(shù)示例

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);
}

此外畜晰,開發(fā)者也可以通過使用 @Query 來執(zhí)行一個更新操作砾莱,為此,我們需要在使用 @Query 的同時凄鼻,用@Modifying 來將該操作標(biāo)識為修改查詢腊瑟,這樣框架最終會生成一個更新的操作,而非查詢块蚌。如下所示:

清單 18. 使用 @Modifying 將查詢標(biāo)識為修改查詢

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

通過調(diào)用 JPA 命名查詢語句創(chuàng)建查詢
命名查詢是 JPA 提供的一種將查詢語句從方法體中獨立出來闰非,以供多個方法共用的功能。Spring Data JPA
對命名查詢也提供了很好的支持峭范。用戶只需要按照 JPA 規(guī)范在 orm.xml 文件或者在代碼中使用
@NamedQuery(或 @NamedNativeQuery)定義好查詢語句财松,唯一要做的就是為該語句命名時,需要滿
足”DomainClass.methodName()”的命名規(guī)則纱控。假設(shè)定義了如下接口:

清單 19. 使用 JPA 命名查詢時辆毡,聲明接口及方法時不需要什么特殊處理

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

如果希望為 findTop5() 創(chuàng)建命名查詢,并與之關(guān)聯(lián)甜害,我們只需要在適當(dāng)?shù)奈恢枚x命名查詢語句舶掖,并將其
命名為 "AccountInfo.findTop5",框架在創(chuàng)建代理類的過程中唾那,解析到該方法時访锻,優(yōu)先查找名為
"AccountInfo.findTop5" 的命名查詢定義,如果沒有找到闹获,則嘗試解析方法名期犬,根據(jù)方法名字創(chuàng)建查詢。

創(chuàng)建查詢的順序

Spring Data JPA 在為接口創(chuàng)建代理對象時避诽,如果發(fā)現(xiàn)同時存在多種上述情況可用龟虎,它該優(yōu)先采用哪種策略
呢?為此沙庐,<jpa:repositories提供了 query-lookup-strategy 屬性鲤妥,用以指定查找的順序佳吞。它有如下三個取
值:

l create --- 通過解析方法名字來創(chuàng)建查詢。即使有符合的命名查詢棉安,或者方法通過 @Query 指定的查詢
語句底扳,都將會被忽略。

l create-if-not-found --- 如果方法通過 @Query 指定了查詢語句贡耽,則使用該語句實現(xiàn)查詢衷模;如果沒有,
則查找是否定義了符合條件的命名查詢蒲赂,如果找到阱冶,則使用該命名查詢;如果兩者都沒有找到滥嘴,則通
過解析方法名字來創(chuàng)建查詢木蹬。這是 query-lookup-strategy 屬性的默認(rèn)值。

l use-declared-query --- 如果方法通過 @Query 指定了查詢語句若皱,則使用該語句實現(xiàn)查詢镊叁;如果沒有,
則查找是否定義了符合條件的命名查詢是尖,如果找到意系,則使用該命名查詢;如果兩者都沒有找到饺汹,則拋
出異常蛔添。

Spring Data JPA 對事務(wù)的支持

默認(rèn)情況下,Spring Data JPA 實現(xiàn)的方法都是使用事務(wù)的兜辞。針對查詢類型的方法迎瞧,其等價于
@Transactional(readOnly=true);增刪改類型的方法逸吵,等價于 @Transactional凶硅。可以看出扫皱,除了將查詢的方
法設(shè)為只讀事務(wù)外足绅,其他事務(wù)屬性均采用默認(rèn)值。

如果用戶覺得有必要韩脑,可以在接口方法上使用 @Transactional 顯式指定事務(wù)屬性氢妈,該值覆蓋 Spring Data
JPA 提供的默認(rèn)值。同時段多,開發(fā)者也可以在業(yè)務(wù)層方法上使用 @Transactional 指定事務(wù)屬性首量,這主要針對
一個業(yè)務(wù)層方法多次調(diào)用持久層方法的情況。持久層的事務(wù)會根據(jù)設(shè)置的事務(wù)傳播行為來決定是掛起業(yè)務(wù)
層事務(wù)還是加入業(yè)務(wù)層的事務(wù)。具體 @Transactional 的使用加缘,請參考 Spring的參考文檔鸭叙。

為接口中的部分方法提供自定義實現(xiàn)

有些時候,開發(fā)者可能需要在某些方法中做一些特殊的處理拣宏,此時自動生成的代理對象不能完全滿足要
求沈贝。為了享受 Spring Data JPA 帶給我們的便利,同時又能夠為部分方法提供自定義實現(xiàn)蚀浆,我們可以采用如
下的方法:

l 將需要開發(fā)者手動實現(xiàn)的方法從持久層接口(假設(shè)為 AccountDao )中抽取出來缀程,獨立成一個新的接
口(假設(shè)為 AccountDaoPlus ),并讓 AccountDao 繼承 AccountDaoPlus市俊;

l 為 AccountDaoPlus 提供自定義實現(xiàn)(假設(shè)為 AccountDaoPlusImpl );

l 將 AccountDaoPlusImpl 配置為 Spring Bean滤奈;

l 在 <jpa:repositories中按清單 19 的方式進(jìn)行配置摆昧。

清單 20. 指定自定義實現(xiàn)類

<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 屬性蜒程,用以指定實現(xiàn)類的后綴绅你。假設(shè)做了如下
配置:

清單 21. 設(shè)置自動查找時默認(rèn)的自定義實現(xiàn)類命名規(guī)則

<jpa:repositories base-package="footmark.springdata.jpa.dao"
repository-impl-postfix="Impl"/>

則在框架掃描到 AccountDao 接口時,它將嘗試在相同的包目錄下查找 AccountDaoImpl.java昭躺,如果找到忌锯,
便將其中的實現(xiàn)方法作為最終生成的代理類中相應(yīng)方法的實現(xiàn)。

結(jié)束語

本文主要介紹了 Spring Data JPA 的使用领炫,以及它與 Spring 框架的無縫集成偶垮。Spring Data JPA 其實并不依
賴于 Spring 框架,有興趣的讀者可以參考本文最后的"參考資源"進(jìn)一步學(xué)習(xí)帝洪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末似舵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子葱峡,更是在濱河造成了極大的恐慌砚哗,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砰奕,死亡現(xiàn)場離奇詭異蛛芥,居然都是意外死亡,警方通過查閱死者的電腦和手機军援,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門仅淑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盖溺,你說我怎么就攤上這事漓糙。” “怎么了烘嘱?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵昆禽,是天一觀的道長蝗蛙。 經(jīng)常有香客問我,道長醉鳖,這世上最難降的妖魔是什么捡硅? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮盗棵,結(jié)果婚禮上壮韭,老公的妹妹穿的比我還像新娘。我一直安慰自己纹因,他們只是感情好喷屋,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瞭恰,像睡著了一般屯曹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惊畏,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天恶耽,我揣著相機與錄音,去河邊找鬼颜启。 笑死偷俭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缰盏。 我是一名探鬼主播涌萤,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乳规!你這毒婦竟也來了形葬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤暮的,失蹤者是張志新(化名)和其女友劉穎笙以,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冻辩,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡猖腕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了恨闪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倘感。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咙咽,靈堂內(nèi)的尸體忽然破棺而出老玛,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布蜡豹,位于F島的核電站麸粮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏镜廉。R本人自食惡果不足惜弄诲,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望娇唯。 院中可真熱鬧齐遵,春花似錦、人聲如沸塔插。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佑淀。三九已至留美,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伸刃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工逢倍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捧颅,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓较雕,卻偏偏與公主長得像碉哑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子亮蒋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容