面試官:@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
  • 當使用Autofac處理一個介面有多個實現的情況時,通常會使用鍵(key)進行區分或者通過IIndex索引註入,也可以通過IEnumerable集合獲取所有實例,以下是一個具體的例子,演示如何在Autofac中註冊多個實現,並通過構造函數註入獲取指定實現。 首先,確保你已經安裝了Autofac Nu ...
  • 本篇將分享Prometheus+Grafana的監控平臺搭建,並監控之前文章所搭建的主機&服務,分享日常使用的一些使用經驗本篇將配置常用服務的監控與面板配置:包括 MySQL,MongoDB,CLickHouse,Redis,RabbitMQ,Linux,Windows,Nginx,站點訪問監控,已... ...
  • 使用Aspirate可以將Aspire程式部署到Kubernetes 集群 工具安裝 dotnet tool install -g aspirate --prerelease 註意:Aspirate 正在開發中,該軟體包將作為預覽版進行版本控制,--prelease 選項將獲得最新的預覽版。 容器註 ...
  • 前言 本文要說的這種開發模式,這種模式並不是只有blazor支持,js中有一樣的方案next.js nuxt.js;blazor還有很多其它內容,本文近關註漸進式開發模式。 是的,前後端是主流,不過以下情況也許前後端分離並不是最好的選擇: 小公司,人員不多,利潤不高,創業階段能省則省 個人開發者,接 ...
  • 在.NET中,Microsoft.Extensions.Logging是一個靈活的日誌庫,它允許你將日誌信息記錄到各種不同的目標,包括資料庫。在這個示例中,我將詳細介紹如何使用Microsoft.Extensions.Logging將日誌保存到MySQL資料庫。我們將使用Entity Framewo ...
  • chatgpt介面開發筆記3: 語音識別介面 1.文本轉語音 1、瞭解介面參數 介面地址: POST https://api.openai.com/v1/audio/speech 下麵是介面文檔描述內容: 參數: { "model": "tts-1", "input": "你好,我是饒坤,我是ter ...
  • 前面兩篇文章主要是介紹瞭如何解決高併發情況下資源爭奪的問題。但是現實的應用場景中除了要解決資源爭奪問題,高併發的情況還需要解決更多問題,比如快速處理業務數據等, 本篇文章簡要羅列一下與之相關的更多技術細節。 1、非同步編程:使用async和await關鍵字進行非同步編程,這可以避免阻塞線程,提高程式的響 ...
  • 大家好,我是棧長。 Nacos 2.3.0 前幾天正式發佈了,新增了不少實用性的新功能,真是史上最強版本。 Nacos 2.3.0 還真是一個比較重要的大版本,因為它涉及了太多重大更新,今天棧長給大家來解讀下。 Nacos 先掃個盲: Nacos 一個用於構建雲原生應用的動態服務發現、配置管理和服務 ...
  • IDEA的遠程開發功能,可以將本地的編譯、構建、調試、運行等工作都放在遠程伺服器上執行,而本地僅運行客戶端軟體進行常規的開發操作即可,舊版本IDEA目前不支持該功能.,本例使用的是IDEA2023.2.5版本 下麵介紹如何在IDEA中設置遠程連接伺服器開發環境並結合Cpolar內網穿透工具實現無公網 ...
  • 本文解釋為啥會有響應式編程,為什麼它在開發者中不太受歡迎,以及引入 Java 虛擬線程後它可能最終會消失。 命令式風格編程一直深受開發者喜愛,如 if-then-else、while 迴圈、函數和代碼塊等結構使代碼易理解、調試,異常易追蹤。然而,像所有好的東西一樣,通常也有問題。這種編程風格導致線程 ...