談?wù)凷pring事務(wù)注解

@Transation 注解是我們在開發(fā)中常用的注解,但是我們大多數(shù)只知道配置上就可以開啟事務(wù),很多人比如我,都不知道spring的事務(wù)原理,比如事務(wù)的嵌套生效情況 ,事務(wù)什么時候會失效,spring的事務(wù)又和mysql數(shù)據(jù)庫的事務(wù)有何聯(lián)系,其實這些都是面試中的高頻考點。

下面進行逐步分析:

Spring中七種事務(wù)傳播行為

事務(wù)傳播行為類型說明

PROPAGATION_REQUIRED如果當(dāng)前沒有事務(wù),就新建一個事務(wù),如果已經(jīng)存在一個事務(wù)中,加入到這個事務(wù)中。這是最常見的選擇。是默認的傳播機制;

PROPAGATION_SUPPORTS支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行。

PROPAGATION_MANDATORY使用當(dāng)前的事務(wù),如果當(dāng)前沒有事務(wù),就拋出異常。

PROPAGATION_REQUIRES_NEW新建事務(wù),如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起。

PROPAGATION_NOT_SUPPORTED以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。

PROPAGATION_NEVER以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。

PROPAGATION_NESTED如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒有事務(wù),則執(zhí)行與PROPAGATION_REQUIRED類似的操作。

Spring事務(wù)嵌套詳解:

1.PROPAGATION_REQUIRED

User1Service方法:

@Servicepublic class User1ServiceImpl implements User1Service {

? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.REQUIRED)

? ? public void addRequired(User1 user){

? ? ? ? user1Mapper.insert(user);

? ? }

}

User2Service方法:

@Servicepublic class User2ServiceImpl implements User2Service {

? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.REQUIRED)

? ? public void addRequired(User2 user){

? ? ? ? user2Mapper.insert(user);

? ? }

}

驗證方法1:

? ? @Override? ? public void notransaction_exception_required_required(){

? ? ? ? User1 user1=new User1();

? ? ? ? user1.setName("張三");

? ? ? ? user1Service.addRequired(user1);


? ? ? ? User2 user2=new User2();

? ? ? ? user2.setName("李四");

? ? ? ? user2Service.addRequired(user2);


? ? ? ? throw new RuntimeException();

? ? }

結(jié)果:插入了張三 李四 數(shù)據(jù) 外圍方法未開啟事務(wù),插入“張三”、“李四”方法在自己的事務(wù)中獨立運行,外圍方法異常不影響內(nèi)部插入“張三”、“李四”方法獨立的事務(wù)。

驗證方法2:

? @Override? ? @Transactional(propagation = Propagation.REQUIRED)

? ? public void transaction_exception_required_required(){

? ? ? ? User1 user1=new User1();

? ? ? ? user1.setName("張三");

? ? ? ? user1Service.addRequired(user1);


? ? ? ? User2 user2=new User2();

? ? ? ? user2.setName("李四");

? ? ? ? user2Service.addRequired(user2);


? ? ? ? throw new RuntimeException();

? ? }

結(jié)果:都沒有插入 張三 李四 數(shù)據(jù)? 外圍方法開啟事務(wù),內(nèi)部方法加入外圍方法事務(wù),外圍方法回滾,內(nèi)部方法也要回滾。

1. 在外圍方法未開啟事務(wù)的情況下Propagation.REQUIRED修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。

2.外圍方法開啟事務(wù)的情況下Propagation.REQUIRED修飾的內(nèi)部方法會加入到外圍方法的事務(wù)中,所有Propagation.REQUIRED修飾的內(nèi)部方法和外圍方法均屬于同一事務(wù),只要一個方法回滾,整個事務(wù)均回滾。

2.PROPAGATION_REQUIRES_NEW

我們?yōu)閁ser1Service和User2Service相應(yīng)方法加上Propagation.REQUIRES_NEW屬性。

User1Service方法:

@Servicepublic class User1ServiceImpl implements User1Service {

? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.REQUIRES_NEW)

? ? public void addRequiresNew(User1 user){

? ? ? ? user1Mapper.insert(user);

? ? }

? ? @Override? ? @Transactional(propagation = Propagation.REQUIRED)

? ? public void addRequired(User1 user){

? ? ? ? user1Mapper.insert(user);

? ? }

}

User2Service方法:

@Servicepublic class User2ServiceImpl implements User2Service {

? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.REQUIRES_NEW)

? ? public void addRequiresNew(User2 user){

? ? ? ? user2Mapper.insert(user);

? ? }


? ? @Override? ? @Transactional(propagation = Propagation.REQUIRES_NEW)

? ? public void addRequiresNewException(User2 user){

? ? ? ? user2Mapper.insert(user);

? ? ? ? throw new RuntimeException();

? ? }

}

外圍方法沒有開啟事務(wù)。

驗證方法1:

