文章目录
- 1、介绍
- 2、Spring事务的隔离级别
- 3、事务的传播行为
- 4、@Transactional注解包含的属性
- 5、使用
- 6、@Transactional失效场景
1、介绍
声明式事务管理是建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
优点:
- 不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过 @Transactional 注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。
不足:
- 最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
2、Spring事务的隔离级别
Spring 的接口 TransactionDefinition 中定义了表示隔离级别的常量,当然其实主要还是对应数据库的事务隔离级别:
ISOLATION_DEFAULT:使用后端数据库默认的隔离界别,MySQL 默认可重复读,Oracle 默认读已提交。
ISOLATION_READ_UNCOMMITTED:读未提交(Mysql:READ-UNCOMMITTED)
ISOLATION_READ_COMMITTED:读已提交(Mysql:READ-COMMITTED)
ISOLATION_REPEATABLE_READ:可重复读(Mysql:REPEATABLE-READ)
ISOLATION_SERIALIZABLE:串行化(Mysql:SERIALIZABLE)
对上面内容陌生的可以先看看这篇数据库事务总结
3、事务的传播行为
- 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。
4、@Transactional注解包含的属性
属性 | 类型 | 描述 |
---|---|---|
value | String | 可选的限定描述符,指定使用的事务管理器 |
propagation | enum:Propagation | 可选的事务传播行为设置 |
isolation | enum:Isolation | 可选的事务传播行为设置 |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int | 事务超时时间设置 |
rollbackFor | Class对象数组必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
5、使用
- 在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。
- @Transactional 注解只能应用到 public 可见度的方法上。
6、@Transactional失效场景
- @Transactional 应用在非 public 修饰的方法上
如果 Transactional 注解应用在非 public 修饰的方法上,Transactional 将会失效。
是因为在 Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法 或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource 的 computeTransactionAttribute方法,获取 Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Method method,Class<?> targetClass) {// Don't allow no-public methods as required.if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;
}
此方法会检查目标方法的修饰符是否为 public,不是 public 则不会获取@Transactional 的属性配置信息。
protected、private修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错
- @Transactional 注解属性 propagation 设置错误(设置的传播类型不支持事务,导致事务失效)
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- @Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认如果抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。
例如:运行过程中报了已检查异常(checked)比如IOException,数据库操作还是会提交的,但是如果我们需要它进行事务回滚,这时候可以在方法上通过修改@Transactional这个注解的rollbackFor=Exception.class来修改它的行为,既使你出现了checked这种例外,那么它也会对事务进行回滚
若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。
-
mysql使用myIsam引擎不支持事务,修改成innodb引擎
-
@Transactional注解所在的类没有被Spring管理,导致事务失效
解决办法是:添加@Service注解或者其他能注册成spring bean的注解 -
catch异常后,没有再次抛出异常,导致事务失效(异常被"吃"了)
如果在加有事务的方法内,使用了try…catch…语句块对异常进行了捕获,而catch语句块没有throw new
RuntimeException异常或者Spring支持的异常类型,则事务不会回滚。需要cache捕获异常后,再次抛出支持Spring事务的异常,事务才会正常执行。 -
方法对同一个类中其他方法调用,导致@Transactional 失效
AOP使用的是动态代理的机制,它会给类生成一个代理类,事务的相关操作都在代理类上完成。内部方式使用this调用方式时,使用的是实例调用,并没有通过代理类调用方法,所以会导致事务失效。
例如:
@Service
public class IUserServiceImpl implements IUserService{@Transactional(rollbackFor = RuntimeException.class)public void insertUser(){//其他处理this.insertUserDept();}@Transactional(rollbackFor = RuntimeException.class)public void insertUserDept(){userMapper.insertUserDept();}
}
解决方法是:
- 在该service实现类中通过@Resource注入自身Bean,然后通过调用自身bean,从而实现使用AOP代理操作。
@Service
public class IUserServiceImpl implements IUserService{@Resourceprivate IUserService userService;@Transactional(rollbackFor = RuntimeException.class)public void insertUser(){//其他处理userService.insertUserDept();}@Transactional(rollbackFor = RuntimeException.class)public void insertUserDept(){userMapper.insertUserDept();}
}
为什么自己注入自己不会导致死循环?