首先,說大概說一下事務傳播行為践盼,隨后講事務失效,具體分析同一個類里方法調(diào)用造成事務失效的情況宾巍,再到事務傳播行為應該在不同類的事務方法傳播咕幻,最后講會如何傳播。
0. 事務傳播行為大概認識
-
PROPAGATION_REQUIRED
假如當前正要執(zhí)行的事務不在另外一個事務里顶霞,那么就起一個新的事務
比如說肄程,ServiceB.methodB的事務級別定義為PROPAGATION_REQUIRED, 那么由于執(zhí)行ServiceA.methodA的時候,ServiceA.methodA已經(jīng)起了事務,這時調(diào)用ServiceB.methodB蓝厌,ServiceB.methodB看到自己已經(jīng)運行在ServiceA.methodA的事務內(nèi)部玄叠,就不再起新的事務。而假如ServiceA.methodA運行的時候發(fā)現(xiàn)自己沒有在事務中褂始,他就會為自己分配一個事務诸典。這樣,在ServiceA.methodA或者在ServiceB.methodB內(nèi)的任何地方出現(xiàn)異常崎苗,事務都會被回滾。即使ServiceB.methodB的事務已經(jīng)被提交舀寓,但是ServiceA.methodA在接下來fail要回滾胆数,ServiceB.methodB也要回滾 -
PROPAGATION_SUPPORTS
如果當前在事務中,即以事務的形式運行互墓,如果當前不再一個事務中必尼,那么就以非事務的形式運行 -
PROPAGATION_MANDATORY
必須在一個事務中運行。也就是說篡撵,他只能被一個父事務調(diào)用判莉。否則,他就要拋出異常`` -
PROPAGATION_REQUIRES_NEW
這個就比較繞口了育谬。 比如我們設計ServiceA.methodA的事務級別為PROPAGATION_REQUIRED券盅,ServiceB.methodB的事務級別為PROPAGATION_REQUIRES_NEW,那么當執(zhí)行到ServiceB.methodB的時候膛檀,ServiceA.methodA所在的事務就會掛起锰镀,ServiceB.methodB會起一個新的事務,等待ServiceB.methodB的事務完成以后咖刃,他才繼續(xù)執(zhí)行泳炉。他與PROPAGATION_REQUIRED 的事務區(qū)別在于事務的回滾程度了。因為ServiceB.methodB是新起一個事務嚎杨,那么就是存在兩個不同的事務花鹅。如果ServiceB.methodB已經(jīng)提交,那么ServiceA.methodA失敗回滾枫浙,ServiceB.methodB是不會回滾的刨肃。如果ServiceB.methodB失敗回滾,如果他拋出的異常被ServiceA.methodA捕獲自脯,ServiceA.methodA事務仍然可能提交之景。 -
PROPAGATION_NOT_SUPPORTED
`當前不支持事務。比如ServiceA.methodA的事務級別是PROPAGATION_REQUIRED 膏潮,而ServiceB.methodB的事務級別是PROPAGATION_NOT_SUPPORTED 锻狗,那么當執(zhí)行到ServiceB.methodB時,ServiceA.methodA的事務掛起,而他以非事務的狀態(tài)運行完轻纪,再繼續(xù)ServiceA.methodA的事務油额。 -
PROPAGATION_NEVER
不能在事務中運行。假設ServiceA.methodA的事務級別是PROPAGATION_REQUIRED刻帚, 而ServiceB.methodB的事務級別是PROPAGATION_NEVER 潦嘶,那么ServiceB.methodB就要拋出異常了。 -
PROPAGATION_NESTED
理解Nested的關(guān)鍵是savepoint崇众。他與PROPAGATION_REQUIRES_NEW的區(qū)別是掂僵,PROPAGATION_REQUIRES_NEW另起一個事務,將會與他的父事務相互獨立顷歌,而Nested的事務和他的父事務是相依的锰蓬,他的提交是要等和他的父事務一塊提交的。也就是說眯漩,如果父事務最后回滾芹扭,他也要回滾的。
而Nested事務的好處是他有一個savepoint赦抖。
我后面還會進一步分析舱卡,先別急。在具體深入講這個傳播行為前队萤。我必須先把失效給講了轮锥。
1.事務失效的幾種可能
1、標注事務的方法不是public的
2浮禾、你的異常類型不是unchecked異常
如果我想check異常也想回滾怎么辦交胚,需要注解上面寫明異常類型即可
@Transactional(rollbackFor=Exception.class)
1
類似的還有norollbackFor,自定義不回滾的異常
3盈电、數(shù)據(jù)庫引擎要支持事務蝴簇,如果是MySQL,注意表要使用支持事務的引擎匆帚,比如innodb熬词,如果是myisam,事務是不起作用的
4吸重、是否開啟了對注解的解析
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
5互拾、spring是否掃描到你這個包,如下是掃描到org.test下面的包
<context:component-scan base-package="org.test" ></context:component-scan>
6嚎幸、檢查是不是同一個類中的方法調(diào)用(如a方法調(diào)用同一個類中的b方法)
這點非常重要颜矿,同一個類的方法調(diào)用,會導致事務無效嫉晶。
7骑疆、UNCHECKED異常是不是被你catch住了
2.為什么同一個類的方法調(diào)用會導致事務無效呢
下面這段代碼田篇,事務會失效,并不會回滾箍铭。
原因就在addUser("13522203330");
其實是this.addUser("13522203330");
而這個THIS泊柬,并不是被SPRING 用CGLIB增強的類。也就是沒有被代理诈火,自然沒有做事務了兽赁。
@Service
public class TransactionalAopServiceImpl implements TransactionalAopService {
@Autowired
private OrderDao orderDao;
@Autowired
private UserDao userDao;
public void addOrder() {
orderDao.insert(OrderModel.builder()
.userId("YK_002") //游客編號
.phone("13522203330")
.orderId("ORDER_2018042602")
.amount(10000L)
.build());
//默開用戶
System.out.println("--->"+this.getClass());
addUser("13522203330");
}
@Transactional
public void addUser(String phone) {
userDao.insert(UserModel.builder().userName("zhangsan").userPhone(phone).build());
throw new RuntimeException();
}
}
破解方法
在調(diào)用addUser(……)之前添加下面的代碼
TransactionalAopService service = (TransactionalAopService) AopContext.currentProxy(); //獲取代理對象
service.addUser("13522203330"); //通過代理對象調(diào)用addUser,做異步增強
這里還不算完冷守,如果就這樣運行刀崖,那肯定會報錯。
在@EnableAspectJAutoProxy添加屬性值拍摇。
@EnableAspectJAutoProxy(exposeProxy = true)
建議可以先看我的AOP那章蒲跨,究其源頭是在于THIS 和SERVICE 是不同的類了。
如果對代理對象和當前對象有點懵的話授翻,可以加上下面的兩行代碼:
System.out.println("------>代理對象:"+service.getClass());
System.out.println("------>當前對象:"+this.getClass());
得到的結(jié)果:
------>代理對象:class com.minuor.aop.impl.TransactionalAopServiceImpl$$EnhancerBySpringCGLIB$$9de92f4b //可以看出來是CGLB動態(tài)代理
------>當前對象:class com.minuor.aop.impl.TransactionalAopServiceImpl
情況二:addOrder和addUser方法上都添加@Transactional
這種情況下,是可以回滾的孙咪,但是不太清楚是在哪個事務回滾堪唐,也不太清楚@Transactional是都有效,還是其中一個有效翎蹈。但是可以模擬淮菠,那就是定義三個異常,分別是OrderException荤堪、UserException合陵、OtherException,然后在兩個方法上指定回滾異常類澄阳。通過拋出不同的異常來看具體的結(jié)果拥知。
@Transactional修改
@Transactional(rollbackFor = OrderException.class, noRollbackFor = RuntimeException.class) //addOrder方法上
@Transactional(rollbackFor = UserException.class, noRollbackFor = RuntimeException.class) //addUser方法上
執(zhí)行結(jié)果分析
1、拋OtherException異常碎赢,沒有回滾低剔,order、user數(shù)據(jù)都成功錄入到數(shù)據(jù)庫中肮塞;
2襟齿、拋UserException異常,沒有回滾枕赵,order猜欺、user數(shù)據(jù)都成功錄入到數(shù)據(jù)庫中,這里可以看的出來addUser方法上的@Transactional注解是無效的拷窜;
3开皿、拋OrderException異常涧黄,回滾成功,order副瀑、user數(shù)據(jù)都沒有錄入到數(shù)據(jù)庫中弓熏,addOrder方法上的@Transactional有效。
這樣的結(jié)果加上動態(tài)代理原理的分析不難得出結(jié)果糠睡,addUser方法的代理增強被繞過挽鞠,只是普通的一個方法調(diào)用,而且這個方法是包含在addOrder方法事務內(nèi)的狈孔。
3.所以事務傳播行為應該在不同類的方法間傳播信认,有了這個基礎后,我們看傳播行為的問題
1.PROPAGATION_REQUIRED
我們?yōu)閁ser1Service和User2Service相應方法加上Propagation.REQUIRED屬性均抽。
User1Service方法:
@Service
public class User1ServiceImpl implements User1Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(User1 user){
user1Mapper.insert(user);
}
}
User2Service方法:
@Service
public class User2ServiceImpl implements User2Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(User2 user){
user2Mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequiredException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
}
1.1 場景一
此場景外圍方法沒有開啟事務嫁赏。
驗證方法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();
}
驗證方法2:
@Override
public void notransaction_required_required_exception(){
User1 user1=new User1();
user1.setName("張三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiredException(user2);
}
分別執(zhí)行驗證方法,結(jié)果:
結(jié)論:通過這兩個方法我們證明了在外圍方法未開啟事務的情況下Propagation.REQUIRED修飾的內(nèi)部方法會新開啟自己的事務油挥,且開啟的事務相互獨立潦蝇,互不干擾。
1.2 場景二
外圍方法開啟事務深寥,這個是使用率比較高的場景攘乒。
驗證方法1:
@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();
}
驗證方法2:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_required_exception(){
User1 user1=new User1();
user1.setName("張三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiredException(user2);
}
驗證方法3:
@Transactional
@Override
public void transaction_required_required_exception_try(){
User1 user1=new User1();
user1.setName("張三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
try {
user2Service.addRequiredException(user2);
} catch (Exception e) {
System.out.println("方法回滾");
}
}
分別執(zhí)行驗證方法,結(jié)果:
結(jié)論:以上試驗結(jié)果我們證明在外圍方法開啟事務的情況下Propagation.REQUIRED修飾的內(nèi)部方法會加入到外圍方法的事務中惋鹅,所有Propagation.REQUIRED修飾的內(nèi)部方法和外圍方法均屬于同一事務则酝,只要一個方法回滾,整個事務均回滾闰集。
2.PROPAGATION_REQUIRES_NEW
我們?yōu)閁ser1Service和User2Service相應方法加上Propagation.REQUIRES_NEW屬性沽讹。
User1Service方法:
@Service
public 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方法:
@Service
public 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();
}
}
2.1 場景一
外圍方法沒有開啟事務。
驗證方法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
public void notransaction_requiresNew_requiresNew_exception(){
User1 user1=new User1();
user1.setName("張三");
user1Service.addRequiresNew(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNewException(user2);
}
分別執(zhí)行驗證方法武鲁,結(jié)果:
結(jié)論:通過這兩個方法我們證明了在外圍方法未開啟事務的情況下Propagation.REQUIRES_NEW修飾的內(nèi)部方法會新開啟自己的事務爽雄,且開啟的事務相互獨立,互不干擾洞坑。
2.2 場景二
外圍方法開啟事務盲链。
驗證方法1:
@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();
}
驗證方法2:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception(){
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.addRequiresNewException(user3);
}
驗證方法3:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception_try(){
User1 user1=new User1();
user1.setName("張三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五");
try {
user2Service.addRequiresNewException(user3);
} catch (Exception e) {
System.out.println("回滾");
}
}
分別執(zhí)行驗證方法,結(jié)果:
結(jié)論:在外圍方法開啟事務的情況下Propagation.REQUIRES_NEW修飾的內(nèi)部方法依然會單獨開啟獨立事務迟杂,且與外部方法事務也獨立刽沾,內(nèi)部方法之間、內(nèi)部方法和外部方法事務均相互獨立排拷,互不干擾侧漓。
3.PROPAGATION_NESTED
我們?yōu)閁ser1Service和User2Service相應方法加上Propagation.NESTED屬性。
User1Service方法:
@Service
public class User1ServiceImpl implements User1Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.NESTED)
public void addNested(User1 user){
user1Mapper.insert(user);
}
}
User2Service方法:
@Service
public 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();
}
}
3.1 場景一
此場景外圍方法沒有開啟事務监氢。
驗證方法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:
@Override
public void notransaction_nested_nested_exception(){
User1 user1=new User1();
user1.setName("張三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNestedException(user2);
}
分別執(zhí)行驗證方法布蔗,結(jié)果:
結(jié)論:通過這兩個方法我們證明了在外圍方法未開啟事務的情況下Propagation.NESTED和Propagation.REQUIRED作用相同藤违,修飾的內(nèi)部方法都會新開啟自己的事務,且開啟的事務相互獨立纵揍,互不干擾顿乒。
3.2 場景二
外圍方法開啟事務。
驗證方法1:
@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();
}
驗證方法2:
@Transactional
@Override
public void transaction_nested_nested_exception(){
User1 user1=new User1();
user1.setName("張三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNestedException(user2);
}
驗證方法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("方法回滾");
}
}
分別執(zhí)行驗證方法泽谨,結(jié)果:
4. REQUIRED,REQUIRES_NEW,NESTED異同
由“1.2 場景二”和“3.2 場景二”對比璧榄,我們可知:
NESTED和REQUIRED修飾的內(nèi)部方法都屬于外圍方法事務,如果外圍方法拋出異常吧雹,這兩種方法的事務都會被回滾骨杂。但是REQUIRED是加入外圍方法事務,所以和外圍事務同屬于一個事務雄卷,一旦REQUIRED事務拋出異常被回滾搓蚪,外圍方法事務也將被回滾。而NESTED是外圍方法的子事務丁鹉,有單獨的保存點妒潭,所以NESTED方法拋出異常被回滾,不會影響到外圍方法的事務揣钦。
由“2.2 場景二”和“3.2 場景二”對比杜耙,我們可知:
NESTED和REQUIRES_NEW都可以做到內(nèi)部方法事務回滾而不影響外圍方法事務。但是因為NESTED是嵌套事務拂盯,所以外圍方法回滾之后,作為外圍方法事務的子事務也會被回滾记靡。而REQUIRES_NEW是通過開啟新的事務實現(xiàn)的谈竿,內(nèi)部事務和外圍事務是兩個事務,外圍事務回滾不會影響內(nèi)部事務摸吠。