1 同一个类中函数相互调用
假设: 同一个类AClass中,有两个方法aFunction、aInnerFunction;aFunction调用aInnerFunction;aFunction函数被其他类调用;
实例1: 两个方法都添加了@Transactional注解,aInnerFunction使用Propagation.REQUIRES_NEW传播方式;aInnerFunction抛异常;
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 数据库操作A(增,删,该)
aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void aInnerFunction() {
//todo: 操作数据B(做了增,删,改 操作)
throw new RuntimeException("函数执行有异常!");
}
结果: 两个函数操作的数据都会回滚;同类调用,不涉及事务传播,相当于aInnerFunction的代码加到了aFunction方法内;
实例2: aFunction不添加注解,aInnerFunction添加注解;aInnerFunction抛异常;
public void aFunction() {
//todo: 数据库操作A(增,删,该)
aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
}
@Transactional(rollbackFor = Exception.class)
public void aInnerFunction() {
//todo: 操作数据B(做了增,删,改 操作)
throw new RuntimeException("函数执行有异常!");
}
结果: 两个函数对数据库的操作都不会回滚;因为同类方法调用不会调用代理对象的方法,@Transactional注解添加和没添加一样;
2 不同类中函数相互调用
假设: 两个类AClass、BClass;AClass类有aFunction、BClass类有bFunction;AClass类aFunction调用BClass类bFunction;AClass类的aFunction被其他类调用;。
实例1: aFunction添加注解,bFunction不添加注解;bFunction抛异常;
@Service
public class AClass {
@Autowired
private BClass bClass;
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 数据库操作A(增,删,该)
bClass.bFunction();
}
}
@Service
public class BClass {
public void bFunction() {
//todo: 数据库操作A(增,删,该)
throw new RuntimeException("函数执行有异常!");
}
}
结果: 两个函数对数据库的操作都回滚了;相当于aFunction执行时抛了异常;此时,bFunction如果打上事务注解并且使用默认的事务传播方式,结果也一样;因为两个方法处于同一个事务内;
实例2: aFunction、bFunction两个函数都添加事务注解;bFunction抛异常;aFunction抓出异常并吞掉异常;
@Service
public class AClass {
@Autowired
private BClass bClass;
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 数据库操作A(增,删,该)
try {
bClass.bFunction();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Service
public class BClass {
@Transactional(rollbackFor = Exception.class)
public void bFunction() {
//todo: 数据库操作A(增,删,该)
throw new RuntimeException("函数执行有异常!");
}
}
结果: 两个函数数据库操作都没成功,而且还抛异常了org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only;
可以这么理解,两个函数用的是同一个事务;bFunction函数抛了异常,调了事务的rollback函数,并且事务被标记了只能rollback了;程序继续执行,aFunction函数里面把异常给抓出来了,这个时候aFunction函数没有抛出异常,既然你没有异常那事务就需要提交,会调事务的commit函数;而之前这个事务已经被标记了只能rollback-only(因为是同一个事务),因此直接就抛异常了,不让调了;
实例3: aFunction、bFunction两个函数都添加注解;bFunction抛异常,aFunction抓出异常;这里要注意bFunction函数@Transactional注解我们是有变化的,加了一个参数propagation = Propagation.REQUIRES_NEW,控制事务的传播行为,表明是一个新的事务;其实情况3就是来解决情况2的问题的;
@Service
public class AClass {
@Autowired
private 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("函数执行有异常!");
}
}
结果: bFunction函数里面的操作回滚了,aFunction里面的操作成功了;有了前面情况2的理解。这种情况也很好解释,因为两个函数不是同一个事务了,所以bFunction抛异常只会导致bFunction的回滚,不影响aFunction所在事务的正常执行;
3 失效场景
- @Transactional注解未打在public方法上
Java的访问权限主要有四种:private、default、protected、public;如果事务方法定义了错误的访问权限(非public方法),会导致事务失效;
- 目标方法用final修饰
某个方法不想被子类重写,可以将该方法定义成final的;如果将事务方法定义成final,会导致事务失效;
原因: Spring事务基于Spring AOP,通过JDK动态代理或者CGlib代理,在代理类中实现的事务功能;但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法;同样,static修饰的方法,同样无法通过动态代理,变成事务方法;
- 同一个类中的方法直接内部调用
原因:方法被事务管理是因为Apring AOP为其生成代理了对象,但是直接this调用同类方法,调用的是目标类对象的方法,而非代理类方法,因此,在同类中的方法直接内部调用,会导致事务失效;
如果有些场景,确实想在同一个类的某个方法中,调用当前类的另外一个事务方法
方法1:新写一个Service,把事务方法挪过去,在当前类注入新的Service
方法2:在当前Service注入自己;
方法3:在当前Service类中使用AopContext.currentProxy()获取当前类的代理对象,相比方法2更加直观;
@Servcie
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
- 事务方法所在的类未被Spring管理
使用Spring事务的前提是:对象要被Spring IOC容器管理,需要创建bean实例;打了注解,但是忘了在当前类加@Service注解,导致事务不生效,也是小白常见的编码错误;
- 多线程调用
如果两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务;