? ? @Override? ? public void notransaction_exception_requiresNew_requiresNew(){

? ? ? ? User1 user1=new User1();

? ? ? ? user1.setName("張三");

? ? ? ? user1Service.addRequiresNew(user1);


? ? ? ? User2 user2=new User2();

? ? ? ? user2.setName("李四");

? ? ? ? user2Service.addRequiresNew(user2);

? ? ? ? throw new RuntimeException();


? ? }

結(jié)果:張三 李四都插入 。外圍方法沒有事務(wù),插入“張三”、“李四”方法都在自己的事務(wù)中獨立運行,外圍方法拋出異?;貪L不會影響內(nèi)部方法。

驗證方法2:

? ? @Override? ? @Transactional(propagation = Propagation.REQUIRED)

? ? public void transaction_exception_required_requiresNew_requiresNew(){

? ? ? ? User1 user1=new User1();

? ? ? ? user1.setName("張三");

? ? ? ? user1Service.addRequired(user1);


? ? ? ? User2 user2=new User2();

? ? ? ? user2.setName("李四");

? ? ? ? user2Service.addRequiresNew(user2);


? ? ? ? User2 user3=new User2();

? ? ? ? user3.setName("王五");

? ? ? ? user2Service.addRequiresNew(user3);

? ? ? ? throw new RuntimeException();

? ? }

結(jié)果:張三 未插入 李四 王五都插入 。外圍方法沒有事務(wù),插入“張三”、“李四”方法都在自己的事務(wù)中獨立運行,外圍方法拋出異?;貪L不會影響內(nèi)部方法。

1.在外圍方法未開啟事務(wù)的情況下Propagation.REQUIRES_NEW修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。

2.在外圍方法開啟事務(wù)的情況下Propagation.REQUIRES_NEW修飾的內(nèi)部方法依然會單獨開啟獨立事務(wù),且與外部方法事務(wù)也獨立,內(nèi)部方法之間、內(nèi)部方法和外部方法事務(wù)均相互獨立,互不干擾

3.PROPAGATION_NESTED

我們?yōu)閁ser1Service和User2Service相應(yīng)方法加上Propagation.NESTED屬性。

User1Service方法:

@Servicepublic class User1ServiceImpl implements User1Service {

? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.NESTED)

? ? public void addNested(User1 user){

? ? ? ? user1Mapper.insert(user);

? ? }

}

User2Service方法:

@Servicepublic class User2ServiceImpl implements User2Service {

? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.NESTED)

? ? public void addNested(User2 user){

? ? ? ? user2Mapper.insert(user);

? ? }


? ? @Override? ? @Transactional(propagation = Propagation.NESTED)

? ? public void addNestedException(User2 user){

? ? ? ? user2Mapper.insert(user);

? ? ? ? throw new RuntimeException();

? ? }

}

此場景外圍方法沒有開啟事務(wù)。

驗證方法1:

? ? @Override? ? public void notransaction_exception_nested_nested(){

? ? ? ? User1 user1=new User1();

? ? ? ? user1.setName("張三");

? ? ? ? user1Service.addNested(user1);


? ? ? ? User2 user2=new User2();

? ? ? ? user2.setName("李四");

? ? ? ? user2Service.addNested(user2);

? ? ? ? throw new RuntimeException();

? ? }

結(jié)果:張三 李四都插入。外圍方法未開啟事務(wù),插入“張三”、“李四”方法在自己的事務(wù)中獨立運行,外圍方法異常不影響內(nèi)部插入“張三”、“李四”方法獨立的事務(wù)。

驗證方法2:

? ? @Transactional? ? @Override? ? public void transaction_exception_nested_nested(){

? ? ? ? User1 user1=new User1();

? ? ? ? user1.setName("張三");

? ? ? ? user1Service.addNested(user1);


? ? ? ? User2 user2=new User2();

? ? ? ? user2.setName("李四");

? ? ? ? user2Service.addNested(user2);

? ? ? ? throw new RuntimeException();

? ? }

結(jié)果:張三 李四都沒有插入。外圍方法開啟事務(wù),內(nèi)部事務(wù)為外圍事務(wù)的子事務(wù),外圍方法回滾,內(nèi)部方法也要回滾。

驗證方法3:

? ? @Transactional? ? @Override? ? public void transaction_nested_nested_exception_try(){

? ? ? ? User1 user1=new User1();

? ? ? ? user1.setName("張三");

? ? ? ? user1Service.addNested(user1);


? ? ? ? User2 user2=new User2();

? ? ? ? user2.setName("李四");

? ? ? ? try {

? ? ? ? ? ? user2Service.addNestedException(user2);

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? System.out.println("方法回滾");

? ? ? ? }

? ? }

結(jié)果:張三 插入 ,李四沒有插入? 這里李四拋出的異常被捕獲,不影響張三事務(wù)提交。外圍方法開啟事務(wù),內(nèi)部事務(wù)為外圍事務(wù)的子事務(wù),插入“李四”內(nèi)部方法拋出異常,可以單獨對子事務(wù)回滾。

1.在外圍方法未開啟事務(wù)的情況下Propagation.NESTED和Propagation.REQUIRED作用相同,修飾的內(nèi)部方法都會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。

