面試官:@Transactional(readOnly=true) 有什麼用?還有誰不會?!

来源:https://www.cnblogs.com/javastack/archive/2023/11/20/17843364.html
-Advertisement-
Play Games

原文翻譯自:https://medium.com 今天,我想談談 Spring 提供的@Transactional(readOnly = true)。 之所以聊這個是因為我公司項目的代碼里有很多@Transactional(readOnly = true),用過的同學都說@Transactional ...


原文翻譯自:https://medium.com

今天,我想談談 Spring 提供的@Transactional(readOnly = true)

之所以聊這個是因為我公司項目的代碼里有很多@Transactional(readOnly = true),用過的同學都說@Transactional(readOnly = true)提高了性能。先思考以下幾點:

  • @Transactional(readOnly = true)是如何工作的,為什麼使用它可以提高性能?
  • 當我們使用 JPA 時,是否應該總是將@Transactional(readOnly = true)添加到服務層的只讀方法?有什麼取捨嗎?

在開始之前,我們使用 Hibernate 來實現 JPA。

推薦一個開源免費的 Spring Boot 實戰項目:

https://github.com/javastacks/spring-boot-best-practice

1、@Transactional(readOnly = true)是如何工作的,為什麼使用它可以提高性能?

首先,讓我們看一下事務介面。

/**
* A boolean flag that can be set to {@code true} if the transaction is
* effectively read-only, allowing for corresponding optimizations at runtime.
* <p>Defaults to {@code false}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction
* but rather silently ignore the hint.
* @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
* @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
*/
boolean readOnly() default false;

我們可以看到 readOnly = true 選項允許優化。事務管理器將使用只讀選項作為提示。讓我們看看用於事務管理器的JpaTransactionManager

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
 JpaTransactionObject txObject = (JpaTransactionObject) transaction;
  // .
  // Delegate to JpaDialect for actual transaction begin.
  int timeoutToUse = determineTimeout(definition);
  Object transactionData = getJpaDialect().beginTransaction(em,
    new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder()));
  //...
}

JpaTransactionManager中,doBegin方法委托JpaDialect來開始實際的事務,併在JpaDialect中調用beginTransaction。讓我們來看看HibernateJpaDialect類。

@Override
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
  throws PersistenceException, SQLException, TransactionException {
   // ...
   // Adapt flush mode and store previous isolation level, if any.
   FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly());
   if (definition instanceof ResourceTransactionDefinition &&
     ((ResourceTransactionDefinition) definition).isLocalResource()) {
    // As of 5.1, we explicitly optimize for a transaction-local EntityManager,
    // aligned with native HibernateTransactionManager behavior.
    previousFlushMode = null;
    if (definition.isReadOnly()) {
     session.setDefaultReadOnly(true);
    }
   }
   // ...
}

protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws PersistenceException {
    FlushMode flushMode = session.getHibernateFlushMode();
    if (readOnly) {
     // We should suppress flushing for a read-only transaction.
     if (!flushMode.equals(FlushMode.MANUAL)) {
      session.setHibernateFlushMode(Flusode.MANUAL);
      return flushMode;
     }
    }
    else {
     // We need AUTO or COMMIT for a non-read-only transaction.
     if (flushMode.lessThan(FlushMode.COMMIT)) {
      session.setHibernateFlushMode(FlushMode.AUTO);
      return flushMode;
     }
    }
    // No FlushMode change needed...
    return null;
}

在JpaDialect中,我們可以看到JpaDialect使用只讀選項準備刷新模式。當 readOnly = true 時, JpaDialect 禁止刷新。此外,您還可以看到,在準備刷新模式後,session.setDefaultReadOnly(true)將session的readOnly屬性設置為true。

/**
 * Change the default for entities and proxies loaded into this session
 * from modifiable to read-only mode, or from modifiable to read-only mode.
 *
 * Read-only entities are not dirty-checked and snapshots of persistent
 * state are not maintained. Read-only entities can be modified, but
 * changes are not persisted.
 *
 * When a proxy is initialized, the loaded entity will have the same
 * read-only/modifiable setting as the uninitialized
 * proxy has, regardless of the session's current setting.
 *
 * To change the read-only/modifiable setting for a particular entity
 * or proxy that is already in this session:
 * @see Session#setReadOnly(Object,boolean)
 *
 * To override this session's read-only/modifiable setting for entities
 * and proxies loaded by a Query:
 * @see Query#setReadOnly(boolean)
 *
 * @param readOnly true, the default for loaded entities/proxies is read-only;
 *                 false, the default for loaded entities/proxies is modifiable
 */
void setDefaultReadOnly(boolean readOnly);

在Session介面中,通過將readOnly屬性設置為true,將不會對只讀實體進行臟檢查,也不會維護持久狀態的快照。此外,只讀實體的更改也不會持久化。

