談談Spring事務注解

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

下面進行逐步分析:

Spring中七種事務傳播行為

事務傳播行為類型說明

PROPAGATION_REQUIRED如果當前沒有事務衅檀,就新建一個事務,如果已經存在一個事務中霎俩,加入到這個事務中哀军。這是最常見的選擇。是默認的傳播機制打却;

PROPAGATION_SUPPORTS支持當前事務杉适,如果當前沒有事務,就以非事務方式執(zhí)行学密。

PROPAGATION_MANDATORY使用當前的事務淘衙,如果當前沒有事務,就拋出異常腻暮。

PROPAGATION_REQUIRES_NEW新建事務彤守,如果當前存在事務,把當前事務掛起哭靖。

PROPAGATION_NOT_SUPPORTED以非事務方式執(zhí)行操作具垫,如果當前存在事務,就把當前事務掛起试幽。

PROPAGATION_NEVER以非事務方式執(zhí)行筝蚕,如果當前存在事務,則拋出異常。

PROPAGATION_NESTED如果當前存在事務起宽,則在嵌套事務內執(zhí)行洲胖。如果當前沒有事務,則執(zhí)行與PROPAGATION_REQUIRED類似的操作坯沪。

Spring事務嵌套詳解:

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();

? ? }

結果:插入了張三 李四 數(shù)據(jù) 外圍方法未開啟事務绿映,插入“張三”、“李四”方法在自己的事務中獨立運行腐晾,外圍方法異常不影響內部插入“張三”叉弦、“李四”方法獨立的事務。

驗證方法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();

? ? }

結果:都沒有插入 張三 李四 數(shù)據(jù)? 外圍方法開啟事務藻糖,內部方法加入外圍方法事務淹冰,外圍方法回滾,內部方法也要回滾巨柒。

1. 在外圍方法未開啟事務的情況下Propagation.REQUIRED修飾的內部方法會新開啟自己的事務樱拴,且開啟的事務相互獨立,互不干擾潘拱。

2.外圍方法開啟事務的情況下Propagation.REQUIRED修飾的內部方法會加入到外圍方法的事務中疹鳄,所有Propagation.REQUIRED修飾的內部方法和外圍方法均屬于同一事務,只要一個方法回滾芦岂,整個事務均回滾瘪弓。

2.PROPAGATION_REQUIRES_NEW

我們?yōu)閁ser1Service和User2Service相應方法加上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();

? ? }

}

外圍方法沒有開啟事務禽最。

驗證方法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();


? ? }

結果:張三 李四都插入 腺怯。外圍方法沒有事務,插入“張三”川无、“李四”方法都在自己的事務中獨立運行,外圍方法拋出異城赫迹回滾不會影響內部方法。

驗證方法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();

? ? }

結果:張三 未插入 李四 王五都插入 懦趋。外圍方法沒有事務晾虑,插入“張三”、“李四”方法都在自己的事務中獨立運行,外圍方法拋出異辰鼋校回滾不會影響內部方法帜篇。

1.在外圍方法未開啟事務的情況下Propagation.REQUIRES_NEW修飾的內部方法會新開啟自己的事務,且開啟的事務相互獨立诫咱,互不干擾笙隙。

2.在外圍方法開啟事務的情況下Propagation.REQUIRES_NEW修飾的內部方法依然會單獨開啟獨立事務,且與外部方法事務也獨立坎缭,內部方法之間竟痰、內部方法和外部方法事務均相互獨立签钩,互不干擾

3.PROPAGATION_NESTED

我們?yōu)閁ser1Service和User2Service相應方法加上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();

? ? }

}

此場景外圍方法沒有開啟事務坏快。

驗證方法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();

? ? }

結果:張三 李四都插入铅檩。外圍方法未開啟事務,插入“張三”莽鸿、“李四”方法在自己的事務中獨立運行柠并,外圍方法異常不影響內部插入“張三”、“李四”方法獨立的事務富拗。

驗證方法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();

? ? }

結果:張三 李四都沒有插入。外圍方法開啟事務鸣戴,內部事務為外圍事務的子事務啃沪,外圍方法回滾,內部方法也要回滾窄锅。

驗證方法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("方法回滾");

? ? ? ? }

? ? }

結果:張三 插入 创千,李四沒有插入? 這里李四拋出的異常被捕獲,不影響張三事務提交入偷。外圍方法開啟事務追驴,內部事務為外圍事務的子事務,插入“李四”內部方法拋出異常疏之,可以單獨對子事務回滾殿雪。

1.在外圍方法未開啟事務的情況下Propagation.NESTED和Propagation.REQUIRED作用相同,修飾的內部方法都會新開啟自己的事務锋爪,且開啟的事務相互獨立丙曙,互不干擾。

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

我們在實際工作中如何正確應用spring事務呢拯爽?我來舉一個示例:

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

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

做以下設計:

會員注冊實現(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事務失效條件:

1.@Transactional 注解只能應用到 public 可見度的方法上洲拇。 如果你在 protected奈揍、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不會報錯赋续,事務也會失效男翰。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末庸诱,一起剝皮案震驚了整個濱河市捻浦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桥爽,老刑警劉巖朱灿,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钠四,居然都是意外死亡盗扒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門缀去,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侣灶,“玉大人,你說我怎么就攤上這事缕碎∪煊埃” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵咏雌,是天一觀的道長凡怎。 經常有香客問我校焦,道長,這世上最難降的妖魔是什么统倒? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任寨典,我火速辦了婚禮,結果婚禮上房匆,老公的妹妹穿的比我還像新娘耸成。我一直安慰自己,他們只是感情好浴鸿,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布瞒滴。 她就那樣靜靜地躺著雷酪,像睡著了一般驴娃。 火紅的嫁衣襯著肌膚如雪煌恢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天宠页,我揣著相機與錄音,去河邊找鬼寇仓。 笑死举户,一個胖子當著我的面吹牛,可吹牛的內容都是我干的遍烦。 我是一名探鬼主播俭嘁,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼服猪!你這毒婦竟也來了供填?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤罢猪,失蹤者是張志新(化名)和其女友劉穎近她,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膳帕,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡粘捎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了危彩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攒磨。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖汤徽,靈堂內的尸體忽然破棺而出娩缰,到底是詐尸還是另有隱情,我是刑警寧澤谒府,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布拼坎,位于F島的核電站浮毯,受9級特大地震影響,放射性物質發(fā)生泄漏演痒。R本人自食惡果不足惜亲轨,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸟顺。 院中可真熱鬧惦蚊,春花似錦、人聲如沸讯嫂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽欧芽。三九已至莉掂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間千扔,已是汗流浹背憎妙。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曲楚,地道東北人厘唾。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像龙誊,于是被迫代替她去往敵國和親抚垃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354