8-基于Spring的框架-事務——8-1 事務引入

概要

過度

我們前面介紹了Spring的基礎知識后,介紹了Spring對一些常用框架api的封裝:

  • jdbc
  • mybatis

這里主要介紹了對持久化層中的sql語句的封裝,使我們避免直接手寫sql,接下來介紹一下Spring對sql的事務的封裝,這能讓我們避免使用事務時的復雜設置操作。

內容簡介

本文分兩部分。

第一部分主要介紹事務的基礎知識,包括:

  • 是什么(事務的定義、特點)
  • 為什么(現(xiàn)實問題)
  • 怎么做(api調用)

emmmmm,是不是有點粗糙,有點像是初中政治湊字數(shù)。但是我覺得把這三點大概熟悉之后,我們在工作中日常使用事務應該問題不大了。至于深入,等后面有時間再說吧。

第二部分主要介紹事務api直接使用時的一些不方便的地方,并引出Spring對事務api的封裝。

后面會在本文中提出的Spring對事務api封裝的基礎上進行spring-tx組件的介紹。

所屬環(huán)節(jié)

Spring-tx 組件引入

上下環(huán)節(jié)

上文: Spring 框架基本實現(xiàn)介紹、Spring基本的持久化框架介紹

下文: Spring-tx框架詳細介紹

事務基礎知識

事務介紹及基本特點

事務在計算機術語中是指訪問并可能更新數(shù)據(jù)庫中各種數(shù)據(jù)項的一個程序執(zhí)行單元(unit)

上面是拷貝自百度知道的一句話,我覺得其中 執(zhí)行單元 這個用詞比較準確。執(zhí)行單元有以下特點:要么成功要么失敗,不存在執(zhí)行到一半的情況。

事務有以下特點:

  • 原子性(Atomic)
  • 一致性(Consistency)
  • 隔離性(Isolation)
  • 持久性(Durability)

對各個性質簡要記錄如下:

原子性

和最上面拷貝的一句話差不多,事務是一個執(zhí)行單元,要么成功要么失敗,不存在成功一半的可能

一致性

事務中的修改狀態(tài)的操作對外部的可見是語義上有意義的狀態(tài),符合數(shù)據(jù)庫和業(yè)務的相關約束

一致性和原子性的區(qū)別在于:原子性強調的是事務執(zhí)行的不可分割性,要么成功要不失敗,不存在執(zhí)行一半的情況;一致性強調的是狀態(tài)的對外暴露,及數(shù)據(jù)的可見性,只有最初和最終的狀態(tài)是可見的

隔離性

并發(fā)執(zhí)行的事務會排除彼此的影響。

對于隔離性,數(shù)據(jù)庫提供了多種隔離級別:

Read Uncommitted (讀取未提交內容)

在該隔離級別,所有事務都可以看到其他未提交事務的執(zhí)行結果。本隔離級別很少用于實際應用,因為它的性能也不比其他級別好多少。讀取未提交的數(shù)據(jù),也被稱之為臟讀(Dirty Read)。

Read Committed (讀取提交內容)

這是大多數(shù)數(shù)據(jù)庫系統(tǒng)的默認隔離級別(但不是MySQL默認的)。它滿足了隔離的簡單定義:一個事務只能看見已經(jīng)提交事務所做的改變。這種隔離級別 也支持所謂的不可重復讀(Nonrepeatable Read),因為同一事務的其他實例在該實例處理其間可能會有新的commit,所以同一select可能返回不同結果。

Repeatable Read (可重讀)

這是MySQL的默認事務隔離級別,它確保同一事務的多個實例在并發(fā)讀取數(shù)據(jù)時,會看到同樣的數(shù)據(jù)行。不過理論上,這會導致另一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當用戶讀取某一范圍的數(shù)據(jù)行時,另一個事務又在該范圍內插入了新行,當用戶再讀取該范圍的數(shù)據(jù)行時,會發(fā)現(xiàn)有新的“幻影” 行。InnoDB和Falcon存儲引擎通過多版本并發(fā)控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。

Serializable (可串行化)
這是最高的隔離級別,它通過強制事務排序,使之不可能相互沖突,從而解決幻讀問題。簡言之,它是在每個讀的數(shù)據(jù)行上加上共享鎖。在這個級別,可能導致大量的超時現(xiàn)象和鎖競爭。

這四種隔離級別采取不同的鎖類型來實現(xiàn),若讀取的是同一個數(shù)據(jù)的話,就容易發(fā)生問題。

例如:

臟讀(Drity Read):某個事務已更新一份數(shù)據(jù),另一個事務在此時讀取了同一份數(shù)據(jù),由于某些原因,前一個RollBack了操作,則后一個事務所讀取的數(shù)據(jù)就會是不正確的。

不可重復讀(Non-repeatable read):在一個事務的兩次查詢之中數(shù)據(jù)不一致,這可能是兩次查詢過程中間插入了一個事務更新的原有的數(shù)據(jù)【一般是修改】。

幻讀(Phantom Read):在一個事務的兩次查詢中數(shù)據(jù)不一致,幻讀是重點在插入和刪除,不可重復讀重點在修改

隔離級別 臟讀 不可重復讀 幻讀
讀未提交
讀提交
可重復讀
可串行化

一般情況下,MySQL的事務隔離級別是可重復讀,也就是說會鎖行,一個事務中兩次讀到的同一行數(shù)據(jù)是不會變化【被修改的】,但是兩次讀取可能會多出來或者少新的數(shù)據(jù)行。

持久性

事務在執(zhí)行完成提交后,造成的改動會被持久化保存。

使用事務的原因

默認情況下,數(shù)據(jù)庫執(zhí)行一條SQL語句就是一個原子的操作,但是很多時候業(yè)務邏輯要求我們將幾個操作綁定在一起,比如最經(jīng)典的“轉賬案例”:A賬戶扣減100元和B賬戶打入100元的操作是不可分割的。對于這種單個流程操作的整合,我們需要使用事務。

當然,數(shù)據(jù)庫事務和分布式事務的使用場景還是不太一樣的:

數(shù)據(jù)庫事務是用來鎖一臺機器的,也就是說事務是保證這一臺機器的操作具有不可分割、讀取的每一個狀態(tài)都是一致的這種效果?!臼褂媚J狀態(tài)】【具體是鎖的機器還是和線程對應的connector,這個沒弄特別透徹】

在多個機器并發(fā)的情況下很容易出問題,所以我們在工作中一般使用的是分布式鎖,保證關鍵事務能鎖住整個應用,同時其他的事務能正常執(zhí)行。

事務API

我們先看一個普通的jdbc調用:

public static void main(String args[]) throws ClassNotFoundException, SQLException {
  // 在 com.mysql.cj.jdbc.Driver 類的靜態(tài)代碼塊中進行了DriverManager的注冊。
  Class.forName("com.mysql.cj.jdbc.Driver");
  Connection connection = DriverManager.getConnection("jdbc:mysql:", "", "");
  Statement statement = connection.createStatement();
  ResultSet resultSet = statement.executeQuery("select * from form");
  while (resultSet.next()) {
    String x = resultSet.getString(1);
    System.out.println("x=" + x);
  }
}

基本就是獲得connection,得到statement,執(zhí)行,關閉幾個步驟。

MySQL默認是將執(zhí)行的sql丟進去,他會自行執(zhí)行并提交,一個SQL就是一個事務,所以如果我們想自己控制事務提交就要做如下改動:

  1. 禁止Connection自動提交事務
  2. 如果有可回滾的小步驟,自行設置保存點,并在執(zhí)行后根據(jù)情況決定是否回滾至保存點
  3. 在執(zhí)行完整體的事務之后,看情況回滾還是提交
  4. 執(zhí)行結束后關閉或者釋放Connection

所以使用事務API的demo如下:

public static void main(String args[]) throws ClassNotFoundException, SQLException {
  // 在 com.mysql.cj.jdbc.Driver 類的靜態(tài)代碼塊中進行了DriverManager的注冊。
  Class.forName("com.mysql.cj.jdbc.Driver");
  Connection connection = DriverManager.getConnection("jdbc:mysql://", "", "");
  // 設置事務
  connection.setAutoCommit(false);
  
  PreparedStatement statement = connection.prepareStatement(sql_a);
  statement.setString(1,"lpc create 2");
  statement.setInt(2,1);
  statement.setString(3,"lpc modify2");
  statement.setLong(4,123L);
  statement.setString(5,"form name2");
  statement.setLong(6,123L);
  statement.setString(7,"aaaaa2");

  statement.execute();

  Savepoint savepoint = connection.setSavepoint();

  PreparedStatement statement1 = connection.prepareStatement(sql_a);
  statement1.setString(1,"lpc create 1");
  statement1.setInt(2,1);
  statement1.setString(3,"lpc modify 1");
  statement1.setLong(4,123L);
  statement1.setString(5,"form name 1");
  statement1.setLong(6,123L);
  statement1.setString(7,"aaaaa 1");

  statement1.execute();
    // 回滾至保存點【保存點之后的修改作廢】
  connection.rollback(savepoint);
  
  // 整體事務執(zhí)行完成,提交【當然這里也可以回滾】
  connection.commit();
  // 關閉
  statement.close();
  connection.close();
}

事務API的使用規(guī)律及不足

不足

這個和前面的問題相似,就是存在大量的廢代碼,比如:

  1. 加載jdbc著一系列模版代碼,還有ConnectionStatement的創(chuàng)建關閉啥的一大堆東西
  2. 事務的管理和Connection相關,如果有多層子函數(shù)調用,可能要到處傳Connection
  3. 安全點的相關創(chuàng)建管理也特變容易亂

規(guī)律

首先:

  1. jdbc的相關東西我們在前面都實現(xiàn)了相關的操作

所以我們專門關注事務的就行了,我們很容易發(fā)現(xiàn)

  1. 安全點的創(chuàng)建和回滾是對應的,從回滾往上找到最近的一個安全點即可
  2. 事務的提交/回滾和事務創(chuàng)建也是對應的,從提交/回滾往上找最近的也就可以了

這個操作過程是不是特別像棧。我們后面是否可以考慮用AOP對原有業(yè)務做一個較小侵入式的事務功能支持!

Spring 對事務的封裝

Spring 實現(xiàn)的demo

Spring 配置的xml:

<!--增加對事務的聲明式支持-->
<tx:annotation-driven/>

<!--注冊事務-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

代碼:

在需要使用事務的地方【接口、方法、具體類】打上注解@Transactional(propagation = Propagation.XXX)

其中:

Propagation.REQUIRED:如果有事務就復用,沒有就新建

Propagation.SUPPORTS:如果有事務就復用,沒有就不在事務中跑

Propagation.MANDATORY:必須在事務中跑,沒有就拋出異常

Propagation.REQUIRES_NEW:必須在新事務中跑,如果當前有事務就先阻塞掉當前事務

Propagation.NOT_SUPPORTED:必須不在事務中跑,如果當前有事務就先阻塞掉當前事務

Propagation.NEVER:必須不在事務中跑,如果當前有事務就拋出異常

Propagation.NESTED:如果當前有事務,就復用,如果沒有就拋出異常

這些感覺不用記,有需要看api的注釋就行,主要是后面過代碼時用。

猜測實現(xiàn)思路

我們前面已經(jīng)了解了Spring框架的基本工作原理,大概熟悉了他的主要功能,根據(jù)使用方法我們可以大概反推一下Spring-tx的實現(xiàn)過程:

  1. 使用了<tx:annotation-driven/>標簽,和前面的MyBatis思路相似,應該是注冊一個BeanFactory的后處理器,它在完成注冊會被ApplicationContext調用,新創(chuàng)建一個實現(xiàn)了Advisor接口的含有事務邏輯的增強器。
  2. 在新創(chuàng)建的含有事務邏輯的增強器中會依賴我們注冊的transactionManager,并在執(zhí)行到增強器時進行注解內容的讀取,并根據(jù)注解配置執(zhí)行指定的事務操作。

因為我們的AOP切面恰好和方法調用堆棧一樣,正好契合了事務的安全點、阻塞+恢復、回滾、提交,所以上面這個思路是可行的。

擴展

問題遺留

參考文獻

事務特性:https://www.cnblogs.com/dooor/p/5303904.html

幻想讀和不可重讀讀讀區(qū)別:https://www.cnblogs.com/xiaohanlin/p/8644749.html

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 不足的地方請大家多多指正,如有其它沒有想到的常問面試題請大家多多評論,一起成長,感謝!~ String可以被繼承嗎...
    啟示錄是真的閱讀 3,067評論 3 3
  • 事務的嵌套概念 所謂事務的嵌套就是兩個事務方法之間相互調用。spring事務開啟 ,或者是基于接口的或者是基于類的...
    jackcooper閱讀 1,499評論 0 10
  • 摘要 本文摘抄了Spring事務相關的一些理論,主要講述事務的特性、事務的傳播行為、事務的隔離規(guī)則。 關鍵詞:事務...
    fad2aa506f5e閱讀 274評論 0 0
  • Spring 事務傳播特性和隔離級別 事務是處理邏輯原子性的保證,作為單個邏輯單元執(zhí)行一系列操作,要么執(zhí)行完成要么...
    自負的魚閱讀 9,103評論 1 33
  • 昨天有區(qū)塊鏈內容平臺的朋友問我,你們以前在簡書寫作,根本沒有鉆貝獎勵,你為什么還寫呢? 他的問題,問得我一愣。 是...
    千漫千尋閱讀 996評論 14 33

友情鏈接更多精彩內容