本文主要講述 Spring Data JPA,可是爲了避免至於給 JPA 和 Spring 的初學者形成較大的學習曲線,咱們首先從 JPA 開始,簡單介紹一個 JPA 示例;接着重構該示例,並引入 Spring 框架,這兩部分不會涉及過多的篇幅,若是但願可以深刻學習 Spring 和 JPA,能夠根據本文最後提供的參考資料進一步學習。html
自 JPA 伴隨 Java EE 5 發佈以來,受到了各大廠商及開源社區的追捧,各類商用的和開源的 JPA 框架如雨後春筍般出現,爲開發者提供了豐富的選擇。它一改以前 EJB 2.x 中實體 Bean 笨重且難以使用的形象,充分吸取了在開源社區已經相對成熟的 ORM 思想。另外,它並不依賴於 EJB 容器,能夠做爲一個獨立的持久層技術而存在。目前比較成熟的 JPA 框架主要包括 Jboss 的 Hibernate EntityManager、Oracle 捐獻給 Eclipse 社區的 EclipseLink、Apache 的 OpenJPA 等。java
本文的示例代碼基於 Hibernate EntityManager 開發,可是讀者幾乎不用修改任何代碼,即可以很是容易地切換到其餘 JPA 框架,由於代碼中使用到的都是 JPA 規範提供的接口 / 類,並無使用到框架自己的私有特性。示例主要涉及七個文件,可是很清晰:業務層包含一個接口和一個實現;持久層包含一個接口、一個實現、一個實體類;另外加上一個 JPA 配置文件和一個測試類。相關類 / 接口代碼以下:mysql
@Entity @Table(name = "t_accountinfo") public class AccountInfo implements Serializable { private Long accountId; private Integer balance; // 此處省略 getter 和 setter 方法。 }
public interface UserService { public AccountInfo createNewAccount(String user, String pwd, Integer init); }
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); public AccountInfo createNewAccount(String user, String pwd, Integer init){ // 封裝域對象 AccountInfo accountInfo = new AccountInfo(); UserInfo userInfo = new UserInfo(); userInfo.setUsername(username); userInfo.setPassword(password); accountInfo.setBalance(initBalance); accountInfo.setUserInfo(userInfo); // 調用持久層,完成數據的保存 return userDao.save(accountInfo); } }
public interface UserDao { public AccountInfo save(AccountInfo accountInfo); }
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; } }
<?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>
public class SimpleSpringJpaDemo { public static void main(String[] args) { new UserServiceImpl().createNewAccount("ZhangJianPing", "123456", 1); } }
回頁首spring
接下來咱們引入 Spring,以展現 Spring 框架對 JPA 的支持。業務層接口 UserService 保持不變,UserServiceImpl 中增長了三個註解,以讓 Spring 完成依賴注入,所以再也不須要使用 new 操做符建立 UserDaoImpl 對象了。同時咱們還使用了 Spring 的聲明式事務:sql
@Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Transactional public AccountInfo createNewAccount( String name, String pwd, Integer init) { …… } }
對於持久層,UserDao 接口也不須要修改,只需修改 UserDaoImpl 實現,修改後的代碼以下:數據庫
@Repository("userDao") public class UserDaoImpl implements UserDao { @PersistenceContext private EntityManager em; @Transactional public Long save(AccountInfo accountInfo) { em.persist(accountInfo); return accountInfo.getAccountId(); } }
<?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>
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); } }
經過對比重構先後的代碼,能夠發現 Spring 對 JPA 的簡化已經很是出色了,咱們能夠大體總結一下 Spring 框架對 JPA 提供的支持主要體如今以下幾個方面:數組
回頁首框架
經過前面的分析能夠看出,Spring 對 JPA 的支持已經很是強大,開發者只需關心核心業務邏輯的實現代碼,無需過多關注 EntityManager 的建立、事務處理等 JPA 相關的處理,這基本上也是做爲一個開發框架而言所能作到的極限了。然而,Spring 開發小組並無止步,他們再接再礪,於最近推出了 Spring Data JPA 框架,主要針對的就是 Spring 惟一沒有簡化到的業務邏輯代碼,至此,開發者連僅剩的實現持久層業務邏輯的工做都省了,惟一要作的,就只是聲明持久層的接口,其餘都交給 Spring Data JPA 來幫你完成!dom
至此,讀者可能會存在一個疑問,框架怎麼可能代替開發者實現業務邏輯呢?畢竟,每個應用的持久層業務甚至領域對象都不盡相同,框架是怎麼作到的呢?其實這背後的思想並不複雜,好比,當你看到 UserDao.findUserById() 這樣一個方法聲明,大體應該能判斷出這是根據給定條件的 ID 查詢出知足條件的 User 對象。Spring Data JPA 作的即是規範方法的名字,根據符合規範的名字來肯定方法須要實現什麼樣的邏輯。ide
接下來咱們針對前面的例子進行改造,讓 Spring Data JPA 來幫助咱們完成業務邏輯。在着手寫代碼以前,開發者須要先 下載Spring Data JPA 的發佈包(須要同時下載 Spring Data Commons 和 Spring Data JPA 兩個發佈包,Commons 是 Spring Data 的公共基礎包),並把相關的依賴 JAR 文件加入到 CLASSPATH 中。
首先,讓持久層接口 UserDao 繼承 Repository 接口。該接口使用了泛型,須要爲其提供兩個類型:第一個爲該接口處理的域對象類型,第二個爲該域對象的主鍵類型。修改後的 UserDao 以下:
public interface UserDao extends Repository<AccountInfo, Long> { public AccountInfo save(AccountInfo accountInfo); }
而後刪除 UserDaoImpl 類,由於咱們前面說過,框架會爲咱們完成業務邏輯。最後,咱們須要在 Spring 配置文件中增長以下配置,以使 Spring 識別出須要爲其實現的持久層接口:
<-- 須要在 <beans> 標籤中增長對 jpa 命名空間的引用 --> <jpa:repositories base-package="footmark.springdata.jpa.dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager"/>
至此便大功告成了!執行一下測試代碼,而後看一下數據庫,新的數據已經如咱們預期的添加到表中了。若是要再增長新的持久層業務,好比但願查詢出給 ID 的 AccountInfo 對象,該怎麼辦呢?很簡單,在 UserDao 接口中增長一行代碼便可:
public interface UserDao extends Repository<AccountInfo, Long> { public AccountInfo save(AccountInfo accountInfo); // 你須要作的,僅僅是新增以下一行方法聲明 public AccountInfo findByAccountId(Long accountId); }
下面總結一下使用 Spring Data JPA 進行持久層開發大體須要的三個步驟:
此外,<jpa:repository> 還提供了一些屬性和子標籤,便於作更細粒度的控制。能夠在 <jpa:repository> 內部使用 <context:include-filter>、<context:exclude-filter> 來過濾掉一些不但願被掃描到的接口。具體的使用方法見 Spring參考文檔。
前面提到,持久層接口繼承 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 { …… }
若是持久層接口較多,且每個接口都須要聲明類似的增刪改查方法,直接繼承 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 吸引開發者的一個很重要的因素。該功能其實並不是 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 類型):
可能會存在一種特殊狀況,好比 AccountInfo 包含一個 user 的屬性,也有一個 userAddress 屬性,此時會存在混淆。讀者能夠明確在屬性之間加上 "_" 以顯式表達意圖,好比 "findByUser_AddressZip()" 或者 "findByUserAddress_Zip()"。
在查詢時,一般須要同時根據多個屬性進行查詢,且查詢的條件也格式各樣(大於某個值、在某個範圍等等),Spring Data JPA 爲此提供了一些表達條件查詢的關鍵字,大體以下:
@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);
命名查詢是 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 在爲接口建立代理對象時,若是發現同時存在多種上述狀況可用,它該優先採用哪一種策略呢?爲此,<jpa:repositories> 提供了 query-lookup-strategy 屬性,用以指定查找的順序。它有以下三個取值:
默認狀況下,Spring Data JPA 實現的方法都是使用事務的。針對查詢類型的方法,其等價於 @Transactional(readOnly=true);增刪改類型的方法,等價於 @Transactional。能夠看出,除了將查詢的方法設爲只讀事務外,其餘事務屬性均採用默認值。
若是用戶以爲有必要,能夠在接口方法上使用 @Transactional 顯式指定事務屬性,該值覆蓋 Spring Data JPA 提供的默認值。同時,開發者也能夠在業務層方法上使用 @Transactional 指定事務屬性,這主要針對一個業務層方法屢次調用持久層方法的狀況。持久層的事務會根據設置的事務傳播行爲來決定是掛起業務層事務仍是加入業務層的事務。具體 @Transactional 的使用,請參考 Spring的參考文檔。
有些時候,開發者可能須要在某些方法中作一些特殊的處理,此時自動生成的代理對象不能徹底知足要求。爲了享受 Spring Data JPA 帶給咱們的便利,同時又可以爲部分方法提供自定義實現,咱們能夠採用以下的方法:
<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 屬性,用以指定實現類的後綴。假設作了以下配置:
<jpa:repositories base-package="footmark.springdata.jpa.dao" repository-impl-postfix="Impl"/>
則在框架掃描到 AccountDao 接口時,它將嘗試在相同的包目錄下查找 AccountDaoImpl.java,若是找到,便將其中的實現方法做爲最終生成的代理類中相應方法的實現。
轉:http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-jpa/#icomments