Spring事务详细解析

关于事务的概念

详细可参阅MySQL知识点总结中的“事务”部分

简单来说Spring事务就是接口在执行时,为了保证ACID属性避免出现并发一致性问题而进行的一组操作

Spring事务的属性

org.springframework.transaction

在这里插入图片描述

  • 事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上
  • 事务属性包含了几个方面:传播行为、隔离规则、回滚规则、事务超时、是否只读

传播行为

Transaction Propagation :: Spring Framework

详解Spring事务的传播机制 - 知乎 (zhihu.com)

  • PropagationBehavior事务传播行为指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。

  • 事务传播行为类型说明
    0:PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚
    1:PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
    2:PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
    3:PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰
    4:PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    5:PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
    6:PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务

隔离规则

主要为了解决并发一致性问题,如何解决可以参考mysql的隔离级别与锁的关系

隔离规则说明
-1:ISOLATION_DEFAULT使用后端数据库默认的隔离级别 对于MYSQL来说就是可重复读
1:ISOLATION_READ_UNCOMMITTED读未提交,防止丢失修改,但会出现脏读、不可重复读、幻读
2:ISOLATION_READ_COMMITTED读已提交,防止脏读,但会出现不可重复读和幻读
4:ISOLATION_REPEATABLE_READ可重复读,mysql默认隔离级别,防止不可重复读,但会出现幻读
8:ISOLATION_SERIALIZABLE可串行化,最高隔离级别,防止幻读,性能极低

超时

事务的定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束

是否只读

当代码读取但不修改数据时,可以使用只读事务,加快执行效率。

Spring实现事务

Declarative Transaction Management :: Spring Framework

Spring——事务注解@Transactional【建议收藏】

  • spring事务分为声明式事务(declarative)和编程式事务(programmatic)
  • 声明式事务:基于Spring AOP,通过注解或XML配置实现。有助于用户将操作与事务规则进行解耦,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。显然声明式事务要优于编程式事务,这正是Spring倡导的非侵入式的开发方式
  • 编程式事务:允许用户在代码中精确定义事务的边界,通过编程代码在业务逻辑时需要时自行实现,粒度更小,Spring推荐使用TransactionTemplate

声明式事务

Using @Transactional :: Spring Framework

xml形式参考官网示例即可,这里着重记录下注解形式

@Transactional 属性

在这里插入图片描述

属性说明
value当在配置文件中有多个TransactionManager, 可以用该属性指定选择哪个事务管理器
transactionManager同上
propagation事务的隔离规则,默认值采用 DEFAULT,对于MYSQL来说就是可重复读RR
isolation事务的传播行为,默认为PROPAGATION_REQUIRED
timeoutOptional transaction timeout. Applies only to propagation values of REQUIRED or REQUIRES_NEW.
timeoutString事务超时时间
readOnly是否只读事务
rollbackFor用于指定能够触发事务回滚的异常类型
rollbackForClassName同上,指定类名
noRollbackFor用于指定不会触发事务回滚的异常类型
noRollbackForClassName同上,指定类名

@Transactional作用范围

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional
  • @Target({ElementType.TYPE, ElementType.METHOD})表明了可以作用于类、接口(包括注释类型)或枚举声明
    • 作用于类时表示所有该类的public方法都配置相同的事务属性信息,会导致事务控制的粒度太大,注解参数无法根据每个类方法的实际需求设置,因此推荐设置在方法上
    • 作用于方法时,当类和方法都配置了该注解,方法的事务会覆盖类的事务配置信息。
    • 作用于接口:不推荐这种使用方法
      • 首先接口的设计应该是面向抽象和规范的,而事务注解属于实现细节。将事务注解直接放在接口上,会暴露框架相关的实现细节(代理对象需要实现接口),破坏了接口的抽象性。
      • 接口上的注解会被所有实现该接口的类继承(@Inherited),这可能导致事务行为的不一致或冲突。不同实现类对事务的要求可能不同,有些实现类可能需要使用其他的事务管理方式,将事务注解放在接口上会限制了灵活性。
      • 将事务注解放在接口上,意味着对该接口所有方法都启用了事务。但实际应用中,不同的方法可能有不同的事务需求,有些方法可能并不需要事务支持。过度使用事务会增加数据库的负担,并可能导致性能问题。
  • @Inherited表明了子类可以继承父类的事务属性

@Transactional的异常回滚

  • spring默认只在运行时异常(runtimeException)和error中会进行回滚,可检查异常并不会回滚。
  • 可检查异常虽说像IOException会强制进行try…catch的处理,但还有一些不需要try catch处理也不属于RuntimeException的异常,所以进行rollbackFor配置还是比较健全的手段@Transactional(rollbackFor = Exception.class)
  • 假如业务有对可检查异常进行处理,则无需加@Transactional(rollbackFor = Exception.class)

@Transactional的原理

@Transactional的失效场景

1.目标方法非public方法
2.目标方法用final/static修饰
  • 无论是JDK动态代理还是CGLib,都需要重写或继承被代理类的方法,final和static不允许重写,所以会失效
  • JDK动态代理是jvm通过继承proxy类的protected InvocationHandler,从而调用其invoke方法。因为java为单继承,所以通过实现接口的方式来实现拓展。这也是为什么jdk动态代理一定要求要接口实现类的原因。
3.同一个类中的方法直接内部调用
  • 方法被事务管理是因为Spring AOP为其生成代理了对象,但是直接this调用同类方法,调用的是被代理类对象的方法,而非代理类方法,因此,在同类中的方法直接内部调用,会导致事务失效
  • 同类调用,不涉及事务传播,相当于A调用了this.B,那么只是把代码融合到一块,并无把B的事务属性给带过去。
4.事务方法所在的类未被Spring管理
  • 使用Spring事务的前提是:对象要被Spring IOC容器管理,需要创建bean实例;打了注解,但是忘了在当前类加@Service注解,导致事务不生效。
5.事务定义了为异步方法
  • 如果使用了 Spring 的 @Async 注解将方法标记为异步执行,@Transactional 注解可能会失效。这是因为异步方法会在新的线程中执行,而事务是与线程绑定的,因此在新的线程中无法获取到原始线程上下文的事务信息,导致事务失效。

编程式事务

上面的这些内容都是基于@Transactional注解的,这种事务使用方式叫做声明式事务;其实,Spring还提供了另外一种创建事务的方式,即通过硬编码的方式使用Spring中提供的事务相关的类来控制事务,这种方式叫做编程式事务;

编程式事务主要有两种用法:

  • 通过事务管理器PlatformTransactionManager控制事务
  • 通过事务模板TransactionTemplate控制事务;常用的是TransactionTemplate,如下:
   @Resource
   private DataSourceTransactionManager transactionManager;
    // 同类方法调用 使用编程式事务
    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    transactionTemplate.execute(transactionStatus -> {
		queryData();
		insertData();
		return Boolean.TRUE;
    });
   });
  1. 避免了由于Spring AOP问题导致的事务失效的问题,如同类事务方法调用;
  2. 可以对代码块加事务,能够更小粒度的更精确的控制事务的范围,一定程度避免事务滥用;
  3. 但是耦合性高,是直接代码侵入业务的操作
  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值