Spring 声明式事务

本文详细介绍了Spring框架中的事务管理,包括编程式和声明式事务的区别,事务隔离级别的功能,以及事务传播行为的设定。还讨论了事务常用配置,如只读模式、回滚规则和注解的使用方法及注意事项。

Spring 的事务管理分为两种:

  • 编程式事务管理:通过编写代码实现的事务管理,包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚。
  • 声明式事务管理:建立在 AOP 之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务管理不需要入侵代码,并且提供了 @Transactional 注解更为简单、快捷地进行事务操作,推荐使用。

事务属性介绍

事务隔离级别(Isolation)

事务隔离级别指的是若干个并发的事务之间的隔离程度,一般可分为以下五个级别:

  • DEFAULT:默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这个值就是READ_COMMITTED。
  • READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。
  • READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
  • SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务传播行为(Propagation)

所谓事务传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时指定的一个事务性方法的执行行为。下面举个例子进行说明:

@Transactional  
public void test1() {  
    test2();   // test1() 调用 test2()
}

@Transactional(propagation = xxx)
public void test2() {
    // 在开始当前事务之前,也就是 test2 事务之前,调用者 test1 已经存在事务管理,
    // 此时使用 propagation 属性指定 test2 事务的执行行为
}

一般可分为以下六种行为:

  • REQUIRED:默认值。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED。


声明式事务介绍

事务常用配置

  • readOnly:该属性用于设置当前事务是否为只读事务,设置为 true 表示只读,false 则表示可读写,默认值为 false。
@Transactional(readOnly=true)
  • rollbackFor:该属性用于设置需要进行回滚操作的异常类数组,当方法中抛出指定异常时,则进行事务回滚操作。
// 指定单一异常类 
@Transactional(rollbackFor=RuntimeException.class);
// 指定多个异常类
@Transactional(rollbackFor={RuntimeException.class, Exception.class});
  • rollbackForClassName:和 rollbackFor 类似,该属性用来设置需要进行回滚操作的异常类名称数组。
// 指定单一异常类名称 
@Transactional(rollbackForClassName="RuntimeException")
// 指定多个异常类名称
@Transactional(rollbackForClassName={"RuntimeException","Exception"})
  • noRollbackFor:该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。
// 指定单一异常类 
@Transactional(noRollbackFor=RuntimeException.class)
// 指定多个异常类 
@Transactional(noRollbackFor={RuntimeException.class, Exception.class})。
  • noRollbackForClassName:和 noRollbackFor 类似,该属性用来设置不需要进行回滚操作的异常类名称数组。
// 指定单一异常类名称 
@Transactional(noRollbackForClassName="RuntimeException")
// 指定多个异常类名称 
@Transactional(noRollbackForClassName={"RuntimeException","Exception"})
  • propagation: 该属性用于设置事务的传播行为。
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
  • isolation:该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置。

  • timeout:该属性用于设置事务的超时秒数,默认值为 -1 表示永不超时。

事务回滚规则

Spring 事务管理器会捕获当前事务内未处理的异常,然后依据规则决定是否要进行事务回滚操作。

默认配置下,Spring 事务管理器只有在抛出的异常为 unchecked 异常时才会进行回滚该事务,也就是说抛出的异常必须为 RuntimeException 或者 Error 的子类。

所以,在使用声明式事务时一般要明确抛出哪些异常时会进行回滚事务,包括 checked 异常;也可以明确抛出哪些异常不进行回滚事务。

需要注意:Spring 事务管理器必须捕获当前事务内未处理的异常,才会进行事务回滚操作。也就是说,要想进行事务回滚操作,不能在方法内部使用try...catch自行将异常处理,是不生效的,如果想再次生效必须进行手动回滚或者手动抛出异常,比如:throw new RuntimeException()

XML 方式使用

 在 spring-mybatis.xml 中配置声明式事务

<!-- 编写通知:对事务进行增强(通知),需要编写对切点和具体执行事务细节
     属性:id:唯一标识
          transaction-manager:指定事务管理器 id,默认值就是 transactionManager
-->
<tx:advice id="advice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 事务配置
             属性:name:对哪些方法起作用,例如:insert* 表示所有以 insert 开头的方法名称。
                        一般只需要对增、删、改方法添加事务
                  rollback-for:指定需要进行事务回滚的异常类,默认是 uncheck 异常
             其它属性一般默认即可
        -->
        <tx:method name="insert*" rollback-for="java.lang.Exception"/>
        <tx:method name="delete*" rollback-for="java.lang.Exception"/>
        <tx:method name="update*" rollback-for="java.lang.Exception"/>
    </tx:attributes>
</tx:advice>

<!-- 编写 aop,对目标生成代理,进行事务的通知 -->
<aop:config>
    <!-- 配置切点表达式 -->
    <aop:pointcut id="txPointcut" expression="execution (* com.example.service.impl.*ServiceImpl.*(..))"/>
    <!-- 将切点和事务的通知整合 -->
    <aop:advisor advice-ref="advice" pointcut-ref="txPointcut"/>
