自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

嵌套事務、掛起事務,Spring 是怎樣給事務又實現(xiàn)傳播特性的?

開發(fā) 后端
Spring 做為風靡世界的Java 開源框架,發(fā)揮著舉足輕重的作用。那你有沒有想過, Spring 內(nèi)部又是怎么樣實現(xiàn)的事務呢?

[[334020]]

Spring 做為風靡世界的Java 開源框架,發(fā)揮著舉足輕重的作用。那你有沒有想過, Spring 內(nèi)部又是怎么樣實現(xiàn)的事務呢?

而且 在 Spring 之中除了設置事務的「隔離級別」之外,還可以額外配置事務的「傳播特性」。你要知道,傳播特性里,有兩個家伙比較特別,一個PROPAGATION_REQUIRES_NEW ,還有一個是PROPAGATION_NESTED。你要知道,所謂的 REQUIRES_NEW,是會在方法級聯(lián)調(diào)用的時候,會開啟一個新的事務,同時掛起(suspend)當前事務;NESTED 代表的嵌套事務,則是在方法級聯(lián)調(diào)用的時候,在嵌套事務內(nèi)執(zhí)行。

我們應該都知道,這些傳播行為,是 Spring 獨有的,和數(shù)據(jù)庫沒有一毛錢關(guān)系。那在底層 Spring 用了什么黑魔法嗎?是怎么樣做到掛起事務的,又是怎么樣嵌套事務的呢?

咱們一起來揭秘。

毋庸置疑,數(shù)據(jù)的操作中,我們離不開事務。

銀行的轉(zhuǎn)帳操作中,我們相信如果一邊扣款,那對方一定會收到,而不竹籃打水一場空

事務在很多場景中都發(fā)揮著關(guān)鍵作用。

咱們先以 MySQL 為例,來捋一捋數(shù)據(jù)庫的事務,隔離級別。

然后再來看這些數(shù)據(jù)庫的配置,特性,在 Spring 里是怎樣做的事務對應的。

以及 Spring 所謂的傳播特性,又是如何作用到數(shù)據(jù)庫的事務的,怎樣做到掛起事務,嵌套事務。

事務

事務是什么?

它是一級原子性的SQL操作,一個獨立的工作單元。如果工作單元中的SQL語句執(zhí)行成功,那會全部成功,一個失敗就會全部回滾。

與數(shù)據(jù)庫的事務同時為大眾熟知的是ACID。即數(shù)據(jù)庫的原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。就像應用內(nèi)為了線程之間的互斥,線程安全等,需要做大量的工作,數(shù)據(jù)庫為了 ACID,也做了許多的工作。

隔離級別

SQL 的標準中定義了四種隔離級別,規(guī)定了哪些是事務之間可見的,哪些是事務內(nèi)可見的。

READ UNCOMMITTED (未提交讀) --> 兩個事務間,一個的事務還沒提交,但被另一個看到了。

READ COMMITTED (提交讀) --> 兩個事務間,只有一個事務提交之后,另一個事務才能看到。

REPEATABLE READ (可重復讀)--> 一個事務執(zhí)行中,多次讀到的結(jié)果是一樣的,即使其他事務做了修改,也先「看不見」

SERIALIZABLE (可串行化)--> 最高隔離級別,強制事務串行執(zhí)行。

由于這些隔離級別,會造成所謂的「臟讀」、「幻讀」、「不可重復讀」等問題,所以需要根據(jù)具體的場景選擇適用的。

如何設置隔離級別

對于 MySQL 的隔離級別,可以全局的,也可以設置 Session 級別的。

官方文檔設置語法如下:

 

我們通過客戶端連接到 MySQL Server 的時候,可以做一些配置,通過jdbc的 URL 你也能看的出來,這樣設置的就是 Session 級別的。

參考上面的官方文檔,設置 session 隔離級別的命令是它:

  1. set session transaction isolation level SERIALIZABLE

注意,MySQL 默認的隔離級別是 REPEATABLE READ,我的改過。同時,這里查詢當前隔離級別的SQL,舊版本的是SELECT @@TX_ISOLATION;,新版本的是SELECT @@Transaction_ISOLATION; 注意區(qū)分。

 

Spring 事務又是怎么回事

看過了數(shù)據(jù)庫的隔離級別之后,我們再來看 Spring 里的實現(xiàn)。

在 Spring 里,隔離級別的定義有五種,四個和數(shù)據(jù)庫的一致,另外包含一個 DEFAULT,代表不具體設置,直接使用數(shù)據(jù)庫定義的。

咱們看到數(shù)據(jù)庫的隔離級別可以設置 GLOBAL 級別的,也可以設置 SESSION 級別的,每個 SESSION 則代表的是一個連接。而在Spring 內(nèi),我們又是通過一個個獨立的「連接」來操作數(shù)據(jù)庫,完成CRUD。到這兒咱們應該就能知道,在 Spring 層面的設置,是通過 session 級別的 隔離來設置的,從而和數(shù)據(jù)庫里事務隔離級別做了對應來實現(xiàn)。

那傳播特性又是怎么回事呢?既然數(shù)據(jù)庫并不直接支持這樣的特性,Spring 又根據(jù)不同的傳播特性要求,來迂回實現(xiàn)了。

總結(jié)起來,對于級聯(lián)操作中,如果是REQUIRED_NEW這種的情況,所謂的掛起當前事務,開啟新的事務,是 Spring 又去申請了一個新的 Connection,這樣就會對應到一個新的事務上,然后將這個連接綁定到當前線程,再繼續(xù)執(zhí)行;

對于嵌套事務,底層則是通過數(shù)據(jù)庫的 SAVEPOINT 來實現(xiàn)所謂的「子事務」

 

