1. 程式人生 > >spring data jpa——學習總結

spring data jpa——學習總結

JPA Spring Data 概述

  • JPA Spring Data : 致力於減少資料訪問層 (DAO) 的開發量. 開發者唯一要做的,就只是宣告持久層的介面,其他都交給 Spring Data JPA 來幫你完成!

  • 框架怎麼可能代替開發者實現業務邏輯呢?比如:當有一個 UserDao.findUserById()  這樣一個方法宣告,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User  物件。Spring Data JPA 做的便是規範方法的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。

 

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

  1. 配置 Spring 整合 JPA

  2. 在 Spring 配置檔案中配置 Spring Data,讓 Spring 為宣告的介面建立代理物件。配置了 <jpa:repositories> 後,Spring 初始化容器時將會掃描 base-package  指定的包目錄及其子目錄,為繼承 Repository 或其子介面的介面建立代理物件,並將代理物件註冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該物件。

  3. 宣告持久層的介面,該介面繼承  Repository,Repository 是一個標記型介面,它不包含任何方法,如必要,Spring Data 可實現 Repository 其他子介面,其中定義了一些常用的增刪改查,以及分頁相關的方法。

  4. 在介面中宣告需要的方法。Spring Data 將根據給定的策略(具體策略稍後講解)來為其生成實現程式碼。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpahttp://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 配置自動掃描的包 -->
    <context:component-scan base-package="com.atguigu.springdata"></context:component-scan>

    <!-- 1. 配置資料來源 -->
    <context:property-placeholder location="classpath:db.properties"/>

    <bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>    
        <property name="driverClass" value="${jdbc.driverClass}"></property>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        
        <!-- 配置其他屬性 -->
    </bean>

    <!-- 2. 配置 JPA 的 EntityManagerFactory -->
    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
        </property>
        <property name="packagesToScan" value="com.atguigu.springdata"></property>
        <property name="jpaProperties">
            <props>
                <!-- 二級快取相關 -->
                <!--  
                <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
                <prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop>
                -->
                <!-- 生成的資料表的列的對映策略 -->
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <!-- hibernate 基本屬性 -->
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

    <!-- 3. 配置事務管理器 -->
    <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"></property>    
    </bean>

    <!-- 4. 配置支援註解的事務 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!-- 5. 配置 SpringData -->
    <!-- 加入  jpa 的名稱空間 -->
    <!-- base-package: 掃描 Repository Bean 所在的 package -->
    <jpa:repositories base-package="com.atguigu.springdata" entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>

</beans>

 

Repository 介面概述

  1. Repository 介面是 Spring Data 的一個核心介面,它不提供任何方法,開發者需要在自己定義的介面中宣告需要的方法 

           public interface Repository<T, ID extends Serializable> { } 

  1. Spring Data可以讓我們只定義介面,只要遵循 Spring Data的規範,就無需寫實現類。 

  2. 與繼承 Repository 等價的一種方式,就是在持久層介面上使用 @RepositoryDefinition 註解,併為其指定 domainClass 和 idClass 屬性。如下兩種方式是完全等價的       

public interface PersonRepository extends Repository<Person, Integer>

@RepositoryDefinition(domainClass=Person.class, idClass=Integer.class)

public interface PersonRepository 

 

Repository 的子介面

 

基礎的 Repository 提供了最基本的資料訪問功能,其幾個子介面則擴充套件了一些功能。它們的繼承關係如下: 

  1. Repository: 僅僅是一個標識,表明任何繼承它的均為倉庫介面類

  2. CrudRepository: 繼承 Repository,實現了一組 CRUD 相關的方法 

  3. PagingAndSortingRepository: 繼承 CrudRepository,實現了一組分頁排序相關的方法 

  4. JpaRepository: 繼承 PagingAndSortingRepository,實現一組 JPA 規範相關的方法 

  5. 自定義的 XxxxRepository 需要繼承 JpaRepository,這樣的 XxxxRepository 介面就具備了通用的資料訪問控制層的能力。

  6. JpaSpecificationExecutor: 不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法 

 

