Spring 事务

1、什么是事务?

它指的是一组操作数据库的动作集合,这些操作要么全部成功,要么全部失败,从而保持数据的一致性和完整性。

1. 事务的特性 - ACID

  1. 原子性(Atomicity)

    事务中不可分割的一个单元,事务中的全部操作要么全部成功,要么全部失败;

  2. 一致性(Consistency)

    表示事务完成时,所有的数据必须保持一致; 事务必须使数据库从一个一致性状态变换到另一个一致性状态。一致性与原子性是密切相关的,因为事务的原子性确保了数据的一致性。

  3. 隔离性(Isolation)

    一个事务的执行不受其他事务的影响,也就是一个事务的内部操作和使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能相互影响;

  4. 持久性(Durability)

    也称为永久性,指一个事务一旦提交,对数据库中数据的修改应该是永久性的,即便系统故障也不会丢失。

2. 事务的隔离级别

1、读未提交(Read Uncommitted)

读未提交,这是事务最低的隔离级别,该隔离级别表示一个事务可以读取另一个事务修改
但还没有提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读,因此很少使用该隔离级别。

2、读已提交(Read Commited)

读已提交,保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务
不能读取该事务未提交的数据。
这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读,
这也是大多数情况下的推荐值。

3、可重复读

可重复读 ,这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免不可重复读的情况产生。
该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。

4、序列化(Serializable)

这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰;
也就是说,该级别可以防止脏读、不可重复读以及幻读。
但是这将严重影响程序的性能。通常情况下也不会用到该级别。

5、默认隔离级别(DEFAULT)

ISOLATION_DEFAULT: 默认隔离级别 ,这是一个PlatfromTransactionManager 
默认的隔离级别,使用数据库默认的事务隔离级别;
oracle默认的是.:READ_COMMITTED ,mysql默认的是:REPEATABLE_READ

3. 事务关键词

  • 脏读:指一个事务B读取了一个未提交事务A的数据,即当一个事务A正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务B也访问这个数据,读取了同一数据,并基于这个“脏”数据做了进一步的处理。如果事务A最终回滚了,那么这个事务B基于脏数据所做的所有工作都将无效,这可能导致数据不一致的问题。

  • 幻读:指一个事务读取一个范围的记录时,另一个并发事务插入了满足该查询条件的新记录,导致第一次查询和第二次查询的结果集不一致。幻读问题不仅涉及数据的修改,还涉及数据的增加或删除。例如,事务A查询了某个范围内的记录,然后事务B在该范围内插入了新的记录并提交,之后事务A再次查询同一范围时,会发现多了一些之前没有的记录,这就是幻读。

  • 不可重复读:指一个事务读取表中的某一行数据,多次读取结果不同,由于其他事务的并发更新,导致在不同时间点读取到的数据不一致。即,事务A在第一次读取某个数据集合后,事务B对该集合中的某些数据进行了修改并提交,然后事务A再次读取该集合时,发现数据已经发生了变化。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

4. 总结

隔离级别解决脏读解决不可重复读解决幻读
Read Uncommitted(读未提交)不会不会不会
Read Commited(读已提交)不会不会
Repeatable read(可重复读)不会
Serializable(序列化)

2、Spring 事务

1. 事务分类

Spring 支持编程式事务声明式事务两种方式

  • 编程式事务:编程式事务管理需要开发者在代码中显式调用事务管理的方法来控制事务的边界,这种方式相对繁琐,在实际开发中很少使用;

  • 声明式事务:声明式事务管理通过注解或 XML 配置来实现,无需在代码中显式调用事务管理的方法。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能插入到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。优点是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过 @Transactional 注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。

2. 声明式事务(xml)

  1. 引入依赖

<!-- spring-jdbc传递依赖了spring-tx -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>6.1.1</version>
</dependency>
​
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>6.1.1</version>
</dependency>
  1. Spring 配置文件

<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="driverClassName" value="${jdbc.driver}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}" />
  <property name="password" value="${jdbc.password}" />
