@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的傳播行為對事務進行了更廣泛的管理.其實這里還有個重要的點,那就是事務中涉及的隔離級別,