簡單條件查詢

  • 簡單條件查詢: 查詢某一個實體類或者集合 

  • 按照 Spring Data 的規範,查詢方法以 find | read | get 開頭, 涉及條件查詢時,條件的屬性用條件關鍵字連線,要注意的是:條件屬性以首字母大寫。 

  • 例如:定義一個 Entity 實體類 class User{     private String firstName;     private String lastName; } 使用And條件連線時,應這樣寫: findByLastNameAndFirstName(String lastName,String firstName); 條件的屬性名稱與個數要與引數的位置與個數一一對應 

 

    支援的關鍵字:

  【直接在介面中定義查詢方法,如果是符合規範的,可以不用寫實現,目前支援的關鍵字寫法如下:】

 

 

查詢方法解析流程:

假如建立如下的查詢:findByUserDepUuid(),框架在解析該方法時,首先剔除 findBy,然後對剩下的屬性進行解析,假設查詢實體為Doc

  1. 先判斷 userDepUuid (根據 POJO 規範,首字母變為小寫)是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;

  2. 從右往左擷取第一個大寫字母開頭的字串(此處為Uuid),然後檢查剩下的字串是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左擷取;最後假設 user 為查詢實體的一個屬性;

  3. 接著處理剩下部分(DepUuid),先判斷 user 所對應的型別是否有depUuid屬性,如果有,則表示該方法最終是根據 “ Doc.user.depUuid” 的取值進行查詢;否則繼續按照步驟 2 的規則從右往左擷取,最終表示根據 “Doc.user.dep.uuid” 的值進行查詢。

  4. 可能會存在一種特殊情況,比如 Doc包含一個 user 的屬性,也有一個 userDep 屬性,此時會存在混淆。可以明確在屬性之間加上 "_" 以顯式表達意圖,比如 "findByUser_DepUuid()" 或者 "findByUserDep_uuid()"

 

特殊的引數: 還可以直接在方法的引數上加入分頁或排序的引數,比如:

  1. Page<UserModel> findByName(String name, Pageable pageable);

  2. List<UserModel> findByName(String name, Sort sort);

   //WHERE a.id > ?

    List<Person> getByAddress_IdGreaterThan(Integer id);

    //WHERE lastName LIKE ?% AND id < ?

    List<Person> getByLastNameStartingWithAndIdLessThan(String lastName, Integer id);
       //WHERE lastName LIKE %? AND id < ?

    List<Person> getByLastNameEndingWithAndIdLessThan(String lastName, Integer id);

 

    //WHERE email IN (?, ?, ?) OR birth < ?

    List<Person> getByEmailInAndBirthLessThan(List<String> emails, Date birth);

 

   //WHERE a.id > ?

    List<Person> getByAddress_IdGreaterThan(Integer id);

使用@Query自定義查詢

這種查詢可以宣告在 Repository 方法中,擺脫像命名查詢那樣的約束,將查詢直接在相應的介面方法中宣告,結構更為清晰,這是 Spring data 的特有實現。

    //查詢 id 值最大的那個 Person

    //使用 @Query 註解可以自定義 JPQL 語句以實現更靈活的查詢

    @Query("SELECT p FROM Person p WHERE p.id = (SELECT max(p2.id) FROM Person p2)")

    Person getMaxIdPerson();

索引引數與命名引數:

  1. 索引引數如下所示,索引值從1開始,查詢中 ”?X” 個數需要與方法定義的引數個數相一致,並且順序也要一致 

    //為 @Query 註解傳遞引數的方式1: 使用佔位符.

    @Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")

    List<Person> testQueryAnnotationParams1(String lastName, String email);
  1. 命名引數(推薦使用這種方式):可以定義好引數名,賦值時採用@Param("引數名"),而不用管順序。

    //為 @Query 註解傳遞引數的方式1: 命名引數的方式.    

@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")

    List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);

 

如果是 @Query 中有 LIKE 關鍵字,後面的引數需要前面或者後面加 %,這樣在傳遞引數值的時候就可以不加 %

    //SpringData 允許在佔位符上新增 %%.

    @Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")

    List<Person> testQueryAnnotationLikeParam(String lastName, String email);

    //SpringData 允許在佔位符上新增 %%.

    @Query("SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% OR p.email LIKE %:email%")

    List<Person> testQueryAnnotationLikeParam2(@Param("email") String email, @Param("lastName") String lastName);

 