</bean>
​
<!-- 配置事务管理器 -->
<bean id="txManager" 
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
</bean>
​
<!-- 事务通知(增强) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
  <tx:attributes>
    <!-- 对哪些方法执行事务,配置事务定义信息(隔离级别、传播行为、是否只读、超时时间、回滚机制)-->
    <!-- 1. name属性: 为哪些方法添加事务 -->
    <!-- 2. isolation属性: 使用什么隔离级别, 这里有5种配置方式 -->
    <!-- 3. timeout属性:-1:表示永不超时;3:3秒钟超时 -->
    <!-- 4. read-only属性 -->
    <!-- 5. rollback-for属性:指定对哪些异常起作用 -->
    <!-- 6. no-rollback-for属性:指定对哪些异常不起作用 -->
    <!-- 7. propagation属性:配置事务的传播行为 -->
    <tx:method name="add*" 
               isolation="DEFAULT" 
               propagation="REQUIRED" />
    <tx:method name="insert*" 
               isolation="DEFAULT" 
               propagation="REQUIRED"
               rollback-for="Throwable" 
               timeout="5"/>
    <tx:method name="delete*" isolation="DEFAULT" 
               propagation="REQUIRED" />
    <tx:method name="query*" isolation="READ_UNCOMMITTED" 
               read-only="true" />
  </tx:attributes>
</tx:advice>
​
<!-- aop配置 -->
<aop:config>
  <!-- 第一个*: 任意方法返回类型 -->
  <!-- 第二个*: 任意类名 -->
  <!-- 第三个*: 任意方法名称 -->
  <!-- 第一个..: 表示com.user.service包及其任意层级子包 -->
  <!-- 第二个..: 任意方法参数 -->
  <aop:pointcut id="txPoint" 
                expression="execution(* com.user.service..*.*(..))" />
  <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint" />
</aop:config>

3. 声明式事务(注解)

<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>
​
<!-- 配置事务管理器 -->
<bean id="txManager" 
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
​
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="txManager" />

注意

  • 使用 @Transactional 注解:在需要事务支持的方法或类上添加 @Transactional 注解。Spring 会自动在这些方法执行时开启事务,并在方法执行完毕后根据方法的执行结果(正常完成或抛出异常)来决定提交或回滚事务。

  • 默认情况下,Spring 的事务回滚只针对运行时异常(RuntimeException)和错误(Error),如果希望 Spring 能够对所有类型的异常都进行回滚,可以设置 @Transactional 注解的 rollbackFor 属性为 Exception.class

  • 使用 @Transactional 注解时,注意不要在同一个类中调用另一个带有 @Transactional 注解的方法,因为这种情况下 Spring 的代理机制可能无法生效,导致事务控制失效。可以通过将事务方法移到另一个类中的方式来避免这个问题。

  • @Transactional 注解的事务传播行为、隔离级别、超时时间等属性可以灵活配置,以满足不同的业务需求。

    • isolation => Isolation(5种) - 隔离级别

    • timeout => int: 秒为单位,-1表示不超时;5:表示5秒超时--超时时间

    • readOnly => boolean 用于加速查询效率- 只读属性

    • rollbackFor => Class[] 表示哪些异常需要回滚

    • rollbackForClassName => String[] 填写要回滚的异常的全限定类名称 - 回滚策略

    • noRollbackFor => Class[] 表示哪些异常不需要回滚

    • noRollbackForClassName => String[] 填写不需要回滚的异常的全限定类名称

    • transactionManager => String 指定事务管理器bean的id

    • propagation => Propagation 事务传播机制

