Spring 事务
1.基本使用
(1)注解方式
主启动类:
@SpringBootApplication
@EnableTransactionManagement
public class TxApplcation{
}
业务逻辑类:@Transaction可以作用在类上,也可以作用在方法上,作用在类上代表对当前类所有public方法做事务管理
@Service
public class TxServiceImpl{
@Autowired
private TxMapper txMapper;
@Transactional
public void test(){
txMapper.insert();
}
}
(2)@Transactional注解6大属性
属性名 | 是否必填 | 默认值 | 描述 |
---|---|---|---|
timeout | 否 | -1(秒为单位) | 超时控制,超过规定时间未结束事务将会报错 |
readonly | 否 | false | 事务是否只读,即只能进行查询操作 |
rollbackfor | 否 | 指定异常回滚,事务默认回滚运行时异常(RuntimeException) | |
norollbackfor | 否 | 指定异常不回滚 | |
isolation | 否 | DEFAULT | 事务隔离级别 |
propagation | 否 | REQUIRED | 事务传播行为 |
(3)事务的传播行为
传播行为下事务的回滚:https://blog.csdn.net/LiZhen314/article/details/117373337
-
什么是事务传播行为:事务方法存在嵌套时,内部事务方法和外部事务方法是否是同一个事务(即同一个数据库连接)。【标注了@transaction注解的方法,才叫事务方法。事务的传播属性才有效。也就是说一个标注的方法,一个没标注的方法嵌套在一起,传播属性设置成req_new也没有什么用。】
-
七种事务传播行为
假设@Transactional注解作用在方法A上
属性值 | 解释 |
---|---|
REQUIRED | 如果有事务在运行,当前的方法A就在这个事务内运行,否则就开启一个新的事务,并在自己的事务内运行,默认传播行为 |
REQUIRED-NEW | 当前方法A必须启动新事务,并在自己的事务内运行,如果有事务正在运行,则将它挂起 |
SUPPROTS | 表示当前方法A应该运行在事务中,如果当前没有事务运行,则方法A只是一个普通方法 |
NOT_SUPPORTS | 表示当前方法A不应该运行在事务中,方法A只是一个普通方法。如果存在当前事务,在该方法运行期间,当前事务将被挂起。 |
MANDATORY | 当前的方法A必须运行在事务内部,如果没有正在运行的事务,就会抛出异常 |
NEVER | 当前方法A不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 没用 |
- 图示传播行为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gdeXZnuh-1686813203061)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220720151447215.png)]
4.Spring 的事务传播类型总结
- PROPAGATION_NESTED(没什么用)
1、共用一个事务,但每一层都有独立的保存点,可以独立提交事务的回滚。
2、每层事务可以通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 独立设置是否回滚。当前层的回滚不会影响上层的回滚,但上层的回滚会影响下层的回滚。
3、如果是通过抛出异常导致的回滚,这个会触发整个事务的回滚(Sping的事务处理中异常的回滚处理和手动回滚走的不同的处理逻辑)。
4、如果下层回滚,最上层不回滚,则最终事务仍是作为正常提交成功的事务,仍会触发事务提交成功后的事件,并不会触发事务回滚的事件。即最终事务的状态由最顶层的事务决定。
- PROPAGATION_REQUIRED(⭐)
1、共用一个事务,只能设置全局回滚或全局不回滚,不支持局部回滚。
2、如果有多层,只要有任意一层设置了回滚,则它的上一层及最上层也必须设置回滚,否则会抛出Transaction rolled back because it has been marked as rollback-only异常。
3、由于所有层都是统一的提交和回滚,因此不会出现部分提交的问题。
(4)几种事务失效的情况
- @Transactional没有作用在public方法上
⭐为什么只能是public方法
解答:这是因为事务底层在使用 Spring AOP 代理时,Spring 在调用 TransactionInterceptor 在目标方法执行前后进行拦截之前,会有个叫DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取 @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。
AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute:
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 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。
- Spring AOP的自调用
解答:因为spring事务是基于aop的代理机制,当方法中调用this本身的方法时候即使在this的方法标明事务注解,但是事务注解会失效。因为调用this本身方法不走代理机制(因为this代表是目标对象不是代理对象,调用目标对象本身方法当然不走代理)。也可以说,aop切入会生成service的代理对象,直接调用被切入的方法是有效的。如果被切入的方法,嵌套在一个没有被切入的方法里,就没有用了。可以通过aopcontext或者getBean错误代码如下:
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@@Service
public class Test{
@Transactional
@Override
public void purchase(String username, String isbn) {
this.update(username, isbn);
}
@Transactional
public void update(String username, String isbn) {
//1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新数的库存
bookShopDao.updateBookStock(isbn);
//3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
解决:使用AopContext获取到当前aop代理(AopContext:能够从当前容器的ThreadLocal中拿到暴露的aop代理对象)。需要在主启动类上加 @EnableAspectJAutoProxy(exposeProxy = true)
。AopContext会把这个事务先提交
@Transactional
@Override
public void purchase(String username, String isbn) {
((BookShopServiceImpl)AopContext.currentProxy()).update(username, isbn);
}
如果AopContext还是报错,那么使用第二种方法。getBean拿出来得是目标对象(this)的代理对象
RoleService roleService = SpringUtil.getBean(this.getClass());
roleService.事务方法()
- 内部方法声明了@Transaction注解,外部方法没有声明。(内层方法声明了,外层也需要。反之可以不需要)
- 在线程方法里面调用声明了@Transaction注解的方法:
- 因为线程不归spring管,所以不管线程所在代码块方法是否声明了@Transaction注解或者AopContext.currentProxy()都没有用,只有一个办法
- 这个办法就是,将线程内被调用的@Transaction方法,抽取到当前线程所在服务之外,即另外一个服务:https://blog.csdn.net/m0_69305074/article/details/124619777
2.声明式事务之原理
- IOC容器启动时,会去查看当前容器内是否有@EnableTransactionManagerment注解,如果有就会去拦截所有bean的创建,查看他们中是否有@Transactional注解,如果有这个注解就会利用Spring aop来为这些bean创建切面类生成代理对象,代理对象中会含有一个拦截器,叫做TransactionInterceptor
- TransactionInterceptor这个类会拦截bean中用@Transactional注解的public方法,会在目标方法执行前开启事务,在目标方法结束后提交/回滚事务
TransactionInterceptor实现MethodInterceptor可以看出,spring aop默认为事务使用cglib代理
import org.aopalliance.intercept.MethodInterceptor;
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
}
- TransactionInterceptor这个类构造时要传入一个事务管理器PlatformTransactionManager,这个接口主要用于开启事务,提交事务,回滚事务。PlatformTransactionManager有多个实现类,用来对应多个环境
- 开启一个事务:getTransaction()
执行了getTransaction
后,spring内部会执行一些操作,为了方便大家理解,咱们看看伪代码:将数据源datasource和connection映射起来放在了ThreadLocal中,ThreadLocal大家应该比较熟悉,用于在同一个线程中共享数据(线程私有内存);后面我们可以通过resources这个ThreadLocal获取datasource其对应的connection对象。
//有一个全局共享的threadLocal对象 resources
static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
//获取一个db的连接
DataSource datasource = platformTransactionManager.getDataSource();
Connection connection = datasource.getConnection();
//设置手动提交事务
connection.setAutoCommit(false);
Map<Object, Object> map = new HashMap<>();
map.put(datasource,connection);
resources.set(map);
- 提交事务:commit()
- 回滚事务:rollback()
ection();
//设置手动提交事务
connection.setAutoCommit(false);
Map<Object, Object> map = new HashMap<>();
map.put(datasource,connection);
resources.set(map);
* 提交事务:commit()
* 回滚事务:rollback()