總而言之,這些是在 Hibernate 中使用@Transactional(readOnly = true)所得到的結果

  • 性能改進:只讀實體不進行臟檢查
  • 節省記憶體:不維護持久狀態的快照
  • 數據一致性:只讀實體的更改不會持久化
  • 當我們使用主從或讀寫副本集(或集群)時,@Transactional(readOnly = true)使我們能夠連接到只讀資料庫

2、當我們使用 JPA 時,是否應該總是將@Transactional(readOnly = true)添加到服務層的只讀方法?有什麼取捨嗎?

我看到,當使用@Transactional(readOnly = true)時,我們可以有很多優勢。但是,將@Transactional(readOnly = true)添加到服務層的只讀方法是否合適?以下是我擔心的事情

  1. 無限制地使用事務可能會導致資料庫死鎖、性能和吞吐量下降。
  2. 由於一個事務占用一個DB連接,所以@Transactional(readOnly = true)添加到Service層的方法可能會導致DB連接饑餓。

推薦一個開源免費的 Spring Boot 實戰項目:

https://github.com/javastacks/spring-boot-best-practice

第一個問題很難重現,所以我做了一些測試來檢查第二個問題。

@Transactional(readOnly = true)
public List<UserDto> transactionalReadOnlyOnService(){
    List<UserDto> userDtos = userRepository.findAll().stream()
            .map(userMapper::toDto)
            .toList();
    timeSleepAndPrintConnection();
    return userDtos;
}

public List<UserDto> transactionalReadOnlyOnRepository(){
    List<UserDto> userDtos = userRepository.findAll().stream()
            .map(userMapper::toDto)
            .toList();
    timeSleepAndPrintConnection();
    return userDtos;
}

我在服務層測試了兩個方法,一個是@Transactional(readOnly = true),另一個是存儲庫層中的@Transactional (readOnly = true)(在 SimpleJpaRepository 中,它是 Jpa Respitory 的預設實現,在類的頂部有@Transformational(ready Only),因此 findAll()方法在預設情況下有@transactional(read only = True))。

我從DB中獲取userInfo並保持線程5秒鐘,然後檢查該方法何時釋放連接。

結果如下:

對於服務層方法中的@Transactional(readOnly = true)

activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnService!!
Hibernate: 
    select
        u1_0.id,
        u1_0.email,
        u1_0.name,
        u1_0.profile_file_name 
    from
        users u1_0
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
end transactionalReadOnlyOnService!!
activeConnections:0, IdleConnections:10, TotalConnections:10

對於存儲庫層方法中的@Transactional(readOnly = true)

activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnRepository!!
Hibernate: 
    select
        u1_0.id,
        u1_0.email,
        u1_0.name,
        u1_0.profile_file_name 
    from
        users u1_0
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
end transactionalReadOnlyOnRepository!!
activeConnections:0, IdleConnections:10, TotalConnections:10

正如您所看到的,@Transactional(readOnly = true)一旦查詢結果到達,存儲庫層就會釋放連接。

然而,@Transactional(readOnly = true)在服務層的方法中直到服務層的方法結束才釋放連接。

因此,當服務層的方法有需要大量時間的邏輯時要小心,因為它可以長時間持有資料庫連接,這可能會導致資料庫連接匱乏。

3、回顧

很明顯,@Transactional(readOnly = true)有很多優點。

  • 性能改進:只讀實體不進行臟檢查
  • 節省記憶體:不維護持久狀態的快照
  • 數據一致性:只讀實體的更改不會持久化
  • 當我們使用主從或讀寫副本集(或集群)時,@Transactional(readOnly = true)使我們能夠連接到只讀資料庫

但是,您還應該記住,@Transactional(readOnly = true)在服務層的方法中可能會導致資料庫死鎖、性能低下和資料庫連接匱乏!

當您需要將只讀查詢僅僅作為一個事務執行時,請毫不猶豫選擇的在服務層的方法中使用@Transactional(readOnly = true),如果你的服務層的方法中有大量其他邏輯方法時,就要做取捨了!

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


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

