- @Transactional注解 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
- spring声明式事务是基于AOP,AOP的实现原理是动态代理,要通过代理的方式获取到代理的具体对象。如果方法无法重写,就无法被代理。所以static和final修饰方法也同样不能支持事务。
一、Spring事务失效的9种情况
1.事务方法不是public修饰的时候,不支持事务
解决方法:
1)修改成public方法
2)开启 AspectJ代理模式
注意点:
spring声明式事务是基于AOP,AOP的实现原理是动态代理,要通过代理的方式获取到代理的具体对象。如果方法无法重写,就无法被代理。所以static和final修饰方法也同样不能支持事务。
2.@Transactional注解的方法不是抛出的异常不是spring的事务支持的异常,导致事务失败
解决方法:
1)在注解上指定spring支持的异常类型,代码格式为@Transactional(rollbackFor=Exception.class)
2)抛出spring支持回滚的异常,比如:try…cache(Exception e){Throw new RuntimeException(“运行时异常”)}
注意点:
spring的事务只支持未检查异常(unchecked),不支持已检查异常(checked),比如IOException,这些异常在java语法中是要求强制处理的。对于这些普通异常,Spring默认它们都已经处理,所以默认不回滚;
3.数据库本身不支持事务,导致事务失败
解决办法:
比如mysql使用myIsam引擎不支持事务,修改成innodb引擎
4.@Transactional注解所在的类没有被Spring管理,导致事务失效
案例:
//@Service
public class UserServiceImpl implements UserService{
@Transactional
public void insertUser(){
……
}
}
解决方法:
添加@Service注解或者其他能注册成spring bean的注解
注意点: 所以必须在实现类等里实现事务
5.catch异常后,没有再次抛出异常,导致事务失效
案例:
@Service
public class UserServiceImpl implements UserService{
@Transactional
public void insertUser(){
try {
……
} cache (Exception e) {
e.printStackTrace();
}
}
}
解决方法:
cache捕获异常后,再次抛出支持Spring事务的异常,比如:
@Service
public class UserServiceImpl implements UserService{
@Transactional
public void insertUser(){
try {
……
} cache (Exception e) {
throw new RuntimeException();
}
}
}
注意点: 如果在加有事务的方法内,使用了try…catch…语句块对异常进行了捕获,而catch语句块没有throw new
RuntimeException异常或者Spring支持的异常类型,则事务不会回滚。
6.调用自身的方法,导致事务失效
案例:
@Service
public class UserServiceImpl implements UserService{
public void insertUser(){
insertUserSub();
}
@Transactional
public void insertUserSub(){
userDao.inserUserSub();
}
}
解决办法:
1)把insertUserSub方法放到另一个类里
2)自己注入自己,用注入的实例调用insertUserSub方法
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserService usertService
public void insertUser(){
userService.insertUserSub();
}
@Transactional
public void insertUserSub(){
userDao.inserUserSub();
}
}
3)获取代理类,用代理类调用insertUserSub方法
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserService usertService
public void insertUser(){
((UserService)AopContext.currentProxy()).insertUserSub();
}
@Transactional
public void insertUserSub(){
userDao.inserUserSub();
}
}
4)把@Transactional注解放在inserUser方法上
注意点: @Transactional是基于动态代理实现的,自己调用自己的过程,不存在代理对象的调用,导致事务失效
7.数据源没有配置事务管理器,导致事务失效
解决方法: 增加事务管理器的配置
例如代码没有配置
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
8.传播类型不支持事务,导致事务失效
实例:
@Service
public class UserServiceImpl implements UserService{
@Transactional
public void insertUser(){
insertUserSub();
}
@Transactional(propagation=Propagation.NO_SUPPORTED)
public void insertUserSub(){
userDao.inserUserSub();
}
}
9.多线程调用,导致事务失效
案例:
@Service
public class UserServiceImpl implements UserService{
@Transactional
public void insertUser(){
userDao.inserUser();
new Thread (() ->{
roleService.insertRole();
}).start();
}
}
@Service
public class RoleServiceImpl implements RoleService{
@Transactional
public void insertRole(){
roleDao.inserRole();
}
}
两个方法不在同一个线程,获取的数据库连接不一样,从而是两个不同的事务。我们说的同一个事物,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和会滚。
二、Spring事务的配置
1.事务的隔离级别
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
2.事务的传播行为
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
3.@Transactional的属性
属性 | 类型 | 描述 |
---|---|---|
value | String | 可选的限定描述符,指定使用的事务管理器 |
propagation | enum: | Propagation |
isolation | enum: | Isolation |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int | (in |
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
4.@Transaction用法
- 在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。
- @Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,
但是这个被注解的方法将不会展示已配置的事务设置。- 注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。
- 通过 元素的 “proxy-target-class” 属性值来控制是基于接口的还是基于类的代理被创建。如果 “proxy-target-class” 属值被设置为
“true”,那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果
“proxy-target-class” 属值被设置为 “false” 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。
参考文档:
- spring的@Transactional注解详细用法 https://www.cnblogs.com/wangfg/p/9475788.html
- Spring 事务失效的 8 种场景! https://blog.csdn.net/sufu1065/article/details/122076645
- Spring事务失效常见场景 https://blog.csdn.net/qq_16268979/article/details/123707823