一、原理
spring事务有两种实现方式,基于jdk动态代理实现、基于cglib实现。spring默认基于jdk动态代理,springboot貌似2.x以后默认是使用cglib,当然选择使用哪种方式都是可配置的。无论使用哪种方式其原理简单来说就是
运行时动态生成代理类、加载该类、执行增强后的代理类的方法。
- jdk动态代理是基于接口生成接口的实现类,接口没有的方法或者private、protected方法是无法被代理的
- cglib代理是基于继承生成被代理类的子类
二、简单使用
Spring事务管理涉及的接口的联系如下:
我们常用的mybatis使用的是DataSourceTransactionManager类
编程式事务这里不多说,这里只讲基于注解的声明式事务。
相关配置这里不详细举例,配置好后使用很简单,一般是在方法上加上@Transactinal注解即可。
@Override
@Transactional
public int update() {
userService.update2();
return userMapper.update();
}
三、回滚策略
@Transcation 默认只回滚 抛出RuntimeException 及Error异常的事务,因此默认情况下如果抛出SQLException、IOException不进行处理的话是无法回滚事务的。
可以通过设置rollbackFor的方式指定哪些异常回滚
//这样指定了,error也一样会回滚
@Transactional(rollbackFor = Exception.class)
error是一定会回滚的
三点结论:
- 当我们抛出的异常为RunTime及其子类或者Error和其子类的时候。不论rollbackFor的异常是啥,都会进行事务的回滚。
- 当我们抛出的异常不是RunTime及其子类或者Error和其子类的时候,必须根据rollbackfor进行回滚。比如rollbackfor=RuntimeException,而抛出IOException时候,事务是不进行回滚的。
- 当我们抛出的异常不是RunTime及其子类或者Error和其子类的时候,如果嵌套事务中,只要有一个rollbackfor允许回滚,则整个事务回滚。
注意下面这种方式,在使用spring整合mybatis的项目中,user表添加了唯一索引模拟sql异常,最后打印
====异常类型:class org.springframework.dao.DuplicateKeyException
SQL异常被spring封装成了Runtime异常,如果大家想测试上面的结论直接手动throw相应的异常就可以了
@Override
@Transactional(rollbackFor = RuntimeException.class)
public int insert(User user) {
try {
int insert = userMapper.insert(user);
int insert2 = userMapper.insert(user);
return insert;
} catch (Exception e){
System.out.println("====异常类型:"+e.getClass());
throw e;
}
}
四、嵌套事务的回滚策略
事务嵌套回滚策略由事务的传播行为决定,可以通过如下方式指定:
@Transactional(propagation = Propagation.REQUIRED)
事务传播行为类型 | 说明 | 外围方法不开启事务 | 外围方法开启事务 |
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 | 外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。外围方法不会加入到任一事务 | 外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 | - | - |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 | - | - |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 | 同第一排PROPAGATION_REQUIRED | 开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 | - | - |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 | - | - |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 | 同第一排PROPAGATION_REQUIRED | 外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务 |
五、事务方法调用自己类下面的其它方法
如下:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert(User user) {
try {
int insert = userMapper.insert(user);
this.update();//调用本类中其它添加了事务注解的方法,导致update事务不生效
return insert;
} catch (Exception e){
throw new RuntimeException();
}
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int update() {
try {
return userMapper.update();
} catch (Exception e){
throw new RuntimeException();
}
}
update事务失效是因为调用的动态代理类的增强方法,该方法内调用实际被增强类的方法,方法内this.xxx();,并不是使用的代理类。
解决方法:
使用增强后的代理类调用该方法,使用@Autowired或从spring容器获取bean,如果该Service加了事务注解,拿到的bean类型其实就是$ProxyXX等代理类并不是我们自己写的Service
如果使用的是springBoot,在启动类添加:@EnableAspectJAutoProxy(ExposeProxy=true) 在通过
AopContext.currentProxy()可以拿到当前类的代理类;
@Autowired
private UserService userService;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert(User user) {
try {
int insert = userMapper.insert(user);
//this.update();//调用本类中其它添加了事务注解的方法,导致update事务不生效
userService.update();//替换为调用增强类的方法
return insert;
} catch (Exception e){
throw new RuntimeException();
}
}
六、事务注解的timeout
一般我们使用默认值,默认为-1等待事务执行结果永不超时。
大家可以参考另一篇博客:spring事务超时时间测试 spring事务的超时时间 = 事务中最后一条sql执行完毕的时间 - 事务开始时间。