-Advertisement-
Play Games
更多相關文章
  • 近年來,車輛保險成為廣大車主必須購買的一項重要保障。然而,如何查詢車輛保險狀態及保單信息卻是許多車主面臨的難題。隨著技術的不斷發展,API的出現為我們提供了一條便捷的解決之路。本文介紹的《車輛保險查詢API——查詢車輛保險狀態及保單信息》便是一款實用的API工具。 一、API的介紹 挖數據平臺車輛保 ...
  • hello,大家好!新手小白踏入 Python 的大門有點像冒險,但別擔心,我已經整理了一個超實用的入門指南,幫你規避學習過程中的十大雷區。這裡有關於 Python 的錯誤你應該註意的建議,一起來看看吧! 1. 拼寫錯誤 小心 prin 和 print 的奇妙之旅! # 錯誤示例 prin("Hel ...
  • 通俗解釋:單例模式 > 單:唯一 > > 例:實例 > > 單例設計模式,即某個類在整個系統中只能有一個實例對象可被獲取和使用的代碼模式 > > 例如:代表JVM運行環境的Runtime類 ...
  • 接上一隨筆,這次學習針對圖像數據的訪問(Numpy.array) 在OpenCV中,使用 imread() 方法可以訪問圖像,其返回值是一個數組,而根據傳入的不同圖像,將會返回不同維度的數組。 針對返回的圖像數據,即數組,我們是可以進行操作的: 1 import cv2 2 3 # MyPic.pn ...
  • 在開發過程中,如果需要在本地調用openAI介面進行開發調試,一般主要是通過以下兩種方式:直連和代理轉發。歡迎私信交流。 1. 直連 1.簡單粗暴,懂的都懂 2. 代理轉發 代理轉發又有兩種類型,使用第三方代理和自建代理兩種,下麵將分別舉例說明 2.1. 第三方AI網關 1.註冊Cloudflare ...
  • 在平時的開發過程中,整數越界是一個容易被忽視的問題,關註潛在的整數越界問題可使我們編寫的代碼更加健壯,規避因整數越界導致的 bug。 ...
  • 對PDF頁面的增刪通常需要藉助專門的工具,而這些工具一般需要付費才能使用。那麼我們可以通過Java代碼免費實現這一功能嗎?答案是肯定的。這篇文章就教大家如何使用一個免費的國產Java庫來刪除PDF中的指定頁面或者刪除PDF中的空白頁。 使用Java快速刪除PDF中的指定頁面 1. 首先,我們需要先將 ...
  • 大家好,我是 Java陳序員。 我們在工作中,經常需要與文件上傳下載進行打交道。甚至有時候要實現文件預覽功能。 如果是一兩種的文件類型,我們或許可以藉助一些插件完成工作,那麼如果是要適配各式各樣的文件類型呢? 今天,給大家介紹一個支持預覽多種文件類型的開源項目 —— kkFileView. 項目介紹 ...
一周排行
    -Advertisement-
    Play Games
  • 一個自定義WPF窗體的解決方案,借鑒了呂毅老師的WPF製作高性能的透明背景的異形視窗一文,併在此基礎上增加了滑鼠穿透的功能。可以使得透明窗體的滑鼠事件穿透到下層,在下層窗體中響應。 ...
  • 在C#中使用RabbitMQ做個簡單的發送郵件小項目 前言 好久沒有做項目了,這次做一個發送郵件的小項目。發郵件是一個比較耗時的操作,之前在我的個人博客裡面回覆評論和友鏈申請是會通過發送郵件來通知對方的,不過當時只是簡單的進行了非同步操作。 那麼這次來使用RabbitMQ去統一發送郵件,我的想法是通過 ...
  • 當你使用Edge等瀏覽器或系統軟體播放媒體時,Windows控制中心就會出現相應的媒體信息以及控制播放的功能,如圖。 SMTC (SystemMediaTransportControls) 是一個Windows App SDK (舊為UWP) 中提供的一個API,用於與系統媒體交互。接入SMTC的好 ...
  • 最近在微軟商店,官方上架了新款Win11風格的WPF版UI框架【WPF Gallery Preview 1.0.0.0】,這款應用引入了前沿的Fluent Design UI設計,為用戶帶來全新的視覺體驗。 ...
  • 1.簡單使用實例 1.1 添加log4net.dll的引用。 在NuGet程式包中搜索log4net並添加,此次我所用版本為2.0.17。如下圖: 1.2 添加配置文件 右鍵項目,添加新建項,搜索選擇應用程式配置文件,命名為log4net.config,步驟如下圖: 1.2.1 log4net.co ...
  • 之前也分享過 Swashbuckle.AspNetCore 的使用,不過版本比較老了,本次演示用的示例版本為 .net core 8.0,從安裝使用開始,到根據命名空間分組顯示,十分的有用 ...
  • 在 Visual Studio 中,至少可以創建三種不同類型的類庫: 類庫(.NET Framework) 類庫(.NET 標準) 類庫 (.NET Core) 雖然第一種是我們多年來一直在使用的,但一直感到困惑的一個主要問題是何時使用 .NET Standard 和 .NET Core 類庫類型。 ...
  • WPF的按鈕提供了Template模板,可以通過修改Template模板中的內容對按鈕的樣式進行自定義。結合資源字典,可以將自定義資源在xaml視窗、自定義控制項或者整個App當中調用 ...
  • 實現了一個支持長短按得按鈕組件,單擊可以觸發Click事件,長按可以觸發LongPressed事件,長按鬆開時觸發LongClick事件。還可以和自定義外觀相結合,實現自定義的按鈕外形。 ...
  • 一、WTM是什麼 WalkingTec.Mvvm框架(簡稱WTM)最早開發與2013年,基於Asp.net MVC3 和 最早的Entity Framework, 當初主要是為瞭解決公司內部開發效率低,代碼風格不統一的問題。2017年9月,將代碼移植到了.Net Core上,併進行了深度優化和重構, ...