3、Spring事务的传播机制

  • REQUIRED:表示如果当前存在一个事务,则加入该事务,否则将新建一个事务;这个是Spring事务默认传播行为

    • 如果A、B两个方法都加了@Transactional注解,默认是REQUIRED传播行为。那么如果A方法调用B方法,它们会共用一个事务,因为默认会使用同一条连接,相当于一个事务里执行。

  • REQUIRES_NEW:表示不管是否存在事务,都创建一个新的事务,把原来的事务挂起,新的事务执行完毕,继续执行老的事务;

    • 如果A、B方法都有事务,但B配置了REQUIRES_NEW,那么B会起一个新的事务,暂停A的事务。等B事务结束,才恢复A的事务。

  • SUPPORTS:表示如果当前存在事务,就加入该事务;如果当前没有事务,那就不使用事务;

    • 如果A方法没有事务(没有加@Transactional注解),B方法配置了SUPPORTS传播行为,那么B方法也会以非事务方式执行。

    • 如果A方法存在事务(加了@Transactional注解),B方法配置了SUPPORTS传播行为,那么B方法会挂起自己的事务,加入到A方法的事务来执行。

  • NOT_SUPPORTED:表示不使用事务;如果当前存在事务,就把当前事务暂停,以非事务方式执行;

    • 如果A方法没有事务(没有加@Transactional注解),B方法配置了NOT_SUPPORTED传播行为,那么B方法也会以非事务方式执行。

    • 如果A方法存在事务(加了@Transactional注解),B方法配置了NOT_SUPPORTED传播行为,那么B方法会挂起自己的事务,以非事务方式加入到A方法的事务来执行。

  • MANDATORY:表示必须在一个已有的事务中执行,如果当前没有事务,则抛出异常;

    • 如果A方法没有事务(没有加@Transactional注解),B方法配置了MANDATORY传播行为,那么B方法将会抛出异常。

    • 如果A方法存在事务(加@Transactional注解),B方法配置了MANDATORY传播行为,那么B方法将加入到该存在的事务来执行。

  • NEVER:表示以非事务方式执行,如果当前存在事务,则抛出异常;

    • 如果A方法没有事务(没有加@Transactional注解),B方法配置了NEVER传播行为,那么B方法会正常以非事务方式执行。

    • 如果A方法存在事务(加了@Transactional注解),B方法配置了NEVER传播行为,那么B方法将会抛出异常。

  • NESTED:这个是嵌套事务; 如果当前存在事务,则在嵌套事务内执行; 如果当前不存在事务,则创建一个新的事务; 嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务但外部事务回滚将导致嵌套事务回滚;

    • 如果A方法没有事务(没有加@Transactional注解),B方法配置了NESTED传播行为,那么B方法将启动一个新的嵌套事务执行。

    • 如果A方法存在事务(加@Transactional注解),B方法配置了NESTED传播行为,那么B方法将会在嵌套事务内执行。

4、事务生效情况

  • 同一个Service中一个没有事务的方法调用了一个有事务的方法,有事务的方法执行异常,事务不回滚

@Service
public class UserServiceImpl implements UserService {

  @Autowired
  UserMapper userMapper;

  @Autowired
  UserService userService;

  @Transactional(propagation = Propagation.REQUIRED, 
                 isolation = Isolation.DEFAULT, 
                 rollbackFor = Exception.class)
  @Override
  public void increase(int id, int money) {
    userMapper.updateMoney(id, money);
    
    // 一个没有事务的方法调用了一个有事务的方法(this), 有事务的方法抛出了异常, 不回滚
    decrease(2, 100);
    
    // 会回滚
     /*
     * 解决办法: 
     * 1、注入自己(这个自己就是代理对象)
     * 
     * 2、配置<aop:aspectj-autoproxy export-proxy="true" /> 
     *   设置export-proxy="true",允许开发者获取代理对象
     *   UserService proxy = (UserService) AopContext.currentProxy();
     */
    // <aop:aspectj-autoproxy export-proxy="true" />
    UserService proxy = (UserService) AopContext.currentProxy();
    proxy.decrease(2, 100)
    int i = 1 / 0;
  }

  @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT, rollbackFor = Exception.class)
  @Override
  public void decrease(int id, int money) {
    userMapper.updateMoney(id, -money);
  }

}
  • 抛出非运行时异常

//解决方法:使用 rollbackFor 指定哪些异常需要回滚
@Transactional(rollbackFor = Exception.class)
@Override
public void transfer(int increaseId, int decreaseId, int money) throws SQLException {
  userService.increase(increaseId, money);
  userService.decrease(decreaseId, money);
  throw new SQLException("111111111111111");
}
  • 方法不是public修饰的

  • 使用try catch捕获异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值