如果你熟悉代碼 DEBUG 過程中每個方法對應的 Frame,可以類比一下,如果執(zhí)行失敗回滾的時候,可以指定回滾到當前事務的某個 SAVEPOINT,不需要全部回滾。

在 Spring 層面,是通過 JDBC 3.0來實現(xiàn)的,看下面這段代碼注釋。

  1. * <p>This transaction manager supports nested transactions via the JDBC 3.0 
  2. * {@link java.sql.Savepoint} mechanism. The 
  3. * {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults 
  4. to "true", since nested transactions will work without restrictions on JDBC 
  5. * drivers that support savepoints (such as the Oracle JDBC driver). 

事務掛起的部分代碼如下:

  1. /** 
  2.    * Suspend the given transaction. Suspends transaction synchronization first
  3.    * then delegates to the {@code doSuspend} template method. 
  4.    * @param transaction the current transaction object 
  5.    * (or {@code nullto just suspend active synchronizations, if any
  6.    * @return an object that holds suspended resources 
  7.    * (or {@code null} if neither transaction nor synchronization active) 
  8.    * @see #doSuspend 
  9.    * @see #resume 
  10.    */ 
  11.   @Nullable 
  12.   protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException { 
  13.     if (TransactionSynchronizationManager.isSynchronizationActive()) { 
  14.       List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization(); 
  15.       try { 
  16.         Object suspendedResources = null
  17.         if (transaction != null) { 
  18.           suspendedResources = doSuspend(transaction); 
  19.         } 
  20.         String name = TransactionSynchronizationManager.getCurrentTransactionName(); 
  21.         TransactionSynchronizationManager.setCurrentTransactionName(null); 
  22.         boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); 
  23.         TransactionSynchronizationManager.setCurrentTransactionReadOnly(false); 
  24.         Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); 
  25.         TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null); 
  26.         boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive(); 
  27.         TransactionSynchronizationManager.setActualTransactionActive(false); 
  28.         return new SuspendedResourcesHolder( 
  29.             suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive); 
  30.       } 
  31.   } 

邏輯在 doSuspend里,繼續(xù)看

  1. @Override 
  2.   protected Object doSuspend(Object transaction) { 
  3.     DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction
  4.     txObject.setConnectionHolder(null); 
  5.     return TransactionSynchronizationManager.unbindResource(obtainDataSource()); 
  6.   } 
  7.  
  8.   @Override 
  9.   protected void doResume(@Nullable Object transaction, Object suspendedResources) { 
  10.     TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources); 
  11.   } 

然后對應的bind 和 unbind 操作,是在ThreadLocal 對象里,將資源對象綁定或移出當前線程對應的 resources 來實現(xiàn)的。

  1. private static final ThreadLocal<Map<Object, Object>> resources = 
  2.       new NamedThreadLocal<>("Transactional resources"); 
  3. /** 
  4.    * Bind the given resource for the given key to the current thread. 
  5.    * @param key the key to bind the value to (usually the resource factory) 
  6.    * @param value the value to bind (usually the active resource object) 
  7.    * @throws IllegalStateException if there is already a value bound to the thread 
  8.    * @see ResourceTransactionManager#getResourceFactory() 
  9.    */ 
  10.   public static void bindResource(Object key, Object value) throws IllegalStateException { 
  11.     Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); 
  12.     Assert.notNull(value, "Value must not be null"); 
  13.     Map<Object, Object> map = resources.get(); 
  14.     // set ThreadLocal Map if none found 
  15.     if (map == null) { 
  16.       map = new HashMap<>(); 
  17.       resources.set(map); 
  18.     } 
  19.     Object oldValue = map.put(actualKey, value); 
  20.     // Transparently suppress a ResourceHolder that was marked as void... 
  21.     if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) { 
  22.       oldValue = null
  23.     } 
  24.   } 

對比上面的說明和代碼,這兩個傳播特性的區(qū)別也很明了了:

NESTED 的特性,本質(zhì)上還是同一個事務的不同保存點,如果涉及到外層事務回滾,則內(nèi)層的也將會被回滾;

 

REQUIRED_NEW 的實現(xiàn)對應的是一個新的事務,拿到的是新的資源,所以外層事務回滾時,不影響內(nèi)層事務。

本文轉(zhuǎn)載自微信公眾號「Tomcat那些事兒」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Tomcat那些事兒公眾號。

 

 

責任編輯:武曉燕 來源: Tomcat那些事兒
相關(guān)推薦

2023-10-30 07:36:19

Spring事務傳播機制

2024-12-17 16:26:31

2024-04-17 08:11:01

數(shù)據(jù)庫事務流程

2022-08-27 14:14:06

Spring事務開發(fā)

2024-01-04 12:48:00

Spring

2020-08-19 09:45:29

Spring數(shù)據(jù)庫代碼

2021-09-02 18:39:01

Spring隔離級別

2023-11-02 07:52:30

Java工具

2023-05-06 07:29:49

Spring事務傳播

2009-07-20 18:11:52

iBATIS事務Spring

2009-12-22 15:55:10

WCF事務

2017-01-19 15:32:36

Java全局事務本地事務

2022-09-27 21:14:54

Spring事務傳播機制

2024-12-17 16:44:22

Spring開發(fā)

2009-06-22 09:01:57

Spring聲明式事務

2022-04-26 21:49:55

Spring事務數(shù)據(jù)庫

2022-10-08 00:24:40

嵌套事務加入事務事務

2018-11-16 15:35:10

Spring事務Java

2009-06-30 16:41:12

Hibernate的事

2018-07-17 10:58:45

數(shù)據(jù)庫數(shù)據(jù)庫事務隔離級別
點贊
收藏

51CTO技術(shù)棧公眾號