Spring 事务失效的十二种场景
1、方法访问权限不是public
2、方法为 final 或 static
3、方法内部调用
4、没有被 spring 管理
5、多线程调用
6、错误的传播机制
7、异常被自己吞掉
8、抛出其他异常(不在指定范围内的)
9、自定义回滚异常(但又不包含)
10、事务嵌套多次回滚
11、表本身不支持事务
12、事务未开启
1、方法访问权限不是public
Java 基础访问权限分为 private、default、protected、public 依次变大
但 spring 事务要求被代理方法必须是 public,否则失效
2、方法被 final 或 static 修饰
spring 底层事务使用了aop 通过代理方式(cglib 或JDK 动态代理)对要代理的方法进行重写从而添加事务。
但被方法被 final 修饰后不允许被修改所以失效,static 类似staic 内容加载顺序在一般文件之前,我理解同样不能被动态修改,所以也会失效
3、方法内部调用
如果在一个没有事务的方法内部直接调用 this 下的其他被事务修饰的方法,那么同样无法生效。
因为此时通过内部调用改被事务修饰的方法实际上是通过 this 的方式被调用,没有被spring AOP 代理到该方法。
如果一定要让事务生效呢?可以如下:
- 将要被调用的方法另建一个ServiceB 然后通过注入的方式在需要调用的ServceA 中注入,然后通过 ServiceB.方法() 的方式,避免 this 的调用。
- 更简单一点可以在 当前Service 中注入自己的类 例如在ServceA 再通过@Autowired 再次注入 ServceA 然后去引用 (而且这样做并不会引起循环依赖,Spring ioc 内部三级缓存自我保证了不会循环)
- 也可以通过 AopContent ,在该 Service 类中使用 AopContext.currentProxy()获取代理对象
@Servcie
public class ServiceA {
public void saveDataA(Xxx xxx) {
// ......
((ServiceA)AopContext.currentProxy()).saveDataB(user);
}
@Transactional(rollbackFor=Exception.class)
public void saveDataB(Xxx xxx) {
// ...........
}
}
4、没有被 spring 管理
使用Spring 的前提是当前类被spring 管理,所以一些注解不要忘记添加
例如:
@Service、@Component、@Repository 等
5、多线程调用
如果有两个加了事务的方法,在其中一个方法中同时调用了另一个添加了事务的方法,但是被添加的事务方法是通过 new Thread 的方式等提交到了另一个线程中执行,那么会失效。
spring 标记是否是同一个事务通过与数据库建立的链接是否是同一个来判断的,当开两个线程去执行时自然建立了两个链接,此时已经不在同一事务中了。
6、错误的传播机制
6.1 spring 事务传播
spring目前支持 7种传播特性:
REQUIRED:
如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
SUPPORTS:
如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
MANDATORY:
如果当前上下文中存在事务,否则抛出异常。
REQUIRES_NEW:
每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
NOT_SUPPORTED:
如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
NEVER:
如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
NESTED:
如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
结论
而目前已知 REQUIRED,REQUIRES_NEW,NESTED 会新建事务
7、异常被自己吞掉
触发Spring 事务的基本条件之一是被事务管理的方法(方法或者类加了事务注解)首先要有异常抛出才会触发回滚,但如果自己将 异常 try catch 掉,使得异常消失那么也就无法回滚数据了。
8、抛出其他异常(不在指定范围内的)
spring事务,默认情况下只回滚RuntimeException(运行时异常)和Error(错误)。
对于普通的Exception(非运行时异常)不会回滚,如果我们手动捕获了异常然后又抛出了普通的Exception(非运行时异常),在没有指定的情况下事务同样不会生效。
9、自定义回滚异常(但又不包含)
@Transactional 可以指定 回滚的异常类型,如果指定的异常类型与实际抛出的不一致那么也将无法回滚数据
如下:
MyException 为我们自定义的异常,如果抛出了其他非我们自定义的异常,例如我们引用的jar 等里面的异常,此时将失效
@Transactional(rollbackFor = MyException.class)
public void saveXXXX(Xxxx xxxx) throws Exception {
// .......
}
实际使用时:
如果使用默认值,一旦程序抛出了Exception,事务不会回滚。
所以,建议一般情况下将该参数设置成:Exception 或 Throwable。
10、事务嵌套多次回滚
即在一个事务方法中 同时也调用了多个其他包含事务的方法(可能来自其他Service),但实际上 在多个事务执行的方法中如果位于后面的方法抛出异常,那么前面的都会被回滚,例如:
@Service
public class ServiceA {
@Autowired
private Xxxxxxx xxxxxxx;
@Autowired
private ServiceB serviceB;
@Transactional
public void saveAasdwd(Xxxxx xxxxx) throws Exception {
// 正常执行
xxxxxxx.saveDataA(xxxxx);
// 正常执行
xxxxxxx.saveDataB(xxxxx);
// 抛出异常的事务 此时上方 saveDataA,B 也会被回滚 实际我们希望的是只有 出异常的被回滚
serviceB.doSomething();
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.NESTED)
public void doSomething() {
// ........
}
}
此时可以将
@Service
public class ServiceA {
@Autowired
private Xxxxxxx xxxxxxx;
@Autowired
private ServiceB serviceB;
@Transactional
public void saveAasdwd(Xxxxx xxxxx) throws Exception {
// 正常执行
xxxxxxx.saveDataA(xxxxx);
// 正常执行
xxxxxxx.saveDataB(xxxxx);
// 抛出异常的事务 捕获后方法正常,只有 doSomething 被回滚
try {
serviceB.doSomething();
} catch (Exception e){
// ......
}
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.NESTED)
public void doSomething() {
// ........
}
}
11、表本身不支持事务
在 mysql5 之前,默认的数据库引擎是 myisam
好处是:索引文件和数据文件是分开存储的,对于查多写少的单表操作,性能比 innodb 更好。(但都是比较老的了,现在默认都是 innodb)
建表时,把ENGINE 参数设置成MyISAM即可。
但是这种引擎 却不支持事务!
12、事务未开启
spring boot 中是默认开启的,只要配置spring.datasource相关参数即可
但 spring 单个组成的项目中,需要手动在 application.xml 文件中,显式的开启
例如:
<!-- 配置事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes> <tx:method name="*" propagation="REQUIRED"/></tx:attributes>
</tx:advice>
<aop:config> <aop:pointcut expression="execution(* com.aurora.*.*(..))" id="pointcut"/> <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>