【Spring】Spring 事务

发布时间:2023年12月19日

Spring 事务

1. 简介

  • 编程式事务:指手动编写程序来管理事务,即通过编写代码的方式直接控制事务的提交和回滚。主要优点是灵活性高,可以按照自己的需求来控制事务的粒度、模式等等。但是,一般编写大量的事务控制代码容易出现问题,对代码的可读性和可维护性有一定影响。
  • 声明式事务:指使用注解或 XML 配置的方式来控制事务的提交和回滚。

下面除了第6节编程式事务,其他都是指声明式事务。

2. Spring事务管理器

Spring声明式事务对应依赖

  • spring-tx :包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
  • spring-jdbc :包含 DataSource 方式事务管理器实现类 DataSourceTransactionManager
  • spring-orm :包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa

Spring声明式事务对应事务管理器接口:

在这里插入图片描述

较常使用的事务管理器是 org.springframework.jdbc.datasource.DataSourceTransactionManager ,将来整合 JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现

DataSourceTransactionManager类中的主要方法:

  • doBegin() :开启事务
  • doSuspend() :挂起事务
  • doResume() :恢复挂起的事务
  • doCommit():提交事务
  • doRollback() :回滚事务

3. 基本使用

  1. 添加依赖
<!-- 声明式事务依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>6.0.6</version>
</dependency>
  1. 开启事务注解:@EnableTransactionManagement
  2. 添加事务管理器到 IoC 容器中
/**
* 装配事务管理实现对象
* @param dataSource
* @return
*/
@Bean
public TransactionManager transactionManager(DataSource dataSource){
    return new DataSourceTransactionManager(dataSource);
}
  1. 在类上或方法上使用 @Transactional
    • 在类上使用,则会影响到类中的每个方法同时,类级别标记的 @Transactional 注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上又设置了 @Transactional 注解。对一个方法来说,离它最近的 @Transactional 注解中的事务属性设置生效。

4. 属性剖析

  1. timeout :默认 -1 ,表示永不超时,超时则会事务回滚

  2. rollbackFor :表示遇到属于该异常的类进行事务回滚,默认 遇到 RuntimeException 及其子类和 Error 及其子类时才会回滚 ,建议设置 rollbackFor = Exception.class 或者 Throwable.class 表示 Exception及其子类Throwable 的异常都会触发回滚,同时不影响Error的回滚

  3. propagation :事务的传播行为,默认 Propagation.REQUIRED ,一共有七种,一般只使用前两种

    事务传播行为类型说明
    REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见也是默认的选择。
    REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
    SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
    MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
    NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
    NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 REQUIRED 类似的操作
  4. isolation :用于设置事务的隔离级别, Isolation.DEFAULT ,一般不修改

  5. readOnly :默认 false,设置为 true 则只能做读操作,用于数据库针对查询优化,一般不使用

5. 声明式事务问题场景

5.1 事务不生效

  1. 未开启事务 :未使用开启事务注解 @EnableTransactionManagement
  2. 未被Spring管理 :被代理类需被Spring管理才能使用 AOP 功能
  3. 访问权限问题 :只有 public 权限修饰的方法才支持声明式事务
  4. 方法用 finalstatic 修饰 :spring 事务底层用了 jdk 动态代理或者 cglib 生成代理类,这两个关键字将导致重写代理类的该方法
  5. 多线程调用 :不同线程获取到的数据库连接不一样,从而必然是两个不同的事务
  6. 表不支持事务 :数据库表引擎是 myisam 或其他不支持事务的引擎
  7. 方法内调用另一个同类方法:如下,add方法中调用了事务方法 updateStatus()
@Service
public class UserService {
 
    @Autowired
    private UserMapper userMapper;
 
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
        updateStatus(userModel);
    }
 
    @Transactional
    public void updateStatus(UserModel userModel) {
        doSameThing();
    }
}

updateStatus() 方法拥有事务的能力是因为 spring aop 生成代理了对象,但这种方式调用了 this 对象的方法,所以 updateStatus 方法不会生成事务。

修改方式:

  1. 新加一个 Service 将方法中挪到新的 Service 中,通过新 Service 调用
  2. 在该 Service 中注入自己,再将调用改为 userService.updateStatus(userModel)
  3. 使用 AopContext.currentProxy() 调用,即改为 ((UserService)AopContext.currentProxy()).updateStatus(userModel)推荐
@Service
public class UserService {
 
    @Autowired
    private UserMapper userMapper;
 
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
        ((UserService)AopContext.currentProxy()).updateStatus(userModel)
    }
 
    @Transactional
    public void updateStatus(UserModel userModel) {
        doSameThing();
    }
}

5.2 事务不回滚

  1. 错误的传播机制 :设置了 propagation = Propagation.NEVER
  2. try…catch…吞了异常:在 catch 中不抛出任何异常或抛了别的异常 spring 事务也不会回滚
  3. rollbackFor参数不合理 :当发生了不属于 rollbackFor 定义的异常类及其子类的异常时,事务将不会回滚

5.3 大事务问题

通常情况下,我们会在方法上加 @Transactional 注解,添加事务功能,比如:

@Transactional
public void add(UserModel userModel) throws Exception {
    query1();
    query2();
    query3();
    roleService.save(userModel);
    update(userModel);
}

@Transactional 注解,如果被加到方法上,有个缺点就是整个方法都包含在事务当中了。上面的这个例子中,在 UserService 类中,其实只有这两行才需要事务,但是这种写法会导致所有的 query() 方法也被包含在同一个事务中。如果 query 方法非常多,调用层级很深,而且有部分查询方法比较耗时的话,会造成整个事务非常耗时,而从造成大事务问题。

6. 编程式事务

5中所提到的问题都是声明式事务的,一般建议少使用声明式事务,但并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用 @Transactional 注解开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。大项目更建议使用基于 TransactionTemplate 的编程式事务,即通过手动编写代码实现的事务。

基于 TransactionTemplate 模板的编程式事务的使用

  1. 注入 TransactionTemplate 模板到 IoC 容器中
@Bean
public DataSource dataSource() {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    dataSource.setUrl(url);
    dataSource.setDriverClassName(driver);
    return dataSource;
}

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager platformTransactionManager) {
    return new TransactionTemplate(platformTransactionManager);
}
  1. 注入到需要使用的类中,并使用如下两个方法之一
    • execute(TransactionCallback<T> action)有返回值
    • executeWithoutResult(Consumer<TransactionStatus> action) 无返回值
@Autowize
private TransactionTemplate transactionTemplate;

@Transactional
public void add(UserModel userModel) throws Exception {
    query1();
    query2();
    query3();
    transactionTemplate.executeWithoutResult((status -> {
        try {
            roleService.save(userModel);
            update(userModel);
        } catch (Exception e) {
            status.setRollbackOnly();
        }
    }));
}
  1. 异常调用 status.setRollbackOnly() 方法进行回滚
文章来源:https://blog.csdn.net/xxx1276063856/article/details/135074643
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。