</aop:config>

 在 CustomerMapper 接口中添加插入方法

@Insert("insert into t_customer(username, job, phone) " +
        "values(#{username}, #{job}, #{phone})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Customer customer);

在 ICustomerService 接口中添加方法定义

// 正常插入
int insertTest1(Customer customer);
// 插入后抛出异常
int insertTest2(Customer customer);

 在 CustomerServiceImpl 类中添加方法实现

@Override
public int insertTest1(Customer customer) {
    return customerMapper.insert(customer);
}

@Override
public int insertTest2(Customer customer) {
    int count = customerMapper.insert(customer);
    int i = 1 / 0;    // By Zero,会抛出一个 RuntimeException 子类异常
    return count;
}

 进行测试:

@Test
public void insertTest() {
    Customer customer1 = new Customer(null, "test1", "worker", "44444444444");
    System.out.println("insertTest1:" + customerService.insertTest1(customer1));
    System.out.println();

    Customer customer2 = new Customer(null, "test2", "worker", "55555555555");
    System.out.println("insertTest2:" + customerService.insertTest2(customer2));
}
==>  Preparing: insert into t_customer(username, job, phone) values(?, ?, ?)
==> Parameters: test1(String), worker(String), 44444444444(String)
<==    Updates: 1
insertTest1:1
    
==>  Preparing: insert into t_customer(username, job, phone) values(?, ?, ?)
==> Parameters: test2(String), worker(String), 55555555555(String)
<==    Updates: 1

java.lang.ArithmeticException: / by zero

查看数据库,可以发现只有 insertTest1() 方法的记录插入成功:


注解方式使用

使用注解方式的前提是开启事务的注解支持,有两种方式配置:

 XML 方式开启事务的注解支持

<!-- 开启事务的注解支持
     transaction-manager 属性:指定事务管理器 id,默认值就是 transactionManager
-->
<tx:annotation-driven transaction-manager="transactionManager"/>

 注解方式开启事务的注解支持

// 其它注解
@EnableTransactionManagement   // 开启事务的注解支持,会自动加载事务管理器
public class SpringConfig {
    // 注册各种 bean
}

开启事务的注解支持之后,可以不用使用 XML,而是直接将 @Transactional 注解作用在类或者方法上。如下所示:

// rollback-for 属性:指定需要进行事务回滚的异常类,默认是 uncheck 异常
@Override
@Transactional(rollbackFor = Exception.class)
public int insertTest1(Customer customer) {
    return customerMapper.insert(customer);
}

@Override
@Transactional(rollbackFor = Exception.class)
public int insertTest2(Customer customer) {
    int count = customerMapper.insert(customer);
    int i = 1 / 0;    // By Zero,会抛出一个 RuntimeException 子类异常
    return count;
}

对上述方法进行测试:

@Test
public void insertTest2() {
    Customer customer1 = new Customer(null, "test3", "worker", "66666666666");
    System.out.println("insertTest1:" + customerService.insertTest1(customer1));
    System.out.println();

    Customer customer2 = new Customer(null, "test4", "worker", "77777777777");
    System.out.println("insertTest2:" + customerService.insertTest2(customer2));
}
==>  Preparing: insert into t_customer(username, job, phone) values(?, ?, ?)
==> Parameters: test3(String), worker(String), 66666666666(String)
<==    Updates: 1
insertTest1:1
    
==>  Preparing: insert into t_customer(username, job, phone) values(?, ?, ?)
==> Parameters: test4(String), worker(String), 77777777777(String)
<==    Updates: 1

java.lang.ArithmeticException: / by zero

查看数据库,可以发现仍然只有 insertTest1() 方法的记录插入成功:


注意事项

注意1:@Transactional 注解仅对 public 方法有效,如果应用在 private 和 protected 方法上是不起作用的。

注意2:@Transactional 注解可以应用在类上,也可以应用在方法上;如果同时应用在类和方法上,方法上的配置会覆盖类上的配置。

注意3:@Transactional 注解默认捕获 unchecked 异常时才会进行事务回滚,一般需要使用 rollbackFor 指定异常。

注意4:@Transactional 注解的方法仅被外部类方法调用时才会起有效,同一个类方法间的内部调用是不起作用的。这是因为该注解的原理是 AOP,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。

  • 同一个类中没有 @Transactional 注解的方法 A 内部调用有 @Transactional 注解的方法 B,B 方法的事务被忽略,相当于调用普通方法,不发生回滚。
  • 同一个类中有 @Transactional 注解的方法 A 内部调用有 @Transactional 注解的方法 B,B 方法的事务被忽略,相当于调用普通方法,只有方法 A 的事务生效。

也就是说,一个方法内部调用同一个类的另一个方法,是没有通过代理类的,事务也就无法生效。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值