JPA中自定義的插入、更新、刪除方法為什麼要添加@Modifying註解和@Transactional註解?

来源:https://www.cnblogs.com/wuhenzhidu/archive/2019/04/16/jpa.html
-Advertisement-
Play Games

前幾天,有個同事在使用JPA的自定義SQL方法時,程式一直報異常,搗鼓了半天也沒能解決,咨詢我的時候,我看了一眼他的程式,差不多是這個樣子的: 我告訴他,你的deleteUserById方法缺少了@Modifying註解和@Transactional註解,他半信半疑地試了一下,然後果然就解決了。其實 ...


  前幾天,有個同事在使用JPA的自定義SQL方法時,程式一直報異常,搗鼓了半天也沒能解決,咨詢我的時候,我看了一眼他的程式,差不多是這個樣子的:

1 @Repository
2 public interface UserRepository extends JpaRepository<User,Long> {
3 
4     @Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
5     void deleteUserById(Long id);
6 }

  我告訴他,你的deleteUserById方法缺少了@Modifying註解和@Transactional註解,他半信半疑地試了一下,然後果然就解決了。其實,如果他查一下官方資料或許很快也就能找到答案。基於這個背景,本文詳細講解一下為何我們自定義的插入、更新、刪除操作需要加@Modifying註解和@Transactional註解。

一、@Modifying註解

  在官方資料中,給出了這樣幾句說明:

As the queries themselves are tied to the Java method that executes them, you can actually bind them directly by using the Spring Data JPA @Query annotation 
rather than annotating them to the domain class.
You can modify queries that only need parameter binding by annotating the query method with @Modifying

The @Modifying annotation is only relevant in combination with the @Query annotation. Derived query methods or custom methods do not require this Annotation.

Doing so triggers the query annotated to the method as an updating query instead of a selecting one.

  如下:

@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);

  第一句話的意思是可以用@Query註解來將自定義sql語句綁定到自定義方法上。

  第二句話的意思時,可以用@Modifying註解來標註只需要綁定參數的自定義的更新類語句(更新、插入、刪除)。

  第三名話的意思是說@Modifying只與@Query聯合使用,派生類的查詢方法和自定義的方法不需要此註解,如:

 1 @Repository
 2 public interface UserRepository extends JpaRepository<User,Long> {
 3 
 4     // 父類的保存方法
 5     @Override
 6     User save(User entity); 
 7 
 8     // 按照JPA語法規則自定義的查詢方法
 9     List<User> findFirst10ByLastname(String lastName, Pageable pageable);  
10 }

  第四句話的意思是,當加上@Modifying註解時,JPA會以更新類語句來執行,而不再是以查詢語句執行。  

  也就是說,當我們要通過自已寫的更新、插入、刪除SQL語句來實現更新、插入、刪除操作時,至少需要用兩個步驟:

  1)@Query來註入我們自定義的sql;

  2)使用@Modifying來標註是一個更新類的自定義語句。

  按照這個規則,修改同事的那個方法:

1  @Repository
2  public interface UserRepository extends JpaRepository<User,Long> {
3  
4      @Modifying
5      @Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
6      void deleteUserById(Long id);
7  }

  但是,此時,該方法還不完整,執行時程式會報以下錯誤:

org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: 
Executing an update/delete query at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:402) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255) ...... at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: javax.persistence.TransactionRequiredException: Executing an update/delete query at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:398) at org.hibernate.query.internal.AbstractProducedQuery.executeUpdate(AbstractProducedQuery.java:1585) .......

二、@Transactional註解

  官方的說明:

  By default, CRUD methods on repository instances are transactional. For read operations, the transaction configuration readOnly flag is set to true. All others are configured with a plain @Transactional so that default transaction configuration applies. For details, see JavaDoc of SimpleJpaRepository. If you need to tweak transaction configuration for one of the methods declared in a repository, redeclare the method in your repository interface, as follows:

  Example. Custom transaction configuration for CRUD

