一、事務的特性(ACID)
1、原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要么全部完成,要么完全不起作用。
2、一致性(Consistency):執行事務前后,數據保持一致;
3、隔離性(Isolation):並發訪問數據庫時,一個用戶的事物不被其他事物所干擾,各並發事務之間數據庫是獨立的;
4、持久性(Durability):一個事務被提交之后。它對數據庫中數據的改變是持久的,即使數據庫發生故障也不應該對其有任何影響。
二、事務的屬性
1、隔離級別
2、傳播行為
3、回滾規則:這些規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的)。 但是你可以聲明事務在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。同樣,你還可以聲明事務遇到特定的異常不回滾,即使這些異常是運行期異常。
4、是否只讀:事務的只讀屬性是指,對事務性資源進行只讀操作或者是讀寫操作。所謂事務性資源就是指那些被事務管理的資源,比如數據源、 JMS 資源,以及自定義的事務性資源等等。如果確定只對事務性資源進行只讀操作,那么我們可以將事務標志為只讀的,以提高事務處理的性能。在 TransactionDefinition 中以 boolean 類型來表示該事務是否只讀。
5、事務超時:所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
三、並發事務帶來的問題
在典型的應用程序中,多個事務並發運行,經常會操作相同的數據來完成各自的任務(多個用戶對統一數據進行操作)。並發雖然是必須的,但可能會導致一下的問題。
-
臟讀(Dirty read): 當一個事務正在訪問數據並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時另外一個事務也訪問了這個數據,然后使用了這個數據。因為這個數據是還沒有提交的數據,那么另外一個事務讀到的這個數據是“臟數據”,依據“臟數據”所做的操作可能是不正確的。
-
丟失修改(Lost to modify): 指在一個事務讀取一個數據時,另外一個事務也訪問了該數據,那么在第一個事務中修改了這個數據后,第二個事務也修改了這個數據。這樣第一個事務內的修改結果就被丟失,因此稱為丟失修改。
例如:事務1讀取某表中的數據A=20,事務2也讀取A=20,事務1修改A=A-1,事務2也修改A=A-1,最終結果A=19,事務1的修改被丟失。
-
不可重復讀(Unrepeatableread): 指在一個事務內多次讀同一數據。在這個事務還沒有結束時,另一個事務也訪問該數據。那么,在第一個事務中的兩次讀數據之間,由於第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這就發生了在一個事務內兩次讀到的數據是不一樣的情況,因此稱為不可重復讀。
-
幻讀(Phantom read): 幻讀與不可重復讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個並發事務(T2)插入或者刪除了一些數據時。在隨后的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱為幻讀。
四、事務隔離級別
TransactionDefinition 接口中定義了五個表示隔離級別的常量:
-
TransactionDefinition.ISOLATION_DEFAULT: 使用后端數據庫默認的隔離級別,Mysql 默認采用的 REPEATABLE_READ隔離級別 Oracle 默認采用的 READ_COMMITTED隔離級別.
-
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀
-
TransactionDefinition.ISOLATION_READ_COMMITTED: 允許讀取並發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生
-
TransactionDefinition.ISOLATION_REPEATABLE_READ: 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生。
-
TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。
五、事務傳播行為
當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事務,並在自己的事務中運行。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:
支持當前事務的情況:
-
TransactionDefinition.PROPAGATION_REQUIRED: 如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。
-
TransactionDefinition.PROPAGATION_SUPPORTS: 如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
-
TransactionDefinition.PROPAGATION_MANDATORY: 如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
不支持當前事務的情況:
-
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 創建一個新的事務,如果當前存在事務,則把當前事務掛起。
-
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事務方式運行,如果當前存在事務,則把當前事務掛起。
-
TransactionDefinition.PROPAGATION_NEVER: 以非事務方式運行,如果當前存在事務,則拋出異常。
其他情況:
-
TransactionDefinition.PROPAGATION_NESTED: 如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。
Spring事務常見陷進
一、假如有個接口,它包含兩個方法a和b,然后有一個類實現了該接口。在該實現類里在a上標上事務注解、b上不標。
如果b方法中調用了a方法,並且客戶端調用了b方法,此時會開啟事務嗎?
不會,雖然會生成代理類,但是代理類只會對帶有注解的方法進行事務增強,而沒有注解的方法只會調用目標類的方法(目標類的方法之前,之后都不做處理),然后目標類的b方法再調用自己的a方法。因此目標對象的代碼是我們自己寫的,和事務沒有半毛錢關系,此時你再調用帶注解的方法,照樣沒有事務,只是一個簡單的方法調用而已。
四、對於沒有實現接口的類,只能使用CGLIB來生成代理。假設有這樣一個類,它里面包含public方法,protected方法,private方法,package方法,final方法,static方法,我都給它們加上事務注解,哪些方法會有事務呢?
Cglib代理也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展。
private修飾的方法,類私有方法,不能被實現和被外部訪問,因此不起作用;
final修飾的方法不能被重寫,因此也不起作用;
static方法和繼承不相干,因此也不起作用;
protected修飾方法可以被重寫以添加事務代碼;
default修飾方法,如果生成的子類位於同一個包里,就可以被重寫;
public方法事務肯定起作用;
注意:代理類需要和目標類實現相同的接口,才能保持和目標類的類型兼容,以及對外接口相同。
只要是以代理方式實現的聲明式事務,無論是JDK動態代理,還是CGLIB直接寫字節碼生成代理,都只有public方法上的事務注解才起作用。而且必須在代理類外部調用才行,如果直接在目標類(例如上個問題中的b方法中調用a方法)里面調用,事務照樣不起作用。