2.在外圍方法開啟事務(wù)的情況下Propagation.NESTED修飾的內(nèi)部方法屬于外部事務(wù)的子事務(wù),外圍主事務(wù)回滾,子事務(wù)一定回滾,而內(nèi)部子事務(wù)可以單獨回滾而不影響外圍主事務(wù)和其他子事務(wù)

我們在實際工作中如何正確應(yīng)用spring事務(wù)呢?我來舉一個示例:

假設(shè)我們有一個注冊的方法,方法中調(diào)用添加積分的方法,如果我們希望添加積分不會影響注冊流程(即添加積分執(zhí)行失敗回滾不能使注冊方法也回滾),

而且在addPoint()中還要用一個addRecord()方法,這個方法用來記錄日志。

做以下設(shè)計:

會員注冊實現(xiàn):

@Service? public class UserServiceImpl implements UserService {

????@Transactional? ? ? ? public void register(User user){

? ? ? ? ? ? try {

?????????????????membershipPointService.addPoint(Point point);

? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? //省略...? ? ? ? ? ? }

? ? ? ? ? ? //省略...? ? ? ? }

? ? ? ? //省略...? }

注冊失敗要影響addPoint()方法(注冊方法回滾添加積分方法也需要回滾)

@Service? public class MembershipPointServiceImpl implements MembershipPointService{

????@Transactional(propagation = Propagation.NESTED)

? ? ? ? public void addPoint(Point point){

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? recordService.addRecord(Record record);

? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? //省略...? ? ? ? ? ? }

? ? ? ? ? ? //省略...? ? ? ? }

? ? ? ? //省略...? }

對于日志無所謂精確,可以多一條也可以少一條,所以addRecord()方法本身和外圍addPoint()方法拋出異常都不會使addRecord()方法回滾,并且addRecord()方法拋出異常也不會影響外圍addPoint()方法的執(zhí)行。

? @Service? public class RecordServiceImpl implements RecordService{


? ? ? ? @Transactional(propagation = Propagation.NOT_SUPPORTED)

? ? ? ? public void addRecord(Record record){

?????????????//省略...? ? ? ? }

? ? ? ? //省略...? }

Spring事務(wù)失效條件:

1.@Transactional 注解只能應(yīng)用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不會報錯,事務(wù)也會失效。

2. Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現(xiàn)的任何接口上。在接口上使用 @Transactional 注解,只能當(dāng)你設(shè)置了基于接口的代理時它才生效。因為注解是不能繼承 的,這就意味著如果正在使用基于類的代理時,那么事務(wù)的設(shè)置將不能被基于類的代理所識別,而且對象也將不會被事務(wù)代理所包裝。

3.數(shù)據(jù)庫不知道事務(wù)(一般不會有)

4.在業(yè)務(wù)代碼中,有如下兩種情況,比如:

throw new RuntimeException(“xxxxxxxxxxxx”); 事務(wù)回滾

throw new Exception(“xxxxxxxxxxxx”); 事務(wù)沒有回滾

一般不需要在業(yè)務(wù)方法中catch異常,如果非要catch,在做完你想做的工作后(比如關(guān)閉文件等)一定要拋出runtime exception,否則spring會將你的操作commit,這樣就會產(chǎn)生臟數(shù)據(jù)

5.異常類型是否配置正確

默認只支持 RuntimeException和Error ,不支持檢查異常

@Transactional(rollbackFor=Exception.class)? 顯示寫了遇到Exception回滾,拋出的是RuntimeException 會造成事務(wù)失效

Spring事務(wù)與數(shù)據(jù)庫事務(wù)的關(guān)系:

對數(shù)據(jù)庫來說隔離級別只有4種,

#讀未提交set tx_isolation='read-uncommitted';? #讀已提交set tx_isolation='read-committed';? #為可重復(fù)讀set tx_isolation='REPEATABLE-READ';? #為串行化set tx_isolation='SERIALIZABLE';

Spring有5種隔離級別,7種傳播行為,Spring多了一個DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別。

spring的事務(wù)是對數(shù)據(jù)庫的事務(wù)的封裝,最后本質(zhì)的實現(xiàn)還是在數(shù)據(jù)庫,假如數(shù)據(jù)庫不支持事務(wù)的話,spring的事務(wù)是沒有作用的

數(shù)據(jù)庫的事務(wù)說簡單就只有開啟,回滾和關(guān)閉,spring對數(shù)據(jù)庫事務(wù)的包裝,原理就是拿一個數(shù)據(jù)連接,根據(jù)spring的事務(wù)配置,操作這個數(shù)據(jù)連接對數(shù)據(jù)庫進行事務(wù)開啟,回滾或關(guān)閉操作.但是spring除了實現(xiàn)這些,還配合spring的傳播行為對事務(wù)進行了更廣泛的管理.其實這里還有個重要的點,那就是事務(wù)中涉及的隔離級別,

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

友情鏈接更多精彩內(nèi)容