1 public interface UserRepository extends CrudRepository<User, Long> {
2 
3   @Override
4   @Transactional(timeout = 10)
5   public List<User> findAll();
6 
7   // Further query method declarations
8 }

  這句話的意思是,預設情況下,repository 介面中的CRUD方法都是被@Transactional註解修飾了的,對於讀的操作方法,@Transactional註解的readOnly屬性是被設置為true的,即只讀;CRUD中的其他方法被@Transactional修飾,即非只讀。如果你需要修改repository 介面中的某些方法的事務屬性,可以在該方法上重新加上@Transactional註解,並設置需要的屬性。

  我們先來看一下,@Transactional註解的源碼:

 1 @Target({ElementType.METHOD, ElementType.TYPE})
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Inherited
 4 @Documented
 5 public @interface Transactional {
 6 
 7     Propagation propagation() default Propagation.REQUIRED;
 8 
 9     Isolation isolation() default Isolation.DEFAULT;
10 
11     int timeout() default -1;
12 
13     boolean readOnly() default false;
14   
15     // 其他省略
16 }

  由上可見@Transactional註解的readOnly預設的屬性的false,即非只讀,當一個事務是非只讀事務的時候,我們可以進行任何操作。

  再看一下repository 介面的實現類SimpleJpaRepository的源碼(只摘了部分源碼):

 1 @Repository
 2 @Transactional(
 3     readOnly = true
 4 )
 5 public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
 6    
 7     @Transactional
 8     public void deleteById(ID id) {
 9         Assert.notNull(id, "The given id must not be null!");
10         this.delete(this.findById(id).orElseThrow(() -> {
11             return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
12         }));
13     }
14 
15     @Transactional
16     public void delete(T entity) {
17         Assert.notNull(entity, "The entity must not be null!");
18         this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity));
19     }
20 
21     @Transactional
22     public void deleteAll(Iterable<? extends T> entities) {
23         Assert.notNull(entities, "The given Iterable of entities not be null!");
24         Iterator var2 = entities.iterator();
25 
26         while(var2.hasNext()) {
27             T entity = var2.next();
28             this.delete(entity);
29         }
30     }
31 
32     public T getOne(ID id) {
33         Assert.notNull(id, "The given id must not be null!");
34         return this.em.getReference(this.getDomainClass(), id);
35     }
36 
37     public List<T> findAll() {
38         return this.getQuery((Specification)null, (Sort)Sort.unsorted()).getResultList();
39     }
40 
41     public List<T> findAll(@Nullable Specification<T> spec) {
42         return this.getQuery(spec, Sort.unsorted()).getResultList();
43     }
44 
45     public List<T> findAll(@Nullable Specification<T> spec, Sort sort) {
46         return this.getQuery(spec, sort).getResultList();
47     }
48 
49     public <S extends T> long count(Example<S> example) {
50         return executeCountQuery(this.getCountQuery(new SimpleJpaRepository.ExampleSpecification(example), example.getProbeType()));
51     }
52 
53     public <S extends T> boolean exists(Example<S> example) {
54         return !this.getQuery(new SimpleJpaRepository.ExampleSpecification(example), example.getProbeType(), (Sort)Sort.unsorted()).getResultList().isEmpty();
55     }
56 
57     @Transactional
58     public <S extends T> S save(S entity) {
59         if (this.entityInformation.isNew(entity)) {
60             this.em.persist(entity);
61             return entity;
62         } else {
63             return this.em.merge(entity);
64         }
65     }
66 
67     @Transactional
68     public <S extends T> S saveAndFlush(S entity) {
69         S result = this.save(entity);
70         this.flush();
71         return result;
72     }
73 
74     @Transactional
75     public void flush() {
76         this.em.flush();
77     }
78 }

  從SimpleJpaRepository源碼中可以看出:

    1)該類上註解了只讀事務@Transactional(readOnly = true);

       2)該類的所有查詢類操作方法都與類相同,都擁有隻讀事務;

       3)該類的所有保存、更新、刪除操作方法都用@Transactional重新註解了(預設readOnly=false)。

  說明JPA為我們提供的所有方法,包括JPA規則的自定義方法在其底層都為我們做好了事務處理,而我們自定義的方法需要自己來標註事務的類型是只讀還是非只讀。根據這個原理,再次修改開篇所列出的方法:

1 @Repository
2 public interface UserRepository extends JpaRepository<User,Long> {
3 
4     @Transactional
5     @Modifying
6     @Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
7     void deleteUserById(Long id);
8 }

  至此,該方法按所期望的結果運行成功了。

