學習Spring-Data-Jpa(十九)---JPA的持久性上下文


1、持久化上下文

  JPA中有持久化上下文存在,但是開發者不直接與之打交道,持久化上下文在應用程序中是透明的。

  我們可以把持久化上下文理解成一個Map,該Map在事務開始的時候創建,在事務結束的時候銷毀。在事務中,可以把對象關聯到持久化上下文中,比如說findById方法,在查出來的時候,這個對象就跟持久化上下文關聯起來了,可以理解成於放入Map中。


持久化上下文特性:
  ①、持久化上下文的生命周期與系統事務一致
  ②、持久化上下文提供自動臟檢查
  ③、持久化上下文是一級緩存

第①個比較好理解,第②條解釋如下:

  在事務提交的時候,JPA會執行一個臟檢查機制,會檢查持久化上下文中的對象狀態和數據庫中的狀態是否一致,如果不一致,就會根據持久化上下文中的狀態去更新數據庫中的狀態。但是這個動作只有在數據庫事務提交的時候在會做,如果事務回滾了,不會做這個動作。

  可以調用JpaRepository提供的flush或saveAndFlush方法立刻同步狀態到數據庫,而不是等到事務提交的時候在同步。需要注意的是,這里的立刻同步到數據庫是指將修改/刪除操作所執行的SQL語句先執行,此時事務並沒有提交,只有在事務提交后,這個更新/刪除才會起作用。

  可以通過下面的例子來理解:

  組織實體:

/**
 * 組織
 * @author caofanqi
 */
@Getter
@Setter
@Entity
@Table(name = "jpa_organization")
@NoArgsConstructor
@AllArgsConstructor
public class Organization extends AbstractID{

    private String code;

    private String name;

}

  數據庫中插入一條數據如下:

  

  測試用例1:

    /**
     * 觀察update語句和success打印的順序,沒有調用flush方法時,success在update語句之前打印,因為此時事務還沒有提交,沒有將修改同步到數據庫。
     * 我們可以使用flush方法,將修改立即同步到數據庫。
     */
    @Test
    void test1(){

        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

        Optional<Organization> organizationOp = organizationRepository.findById(1L);
        Organization organization = organizationOp.orElseThrow(() -> new RuntimeException("查詢不到數據"));
        organization.setName("xxxx");
//        organizationRepository.flush();
        System.out.println("success");

        //提交事務
        transactionManager.commit(status);
        //回滾事務
//        transactionManager.rollback(status);
    }

    沒有調用flush方法時控制台打印如下:

Hibernate: select organizati0_.id as id1_14_0_, organizati0_.code as code2_14_0_, organizati0_.name as name3_14_0_ from cfq_jpa_organization organizati0_ where organizati0_.id=?
success
Hibernate: update cfq_jpa_organization set code=?, name=? where id=?

  將數據庫還原為初始狀態,調用flush方法時控制台打印如下:

Hibernate: select organizati0_.id as id1_14_0_, organizati0_.code as code2_14_0_, organizati0_.name as name3_14_0_ from cfq_jpa_organization organizati0_ where organizati0_.id=?
Hibernate: update cfq_jpa_organization set code=?, name=? where id=?
success

 

  第③條,可以理解為:在事務中查詢時,首先會在持久化上下文中查找。只有在執行findById方法的時候,才會使用

  可以通過以下的例子來理解:

  OrganizationRepository添加如下方法:

    Optional<Organization> findOrganizationById(Long id);

    @Query(value = "select o from  Organization o where  o.id = :id ")
    Optional<Organization> selectById(Long id);

  測試用例2及執行結果打印SQL:

    /**
     * 兩次findById方法,只執行了一次SQL
     */
    @Test
    @Transactional
    void test2(){
        organizationRepository.findById(1L);
        organizationRepository.findById(1L);
    }

  測試用例3及執行結果打印SQL:

    /**
     * 使用方法派生查詢出數據,在使用findById方法查詢,只執行一次SQL
     */
    @Test
    @Transactional
    void test3(){
        organizationRepository.findOrganizationById(1L);
        organizationRepository.findById(1L);
    }

  測試用例4及執行結果打印SQL:

    /**
     * 使用@Query查詢出數據,在使用findById方法查詢,只執行一次SQL
     */
    @Test
    @Transactional
    void test4(){
        organizationRepository.selectById(1L);
        organizationRepository.findById(1L);
    }

  測試用例5及執行結果打印SQL:

    /**
     *  五句SQL
     */
    @Test
    @Transactional
    void test5(){
        organizationRepository.findById(1L);
        organizationRepository.selectById(1L);
        organizationRepository.selectById(1L);
        organizationRepository.findOrganizationById(1L);
        organizationRepository.findOrganizationById(1L);
    }

 

  需要注意的點:

  1、在事務中,對持久性上下文中的對象進行修改的話,再執行非findById查詢時,不調用flush方法也會立刻同步,而不是事務提交時在同步。執行findById查詢時,會先從持久化上下文中查找,找到了不再執行查詢SQL。

  測試用例6及執行結果打印SQL:

    /**
     * 對持久性上下文中的對象進行修改的話,再執行非findById查詢時,不調用flush方法也會立刻同步,而不是事務提交時在同步
     * select,update,select,打印xxxx 3句SQL
     */
    @Test
    @Rollback(false)
    @Transactional
    void test6(){
        Optional<Organization> organizationOp = organizationRepository.findOrganizationById(1L);
        Organization organization = organizationOp.orElseThrow(() -> new RuntimeException("查詢不到數據"));
        organization.setName("xxxx");
        organizationOp =  organizationRepository.findOrganizationById(1L);
        organization = organizationOp.orElseThrow(() -> new RuntimeException("查詢不到數據"));
        System.out.println(organization.getName());
    }

  測試用例7及執行結果打印SQL:

    /**
     * 對持久性上下文中的對象進行修改的話,執行findById查詢時,會先從持久化上下文中查找,找到了不再執行查詢SQL
     * select,打印xxxx ,update 2句SQL
     */
    @Test
    @Rollback(false)
    @Transactional
    void test7(){
        Optional<Organization> organizationOp = organizationRepository.findById(1L);
        Organization organization = organizationOp.orElseThrow(() -> new RuntimeException("查詢不到數據"));
        organization.setName("xxxx");
        organizationOp = organizationRepository.findById(1L);
        organization = organizationOp.orElseThrow(() -> new RuntimeException("查詢不到數據"));
        System.out.println(organization.getName());
    }

 

  2、@Query+@Modifying對數據的修改不會同步到持久化上下文中。

  如沒有設置clearAutomatically屬性,或clearAutomatically設置為false時,如果修改了持久化上下文中的對象,並再次使用findById方法查詢到持久化上下文中的對象時,會導致查到未修改的對象。

  OrganizationRepository新增方法:

    @Modifying
    @Query("update Organization  o set o.name = :name where o.id = :id")
    void  updateNameById1(Long id,String name);

  測試用例8及執行結果打印SQL:

    /**
     * 當@Modifying屬性clearAutomatically為false時,修改后不清空持久化上下文,
     * 使用findById查詢時,如果從持久化上下文中找到要查詢的對象,那么該對象狀態是未修改之前的。
     */
    @Test
    @Rollback(false)
    @Transactional
    void test8(){
        //向持久化上下文中存放對象
        Optional<Organization> organizationOp = organizationRepository.findById(1L);

        //如果使用@Query+@Modifying進行操作對象時,持久化上下文中的對象不會受到影響
        organizationRepository.updateNameById1(1L,"xxxx");

        //findById方法會取持久化上下文中的對象,(name沒有修改為xxxx的),打印的是組織001
        organizationOp = organizationRepository.findById(1L);
        Organization organization = organizationOp.orElseThrow(() -> new RuntimeException("查詢不到數據"));
        System.out.println(organization.getName());
    }

  因對上面的問題,我們可以設置clearAutomatically為true,修改后直接清空持久化上下文,這樣后面的findById方法,因為在持久化上下文中找不到數據,就會執行select進行查詢。

  OrganizationRepository新增方法:

    @Modifying(clearAutomatically = true)
    @Query("update Organization  o set o.name = :name where o.id = :id")
    void  updateNameById2(Long id,String name);

  測試用例9及執行結果打印SQL:

    /**
     * 當@Modifying屬性clearAutomatically為true時,修改后清空持久化上下文
     * 使用findById查詢時,因為持久化上下文被清空了,所以會再次執行select語句。
     */
    @Test
    @Rollback(false)
    @Transactional
    void test9(){
        //向持久化上下文中存放對象
        Optional<Organization> organizationOp = organizationRepository.findById(1L);

        //如果使用@Query+@Modifying進行操作對象時,持久化上下文中的對象不會受到影響,但設置了清空持久化上下文
        organizationRepository.updateNameById2(1L,"xxxx");

        //因為持久化上下文中是空的,所以findById會執行select語句,打印的是xxxx
        organizationOp = organizationRepository.findById(1L);
        Organization organization = organizationOp.orElseThrow(() -> new RuntimeException("查詢不到數據"));
        System.out.println(organization.getName());
    }

 

 

 

 源碼地址:https://github.com/caofanqi/study-spring-data-jpa


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM