天天看點

Spring事務的傳播行為與隔離級别——Spring學習筆記

一、什麼是事務

事務指的是邏輯上的一組操作,這組操作要麼都成功,要麼都不成功。

比如:我去菜市場買菜,部落客現在在杭州都用支付寶轉賬的形式。我買個一斤排骨用支付寶掃碼轉賬形式來付款。轉賬這個過程簡單來看可以拆分成兩個步驟:

1.從我賬戶扣錢;

2.老闆賬戶錢數增加。

這兩個步驟必須是一個整體,可以看成是一個事務。不能說執行步驟1成功了,從我賬戶扣錢了,轉給老闆的時候因為一些原因出錯了,老闆的錢沒增加。我的錢也不還我(事務復原)。那我倆就會組隊去支付寶門口耍賴去了。是以這裡,要麼兩個步驟都執行成功。如果有一個執行失敗就要進行事務的復原,使兩個步驟都失敗。

二、事務的特性(ACID)

1. 原子性(Atomicity)

 原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗復原。

  

2.一緻性(Consistency)

 一緻性是指事務必須使資料庫從一個一緻性狀态變換到另一個一緻性狀态,也就是說一個事務執行之前和執行之後都必須處于一緻性狀态。

 

 比如轉賬,假設使用者A和B兩者的錢加起來一共是100元,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個使用者的錢相加起來應該依然是100元,這就是事務的一緻性。

3. 隔離性(Isolation)

 隔離性是一個事務的執行不受其他事物的幹擾。

  

 比如當多個使用者并發通路資料庫時,操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所幹擾,多個并發事務之間要互相隔離。

 

5.持久性(Durability)

 持久性是指一個事務一旦被送出了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丢失已送出事務的操作。

三、事務的隔離級别

說隔離級别之前先說幾個概念:

髒讀 : 一個事務讀取到另一事務未送出的資料。如果這些資料被復原了,則讀到的是無效資料。

不可重複讀 : 在同一事務中,多次讀取同一資料傳回的結果有所不同。這是由于在查詢間隔,被另一個事務修改并送出了。

不可重複讀和髒讀的差別:髒讀是某一事務讀取了另一個事務未送出的髒資料,而不可重複讀則是讀取了前一事務送出的資料。

幻讀(虛讀) : 一個事務讀到另一個事務已送出的insert資料。

幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中所有的

type='1'

改為

type='2'

的操作,這時事務T2又對這個表中插入了一行

type='1'

的資料并送出。而操作事務T1的使用者如果再檢視剛剛修改的資料,會發現還有一行

type='1'

沒有修改,其實這行是從事務T2中添加的,這就是發生了幻讀。

幻讀和不可重複讀都是讀取了另一條已經送出的事務(這點就髒讀不同),所不同的是不可重複讀查詢的都是同一個資料項,而幻讀針對的是一批資料整體(比如資料的個數)。

接下來說事務的隔離級别:

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
1.讀未送出資料(會出現髒讀, 不可重複讀) 基本不使用

@Transactional(isolation = Isolation.READ_COMMITTED)
2.讀已送出資料(會出現不可重複讀和幻讀)

@Transactional(isolation = Isolation.REPEATABLE_READ)
3.可重複讀(會出現幻讀)

@Transactional(isolation = Isolation.SERIALIZABLE)
4.串行化或叫序列化
           

以上四種隔離級别由高到低為:

4>3>2>1

,最高的是

SERIALIZABLE

級别,最低的是

READ_UNCOMMITTED

級别,當然級别越高,執行效率就越低。像

SERIALIZABLE

這樣的級别,就是以鎖表的方式使得其他的線程隻能在鎖外等待,是以平時選用何種隔離級别應該根據實際情況。

MySQL

資料庫中預設的隔離級别為

REPEATABLE_READ

(可重複讀)。

SQLSERVER

資料庫中預設的隔離級别為

READ_COMMITTED

(讀已送出)。

Oracle

資料庫中預設的隔離級别為

READ_COMMITTED

(讀已送出)。(這裡需要指出的是,

Oracle

僅僅支援

SERIALIZABLE

READ_COMMITTED

部落客所在公司使用的是

MySql

,設定的隔離級别為

READ-COMMITTED

。如果你們用的也是

MySql

可以使用:

select @@tx_isolation;

來進行查詢。

四、事務的傳播行為

@Transactional(propagation=Propagation.REQUIRED) 
1.如果有事務,那麼加入事務,沒有的話建立一個事務(預設情況下)

@Transactional(propagation=Propagation.REQUIRES_NEW) 
2.不管是否存在事務,都建立一個新的事務。如果原來有事物存在,則将原來的事物挂起,
待新的事物執行完畢後,繼續執行原來挂起的老事務

@Transactional(propagation=Propagation.NOT_SUPPORTED) 
3.以非事務方式運作。如果有事物存在,則挂起目前事物,待執行完畢後,繼續執行原來挂起的事務

@Transactional(propagation=Propagation.SUPPORTS) 
4.如果有事物存在,那就加入目前事務。如果沒有事物存在,那就不用事務

@Transactional(propagation=Propagation.MANDATORY) 
5.必須在一個已有的事務中執行,如果不存在事物,則抛出異常(IllegalTransactionStateException)

@Transactional(propagation=Propagation.NEVER) 
6.以非事物運作,如果存在事物,則抛出異常(IllegalTransactionStateException與Propagation.MANDATORY相反)

@Transactional(propagation=Propagation.NESTED) 
7.如果目前存在事務,則在嵌套事務内執行,如果目前不存在事務,則建立一個新的事務,
嵌套事務使用資料庫中的儲存點來實作,即嵌套事務復原不影響外部事務,
但外部事務復原将導緻嵌套事務復原
           

五、工作中遇到的那些坑

1.

@Transactional

隻能被應用到public方法上,對于其它非public的方法,如果标記了

@Transactional

也不會報錯,但方法沒有事務功能.。

2.spring團隊的建議是你在具體的類(或類的方法)上使用

@Transactional

注解,而不要使用在類所要實作的任何接口上。

3.僅僅在方法或類上标記

@Transactional

是不足以開啟事物行為的,需要配置

<tx:annotation-driven/>

切記!

4.用spring事務管理器,由spring來負責資料庫的打開,送出,復原。預設遇到運作期異常(

throw new RuntimeException("注釋");

)會復原,即遇到不受檢查(

unchecked

)的異常時復原。

而遇到需要捕獲的異常(

throw new Exception("注釋");

)不會復原,即遇到受檢查的異常(就是非運作時抛出的異常,編譯器會檢查到的異常叫受檢查例外或受檢查異常)時,需我們指定方式來讓事務復原,要想所有異常都復原,要加上

@Transactional( rollbackFor={Exception.class,其它異常})

.如果讓

unchecked

例外不復原可以加上

@Transactional(notRollbackFor=RunTimeException.class)

5.在有事務的方法中切記遇到異常時要向外抛出,不能捕獲後悄悄的消化了,這樣事務捕獲不到異常是無法執行復原的,或者在

catch

代碼塊中手工執行復原。

6.在同一類中有兩個方法

testA()

事務屬性為

REQUIRED

testB()

事務屬性為

REQUIRED_NEW

。如果

testA()

方法中通過

this.testB()

的形式“自我調用”

testB()

方法。這時

testB()

方法上配置的事務屬性

REQUIRED_NEW

不會生效,而是會沿用

testA()

方法的事務屬性

REQUIRED

。這是因為spring事務實作本質是代理,事務的配置屬性是在代理對象中,而通過

this

形式調用不會調到代理對象,而是直接調用目标對象(target),是以事務配置屬性無效。

暫時記錄這些,後續工作中遇到事務相關問題還會回來進行補充!

如果文中有錯誤,或者不準确的地方,還請各位大佬指出!