@Modifying 註解和事務

@Query 與 @Modifying 執行更新操作:

  1. @Query 與 @Modifying 這兩個 annotation一起宣告,可定義個性化更新操作,例如只涉及某些欄位更新時最為常用,示例如下:

    //可以通過自定義的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支援使用 INSERT

    //在 @Query 註解中編寫 JPQL 語句, 但必須使用 @Modifying 進行修飾. 以通知 SpringData, 這是一個 UPDATE 或 DELETE 操作

    //UPDATE 或 DELETE 操作需要使用事務, 此時需要定義 Service 層. 在 Service 層的方法上新增事務操作.

    //預設情況下, SpringData 的每個方法上有事務, 但都是一個只讀事務. 他們不能完成修改操作!

    @Modifying

    @Query("UPDATE Person p SET p.email = :email WHERE id = :id")

    void updatePersonEmail(@Param("id") Integer id, @Param("email") String email);
  1. 注意:

-- 方法的返回值應該是 int,表示更新語句所影響的行數

-- 在呼叫的地方必須加事務,沒有事務不能正常執行

 

事務:

  1. Spring Data 提供了預設的事務處理方式,即所有的查詢均宣告為只讀事務。

  2. 對於自定義的方法,如需改變 Spring Data 提供的事務預設方式,可以在方法上註解 @Transactional 宣告

  3. 進行多個 Repository 操作時,也應該使它們在同一個事務中處理,按照分層架構的思想,這部分屬於業務邏輯層,因此,需要在 Service 層實現對多個 Repository 的呼叫,並在相應的方法上宣告事務。

 

 

CrudRepository介面

CrudRepository 介面提供了最基本的對實體類的添刪改查操作 :

  1. T save(T entity);//儲存單個實體 

  2. Iterable<T> save(Iterable<? extends T> entities);//儲存集合       

  3. T findOne(ID id);//根據id查詢實體        

  4. boolean exists(ID id);//根據id判斷實體是否存在        

  5. Iterable<T> findAll();//查詢所有實體,不用或慎用!        

  6. long count();//查詢實體數量        

  7. void delete(ID id);//根據Id刪除實體        

  8. void delete(T entity);//刪除一個實體 

  9. void delete(Iterable<? extends T> entities);//刪除一個實體的集合        

  10. void deleteAll();//刪除所有實體,不用或慎用! 

 

PagingAndSortingRepository介面

該介面提供了分頁與排序功能:【不提供條件分頁查詢】

  1. Iterable<T> findAll(Sort sort); //排序

  2. Page<T> findAll(Pageable pageable); //分頁查詢(含排序功能)

@Test

    public void testPagingAndSortingRespository(){

        //pageNo 從 0 開始.

        int pageNo = 6 - 1;

        int pageSize = 5;

        //Pageable 介面通常使用的其 PageRequest 實現類. 其中封裝了需要分頁的資訊

        //排序相關的. Sort 封裝了排序的資訊

        //Order 是具體針對於某一個屬性進行升序還是降序.

        Order order1 = new Order(Direction.DESC, "id");

        Order order2 = new Order(Direction.ASC, "email");

        Sort sort = new Sort(order1, order2);

        

        PageRequest pageable = new PageRequest(pageNo, pageSize, sort);

        Page<Person> page = personRepsotory.findAll(pageable);

        

        System.out.println("總記錄數: " + page.getTotalElements());

        System.out.println("當前第幾頁: " + (page.getNumber() + 1));

        System.out.println("總頁數: " + page.getTotalPages());

        System.out.println("當前頁面的 List: " + page.getContent());

        System.out.println("當前頁面的記錄數: " + page.getNumberOfElements());

    }

 

JpaRepository介面

 