三、@Modifying註解補充說明

1 @Retention(RetentionPolicy.RUNTIME)
2 @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
3 @Documented
4 public @interface Modifying {
5 
6     boolean flushAutomatically() default false;
7 
8     boolean clearAutomatically() default false;
9 }

  該註解中有兩個屬性:flushAutomatically、clearAutomatically,從字面理解是自動刷新和自動清除。

  自動刷新,即執行完語句後立即將變化內容刷新到磁碟,如果是insert語句操作,則與JPA的<S extends T> S saveAndFlush(S entity);方法效果相同;

  自動清除,即執行完語句後自動清除掉已經過期的實體,比如,我們刪除了一個實體,但是在還沒有執行flush操作時,這個實體還存在於實體管理器EntityManager中,但這個實體已經過期沒有任何用處,直到flush操作時才會被刪除掉。如果希望在刪除該實體時立即將該實體從實體管理器中刪除,則可以將該屬性設置為true,如:

1 @Modifying(clearAutomatically = true)
2     @Transactional
3     @Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
4     void deleteUserById(Long id);

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • What 本篇應該是穩定性「三十六計」系列的一篇:超時重試。但是「設置預設的超時和重試是一個基礎設施的基本素養」這句話我在我們組內三次開會的時候都說了。表達了我的一個理念。 Why 為什麼一個基礎設施要設置預設的超時和重試?想象下麵一個場景。 TCP協議里有一些基本的概念:MSL、TTL、RTT。 ...
  • 第一次寫文章 很粗略 請多多指教 有什麼疑問或者問題歡迎發郵件給我 [email protected] 鏈接:http://note.youdao.com/noteshare?id=ea9b9d16c549d6ddc827dd2e70c185b1&sub=B5DEED2633F242309ECEA96 ...
  • [TOC] 介紹 初學Java虛擬機幾天, 被方法區, 永久代這些混雜的概念搞混了. 我覺得學習這部分知識應該把官方定義的虛擬機運行時數據區域和虛擬機記憶體結構分開敘述, 要不然容易誤導. 本文先介紹官方文檔規定的運行時數據區域, 然後以JDK1.8的HotSpot虛擬機為例, 介紹虛擬機的記憶體結構. ...
  • php概述 什麼是php,PHP語言的優勢,PHP5的新特性,PHP的發展趨勢,PHP的應用領域。 PHP是超文本預處理器,是一種伺服器端,跨平臺,HTML嵌入式的腳本語言,具有c語言,Java語言,和Perl語言的特點,是一種被廣泛應用的開源式的多用途腳本語言,適合web開發。 PHP是b/s體系 ...
  • 1 數組也是一種類型 Java中要求所有的數組元素具有相同的數據類型。因此在一個數組中,數組元素的類型是唯一的,不能存儲多種類型的數據。 一旦數組的初始化完成,數組在記憶體中所占的空間將被固定下來,因此數組的長度不可以被改變。即使某個數組元素的數據被清空,他占的空間依然被保留,依然屬於該數組,數組的長 ...
  • 一、概述 當我們打開一個SqlSession的時候,我們就完成了操作資料庫的第一步,那MyBatis是如何執行Sql的呢?其實MyBatis的增刪改查都是通過Executor執行的,Executor和SqlSession綁定在一起,由Configuration類的newExecutor方法創建。 二 ...
  • 在很多時候,我們代碼中會有很多分支,而且分支下麵的代碼又有一些複雜的邏輯,相信很多人都喜歡用 if-else/switch-case 去實現。做的不好的會直接把實現的代碼放在 if-else/switch-case 的分支之下: 這樣的代碼不僅冗長,讀起來也非常困難。做的好一點的會把這些邏輯封裝成函 ...
  • 本文首發於公眾號:javaadu 簡單介紹 構建高性能的Java應用過程中,必然會遇到各種各樣的問題,像CPU飆高、記憶體泄漏、應用奔潰,以及其他疑難雜症,這時可以使用Serviceability Agent(SA)。SA是JDK提供的一個強大的調試工具集,適用於語言層和虛擬機層,支持調試運行著的Ja ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...