Spring ORM+Hibernate?Out!換 Spring Data JPA 吧!

anxpp發表於2016-05-15

轉載請註明出處:http://blog.csdn.net/anxpp/article/details/51415698,謝謝!

    在一切開始之前,先舉個簡單的例子,以提高大家的興致!

    如果一張表user有三個欄位,id、name和age,要查詢指定姓氏在某年齡以上的user,在傳統的Spring+Hibernate中,dao層我們是這樣寫的:

    UserDao:

  1. public interface UserDao{
  2. List<User> findByNameLikeAndAgeGreaterThan(String firstName,Integer age);
  3. }

    UserDaoImpl(已經是相對簡單的HibernateTemplate方式了):

  1. public class UserDaoImpl implements UserDao{
  2. @Override
  3. public List<User> findByFirstNameAndAge(String firstName, Integer age) {
  4. //具體hql查詢:"from User where name like '%'"+firstName + "and age > " + age;
  5. return hibernateTemplateMysql.execute(new HibernateCallback() {
  6. @Override
  7. public Object doInHibernate(Session session) throws HibernateException {
  8. String hql = "from User where name like '?' and age > ?";
  9. Query query = session.createQuery(hql);
  10. query.setParameter(0, firstName+"");
  11. query.setParameter(1, age);
  12. return query.uniqueResult();
  13. }
  14. });
  15. }
  16. }

    然而,如果我們用Spring Data JPA呢:

  1. public interface UserDao extends JpaRepository<User, Serializable>{
  2. List<User> findByNameLikeAndAgeGreaterThan(String firstName,Integer age);
  3. }

    對,就這樣,已經沒有了,連實現都不需要寫的!service直接呼叫UserDao.findByNameLikeAndAgeGreaterThan(firstName+"%",age)即可。

    那麼,下面就來介紹,Spring Data JPA是個什麼,如何為我們簡化JPA開發。

    推薦兩篇文章:

  1.         JPA規範介紹及例項(Java資料持久化解決方案)
  2.         本文重點做介紹,如果需要了解如何搭建,請參考這篇文章:手把手教你從最基本的Java工程搭建SpringMVC+SpringDataJPA+Hibernate(含原始碼下載)(Spring Data JPA詳細搭建過程)

1、簡介

    官網:http://projects.spring.io/spring-data-jpa/

    Spring Data是一個用於簡化資料庫訪問,並支援雲服務的開源框架。其主要目標是使得對資料的訪問變得方便快捷,並支援map-reduce框架和雲端計算資料服務。

    Spring Data 包含多個子專案:

    01

    JPA就是其中子專案之一,正如JPA規範介紹及例項中的介紹,JPA屬於重量級的,因為它需要執行在JAVA EE容器中,而Spring Data JPA提供了輕量級的實現,在任何servlet容器中就可以執行。

    Spring Data JPA相對於Java EE中的JPA,更加簡潔易用:

  1.     配置更簡單。
  2.     Spring以輕量級的方式實現了部分在 EJB 容器環境下才具有的功能。
  3.     Spring 將 EntityManager 的建立與銷燬、事務管理等程式碼抽取出來,並由其統一管理。
  4.     正如前面的例子,極大的簡化了資料庫訪問層(dao)的程式碼。

    Spring Data JPA 讓一切近乎完美!Spring 對 JPA 的支援非常強大,開發者只需關心核心業務邏輯的實現程式碼,無需過多關注 EntityManager 的建立、事務處理等 JPA 相關的處理,這基本上也是作為一個開發框架而言所能做到的極限了。至此,開發者連僅剩的實現持久層業務邏輯的工作都省了,唯一要做的,就只是宣告持久層的介面,其他都交給 Spring Data JPA 來幫你完成!

2、開發步驟

    Spring Data JPA 進行持久層開發一般分三個步驟:

  1.     宣告持久層的介面,該介面繼承 Repository(或Repository的子介面,其中定義了一些常用的增刪改查,以及分頁相關的方法)。
  2.     在介面中宣告需要的業務方法。Spring Data 將根據給定的策略生成實現程式碼。
  3.     在 Spring 配置檔案中增加一行宣告,讓 Spring 為宣告的介面建立代理物件。配置了 <jpa:repositories> 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,為繼承 Repository 或其子介面的介面建立代理物件,並將代理物件註冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該物件。

3、主要的介面

  •     Repository: 僅僅是一個標識,沒有任何方法,方便Spring自動掃描識別
  •     CrudRepository: 繼承Repository,實現了一組CRUD相關的方法
  •     PagingAndSortingRepository: 繼承CrudRepository,實現了一組分頁排序相關的方法
  •     JpaRepository: 繼承PagingAndSortingRepository,實現一組JPA規範相關的方法

    (根據一個最小什麼的原則,一下想不起了)我們通常優先考慮Repository介面,因為它已經足以完成大多時候的使用了,如果有更多的需求,再依次選擇其子介面。

    我們的dao層介面繼承Repository後,如無更多需要,可以不用寫任何程式碼,僅僅是一個空的介面就行了。

    同時,也可以選擇不繼承介面,而使用註解方式,比如,下面兩種方式是完全一樣的:

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

 

4、建立查詢的方法

    4.1、Query creation from method names(解析方法名)

    如文首例子中的 findByNameLikeAndAgeGreaterThan(String firstName,Integer age) 方法一樣,我們從方法名就知道想要做什麼,而此時,Spring也知道... 所以它為我們建立了對應的查詢,而不需要我們多些一段程式碼。如何做到的呢:

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

    如上面的findByNameLikeAndAgeGreaterThan,解析步驟如下:

    1、剔除findBy

    2、先判斷 nameLikeAndAgeGreaterThan(根據 POJO 規範,首字母變為小寫)是否為 User 的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則進行下一步。

    3、從右往左擷取第一個大寫字母開頭的字串(此處為Than),然後檢查剩下的字串是否為 User 的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第2步,繼續從右往左擷取;知道找出 name 為 User 的屬性。

    4、從已擷取的部分後面開始,重新從第1步開始(剔除條件語句),迴圈忘後,直到整個方法名處理完畢。

    5、通過獲取的操作、條件和屬性,帶入引數的值,生成查詢。

    在查詢時,通常需要同時根據多個屬性進行查詢,提供的一些表達條件查詢的關鍵字完整的表:

    02

    如果擷取的時候,因為Entity的一些屬性剛好碰到有歧義的時候,這種方式的可讀性就不是太好了。

    4.2、通過 @Query 建立查詢

    如果想要更好的可讀性,使用@Query 建立查詢也是一種很好的方法。

    比如文首的例子,我們可以這樣改寫:

  1. public interface UserDao extends JpaRepository<User, Serializable>{
  2. @Query("select * from User u where u.name like ?1 and u.age>?2")
  3. List<User> findByNameLikeAndAgeGreaterThan(String firstName,Integer age);
  4. }

    出了使用?+數字的方式代替引數,還可以使用如下方式:

  1. public interface UserDao extends JpaRepository<User, Serializable>{
  2. @Query("select * from User u where u.name like :first and u.age>:age")
  3. List<User> findByNameLikeAndAgeGreaterThan(@Param("first")String firstName,@Param("age")Integer age);
  4. }

    @Query 也可以用來修改和刪除等,加上@Modifying即可:

  1. @Modifying
  2. @Query("update User u set u.name = ?1 where u.id = ?2")
  3. public int increaseSalary(String name, int id);

     可以如下加上引數以使用原生查詢:

  1. public interface UserRepository extends JpaRepository<User, Long> {
  2. @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
  3. User findByEmailAddress(String emailAddress);
  4. }

    4.3、JPA NamedQueries(命名查詢)

    有兩種方式,一是通過xml配置:

  1. <named-query name="User.findByLastname">
  2. <query>select u from User u where u.lastname = ?1</query>
  3. </named-query>

    另一種是通過註解:

  1. @Entity
  2. @NamedQuery(name = "User.findByEmailAddress",query = "select u from User u where u.emailAddress = ?1")
  3. public class User {}

    如果要使用上面編寫的兩個查詢,只需要dao層介面生命方法即可:

  1. public interface UserDao extends JpaRepository<User, Long> {
  2. List<User> findByLastname(String lastname);
  3. User findByEmailAddress(String emailAddress);
  4. }

    對於查詢的建立,會有限查詢是否有命名查詢,而不是根據方法名建立查詢。

    4.4、查詢建立策略

  •     CREATE:企圖建構從查詢方法名特定儲存查詢。在一般的方法是從該方法名中移除一組特定字首和解析方法的其餘部分。其他兩種建立查詢的方法會被忽略。
  •     USE_DECLARED_QUERY:如果方法通過 @Query 指定了查詢語句,則使用該語句實現查詢;如果沒有,則查詢是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則丟擲異常。
  •     CREATE_IF_NOT_FOUND(預設):如果方法通過 @Query 指定了查詢語句,則使用該語句實現查詢;如果沒有,則查詢是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則通過解析方法名字來建立查詢。

    4.5、為介面中部分方法提供自定義實現

    雖然 Spring Data JPA 已為我們提供瞭如此強大的功能,但有時候,可能我們還是希望能自定義一些查詢的實現。

    自定義實現一般又兩種方法:

    方法一:

  •     將需要手動實現的方法從持久層介面(比如 UserDao )中抽取出來,獨立成一個新的介面(比如 UserDaoMore ),並讓 UserDao 繼承 UserDaoMore
  •     為 UserDaoMore 提供自定義實現(比如 UserDaoMoreImpl )
  •     將 UserDaoMoreImpl 配置為 Spring Bean
  •     在 <jpa:repositories> 中如下配置:
  1. <jpa:repositories base-package="com.anxpp.demo.core.dao">
  2. <jpa:repository id="UserDao" repository-impl-ref=" UserDaoMore" />
  3. </jpa:repositories>
  4. <bean id="UserDaoMore" class="..."/>

    方法二:

    設定自動查詢時預設的自定義實現類命名規則,用以指定實現類的字尾:

  1. <jpa:repositories base-package="com.anxpp.demo.core.dao" repository-impl-postfix="Impl"/>

    在框架掃描到 UserDao 介面時,它將嘗試在相同的包目錄下查詢 UserDaoImpl.java,如果找到,便將其中的實現方法作為最終生成的代理類中相應方法的實現。