該介面提供了JPA的相關功能:

  1. List<T> findAll(); //查詢所有實體

  2. List<T> findAll(Sort sort); //排序、查詢所有實體

  3. List<T> save(Iterable<? extends T> entities);//儲存集合

  4. void flush();//執行快取與資料庫同步

  5. T saveAndFlush(T entity);//強制執行持久化【類似於jpa的merge】

  6. void deleteInBatch(Iterable<T> entities);//刪除一個實體集合

 

JpaSpecificationExecutor介面

不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法 :

  1. findOne(Specification<T>) // 返回一個匹配給定的結果

  2. findAll(Specification<T>) //返回所有匹配給定的結果

  3. findAll(Specification<T>, Pageable) // 返回帶分頁的所有匹配給定的結果

  4. findAll(Specification<T>, Sort) //返回帶排序的所有匹配給定的結果

  5. count(Specification<T>) //返回總數

 

Specification:封裝  JPA Criteria 查詢條件。通常使用匿名內部類的方式來建立該介面的物件  

  /**

     * 目標: 實現帶查詢條件的分頁. id > 5 的條件

     *

     * 呼叫 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> spec, Pageable pageable);

     * Specification: 封裝了 JPA Criteria 查詢的查詢條件

     * Pageable: 封裝了請求分頁的資訊: 例如 pageNo, pageSize, Sort

     */

    @Test

    public void testJpaSpecificationExecutor(){

        int pageNo = 3 - 1;

        int pageSize = 5;

        PageRequest pageable = new PageRequest(pageNo, pageSize);

        

        //通常使用 Specification 的匿名內部類

        Specification<Person> specification = new Specification<Person>() {

            /**

             * @param *root: 代表查詢的實體類.

             * @param query: 可以從中可到 Root 物件, 即告知 JPA Criteria 查詢要查詢哪一個實體類. 還可以

             * 來新增查詢條件, 還可以結合 EntityManager 物件得到最終查詢的 TypedQuery 物件.

             * @param *cb: CriteriaBuilder 物件. 用於建立 Criteria 相關物件的工廠. 當然可以從中獲取到 Predicate 物件

             * @return: *Predicate 型別, 代表一個查詢條件.

             */

            @Override

            public Predicate toPredicate(Root<Person> root,

                    CriteriaQuery<?> query, CriteriaBuilder cb) {

                Path path = root.get("id");  // 通過實體類找到對應屬性的路徑【可以進行級聯查詢】

                Predicate predicate = cb.gt(path, 5);

                return predicate;

            }

        };

        

        Page<Person> page = personRepsotory.findAll(specification, pageable);

        

        System.out.println("總記錄數: " + page.getTotalElements());

        System.out.println("當前第幾頁: " + (page.getNumber() + 1));

        System.out.println("總頁數: " + page.getTotalPages());

        System.out.println("當前頁面的 List: " + page.getContent());

        System.out.println("當前頁面的記錄數: " + page.getNumberOfElements());

    }

自定義 Repository 方法

為某一個 Repository 上新增自定義方法:

  1. 定義一個介面: 宣告要新增的, 並自實現的方法

  2. 提供該介面的實現類: 類名需在要宣告的 Repository 後新增 Impl, 並實現方法

  3. 宣告 Repository 介面, 並繼承 1) 宣告的介面

  4. 使用.

  5. 注意: 預設情況下, Spring Data 會在 base-package 中查詢 "介面名Impl" 作為實現類. 也可以通過 repository-impl-postfix 聲明後綴.

 

 

 

為所有的 Repository 都新增自實現的方法:

  1. 宣告一個介面, 在該介面中宣告需要自定義的方法, 且該介面需要繼承 Spring Data 的 Repository.

  2. 提供 1) 所宣告的介面的實現類. 且繼承 SimpleJpaRepository, 並提供方法的實現

  3. 定義 JpaRepositoryFactoryBean 的實現類, 使其生成 1) 定義的介面實現類的物件

  4. 修改 <jpa:repositories /> 節點的 factory-class 屬性指向 3) 的全類名

  5. 注意: 全域性的擴充套件實現類不要用 Imp 作為字尾名, 或為全域性擴充套件介面新增 @NoRepositoryBean 註解告知  Spring Data: Spring Data 不把其作為 Repository