Spring的@Transactional失效场景和事务注解方法之间调用(二)
Spring的@Transactional失效场景和事务注解方法之间调用(二)
三 @Transactional注解的方法之间调用(同类和不同类之间)
针对methodA方法调用methodB,且methodB中抛出异常的情况:
同类调用 | 不同类调用 |
---|
编号 | methodA是否@Transactional | methodB是否@Transactional | 事务是否有一致性 | 编号 | methodA是否@Transactional | methodB是否@Transactional | 事务是否有一致性 |
---|---|---|---|---|---|---|---|
1 | 无 | 有 | ❌ | 1 | 无 | 有 | 只有B回滚 |
2 | 有 | 无 | ✅ | 2 | 有 | 无 | ✅ |
3 | 有 | 有 | ✅ | 3 | 有 | 有 | ✅ |
4 | 有 | 有且requires_new | ✅ | 4 | 有 | 有且requires_new | |
5 | 有且catch | 无/有 | ❌ | 5 | 有且catch | 有 | 操作失败且抛异常 |
6 | 6 | 有且catch | 有且requires_new | 只有B回滚 |
3.1 同类中调用
3.1.1 各种情况说明
1 如果A非事务方法,调用同类中的B是事务方法,那么此时B的@Transactional不生效,此时即便B抛异常,那么也不会回滚。此时不具有事务一致性(情况1);
2 对于@Transactional的A,无论B方法中,是否有注解、或者注解为requires-new,都会处于同一个事务中,具有事务一致性(情况2、3、4);
3 如果@T的A中调用B(无论是否@Tran),此时catch,那么此时由于事务不会抛异常,所以事务不会回滚。(想要保证事务有效,此时需要再catch中再次throw异常)
3.1.2 总结
根据同类中调用,@Transactional会失效的原理,以及解决方案,那么,针对同类中的方法调用,就总结出如下的情况:
@Transactional
methodA(){
this.methodB();
this.methodC();
//Service service= AopContext.currentProxy();
//service.methodB();
//service.methodC();
}
//@Transactional /@Transactional(Propagation.REQUIRES_NEW)情况---------两种注解都无效
methodB(){
int i=1/0;//抛异常
}
//@Transactional /@Transactional(Propagation.REQUIRES_NEW)情况
methodC(){
}
1 如果只在methodA上注解,无论B和C是否加注解,那么B和C都处于当前事务中(在b抛出异常时,作为整体的事务,都会回滚);
2 在自我调用过程中,无论是否使用AopContext.currentProxy()注解调用,都处于同一个事务中(此时,使用AC.current.P调用,B和C上的注解也都是无效的);
3 如果想要B和C单独处于独立的事务中,那么需要将a方法注解取消,BC方法上以下两种情况,都会使得BC方法单独处于独立的事务中,各自保持各自事务的一致性;(注意:这里需要使用代理对象AopContext.currentProxy(),调用后续内部的方法,才能使事务生效)
methodA(){
Service service= AopContext.currentProxy(); //注意:此时仍旧需要使用代理对象(因为,如果A非事务,调用内部事务方法B,注解失效)
service.methodB();
service.methodC();
}
@Transactional /@Transactional(Propagation.REQUIRES_NEW)情况
methodB(){
int i=1/0;//抛异常
}
@Transactional /@Transactional(Propagation.REQUIRES_NEW)情况
methodC(){
}
4 如果想要对单独的事务方法B和C,根据执行情况控制逻辑执行,可进行如下的开发:
methodA(){
Service service= AopContext.currentProxy();
try{
service.methodB();
}catch(Exception){
//该处逻辑可以自己定义,实现B和C两个单独的事务之间的逻辑
return;
}
try{
service.methodC();
}catch(Exception){
return;
}
}
@Transactional /@Transactional(Propagation.REQUIRES_NEW)情况
methodB(){
try{
int i=1/0;//抛异常
}catch(Exception){
//注意:在事务方法中,try以内的部分不受事务控制,如果需要再抛出异常时,spring仍旧能够回滚,需要再catch中重新抛出异常
//另外,在当前throw代码之前的部分,仍旧处于回滚事务的部分,在throw抛异常时,仍旧会回滚。所以catch中添加的操作是无效的
throw new RuntimeException("xxx");
}
}
@Transactional /@Transactional(Propagation.REQUIRES_NEW)情况
methodC(){
}
此时,因为methodA中方法不是事务,而对于B和C都是单独的事务,因此a方法中catch到异常时,不需要再抛异常了。如果c的执行需要根据b的执行情况,那么就用上面的逻辑:
如果B抛异常,那么直接在methodB事务方法中catch并重新throw异常,所以会回滚;然后,A方法会catch异常。此时可自定义逻辑,如果直接return,那么不会执methodC中操作;如果B成功,那么C正常执行。
3.2 不同类中调用
methodB中抛异常,被methodA调用时,
同类调用 | 不同类调用 |
---|
编号 | methodA是否@Transactional | methodB是否@Transactional | 事务是否有一致性 | 编号 | methodA是否@Transactional | methodB是否@Transactional | 事务是否有一致性 |
---|---|---|---|---|---|---|---|
1 | 无 | 有 | ❌ | 1 | 无 | 有 | 只有B回滚 |
2 | 有 | 无 | ✅ | 2 | 有 | 无 | ✅ |
3 | 有 | 有 | ✅ | 3 | 有 | 有 | ✅ |
4 | 有 | 有且requires_new | ✅ | 4 | 有 | 有且requires_new | |
5 | 有且catch | 无/有 | ❌ | 5 | 有且catch | 有 | 操作失败且抛异常 |
6 | 6 | 有且catch | 有且requires_new | 只有B回滚 |
3.2.1 各种情况说明
1 a无@T,b有@T,此时只有b是个单独的事务方法,会单独的回滚,a执行。
2 如果a是@Tran,调用b方法,无论b是否有@Trans,具有事务一致性。只不过:
在@Tran A--->@Tran B时,可以认为事务rollback了两次,此时b抛异常回滚,同时A也抛异常回滚(情况3);
此时回滚一次(情况2);
3 如果@Tran A--->@Tran B,且a中catch异常,那么操作不会成功(可以理解回滚),此外,还抛异常(情况5)
org.springframework.transaction.UnexpectedRollbackException: Transaction rollback because it has benn marked as rollback-only
由于两者同处于一个事务,b抛异常,调用事务的rollback,并且标记为rollback-only;而在a方法中catch异常,这时候没有抛出异常,spring认为方法a需要commit,调用提交方法,而b中已经标记事务只能回滚,此时就会直接抛异常了,方法的执行失败。(此时事务的执行不会成功,操作失败)
针对此种情况,需要再a方法中,在catch中重新throw异常。
4 a中catch,b中设置事务的传播级别为REQUIRES_NEW。此时新添加的事务,会作为新的事务执行。
@Service()
public class AClass {
private BClass bClass;
@Autowired
public void setbClass(BClass bClass) {
this.bClass = bClass;
}
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 数据库操作A(增,删,该)
try {
bClass.bFunction();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Service()
public class BClass {
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bFunction() {
//todo: 数据库操作A(增,删,该)
throw new RuntimeException("函数执行有异常!");
}
}
此时,b方法回滚,a操作执行。(情况6)