5、呼叫儲存過程

    如果需要呼叫儲存過程,可以通過如下方式:

    首先在實體上定義:

  1. @Entity
  2. @NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = {
  3. @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
  4. @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
  5. public class User {}

    顯示的呼叫儲存過程:

  1. @Procedure("plus1inout")
  2. Integer explicitlyNamedPlus1inout(Integer arg);

    通過別名隱示的呼叫:

  1. @Procedure("plus1inout")
  2. Integer explicitlyNamedPlus1inout(Integer arg);

    或者通過實體的註解呼叫:

  1. @Procedure(name = "User.plus1IO")
  2. Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);

    以及通過方法名的隱示呼叫:

  1. Procedure
  2. Integer plus1(@Param("arg") Integer arg);

6、Query by Example

    Example API 主要由三部分組成:

  •     Probe: That is the actual example of a domain object with populated fields.
  •     ExampleMatcher: The ExampleMatcher carries details on how to match particular fields. It can be reused across multiple Examples.
  •     Example: An Example consists of the probe and the ExampleMatcher. It is used to create the query.

    何時使用:

  •     查詢您的資料儲存與一組靜態或動態約束
  •     可以對物件頻繁的重構,而無需擔心破壞現有的查詢
  •     從底層資料儲存API獨立工作

    限制:

  •     查詢謂詞使用and連線
  •     不支援條件巢狀(如 firstname = ?0 or (firstname = ?1 and lastname = ?2))
  •     starts/contains/ends/regex 僅支援 String型別,exact 僅支援其他型別。

    示例實體:

  1. public class Person {
  2. @Id
  3. private String id;
  4. private String firstname;
  5. private String lastname;
  6. private Address address;
  7. // … getters and setters omitted
  8. }

    這一塊暫時不忙研究了...  感覺寫完睡覺了,有時間再好好看看。我猜有點像享元模式,不過擴充套件了很多東西。

7、事務

    預設情況下,Spring Data JPA 實現的方法都是使用事務的。

    針對查詢型別的方法,其等價於 @Transactional(readOnly=true)。

    增刪改型別的方法,等價於 @Transactional。可以看出,除了將查詢的方法設為只讀事務外,其他事務屬性均採用預設值。

    也可以自定義事務:

  1. public interface UserRepository extends CrudRepository<User, Long> {
  2. @Override
  3. @Transactional(timeout = 10)
  4. public List<User> findAll();
  5. }

    可以將事務註解到service的一個方法上:

  1. @Service
  2. class UserManagementImpl implements UserManagement {
  3. private final UserRepository userRepository;
  4. private final RoleRepository roleRepository;
  5. @Autowired
  6. public UserManagementImpl(UserRepository userRepository,
  7. RoleRepository roleRepository) {
  8. this.userRepository = userRepository;
  9. this.roleRepository = roleRepository;
  10. }
  11. @Transactional
  12. public void addRoleToAllUsers(String roleName) {
  13. Role role = roleRepository.findByName(roleName);
  14. for (User user : userRepository.findAll()) {
  15. user.addRole(role);
  16. userRepository.save(user);
  17. }
  18. }

    注意:需要<tx:annotation-driven /> 或使用了 @EnableTransactionManagement

    可以在查詢的方法前面註解事務:

  1. @Transactional(readOnly = true)
  2. public interface UserRepository extends JpaRepository<User, Long> {
  3. List<User> findByLastname(String lastname);
  4. @Modifying
  5. @Transactional
  6. @Query("delete from User u where u.active = false")
  7. void deleteInactiveUsers();
  8. }

    這裡的deleteInactiveUsers()方法因為的事務註解覆蓋了整體的readOnly,所以這個方法不是隻讀的。

8、附錄

    查詢關鍵字:

    03

    查詢的返回值型別:

    04


    小結:本文大概的介紹了Spring Data JPA,如果需要了解如何搭建相關專案,請參考:手把手教你從最基本的Java工程搭建SpringMVC+SpringDataJPA+Hibernate(含原始碼下載)

相關文章