Spring事务和事务传播机制

Spring 编程式事务(Programmatic Transaction)

在 Spring 中,事务管理有两种方式:声明式事务编程式事务。声明式事务使用 @Transactional 注解,而编程式事务是通过 PlatformTransactionManager 手动控制事务的开始、提交和回滚。

编程式事务适合复杂业务逻辑或者动态选择事务行为的场景。


1. 核心概念

  1. PlatformTransactionManager:事务管理器接口,Spring 提供 DataSourceTransactionManager(JDBC)、JpaTransactionManager(JPA)、HibernateTransactionManager(Hibernate)等实现。
  2. TransactionDefinition:事务定义,用来指定事务的隔离级别、传播行为、超时时间等。
  3. TransactionStatus:事务状态对象,用于提交或回滚事务。

2. Maven/Gradle 依赖

以 Spring Boot + JDBC 为例:

<!-- Maven -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

3. 配置事务管理器

Spring Boot 自动配置了 DataSourceTransactionManager,如果是普通 Spring,则可以手动配置:

@Configuration
public class TransactionConfig {

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

4、Spring 编程式事务示例

1. 使用 TransactionTemplate

@Service
public class UserService {

    @Autowired
    private TransactionTemplate transactionTemplate; // 事务模板

    @Autowired
    private UserMapper userMapper; // 数据操作接口

    /**
     * 注册用户示例
     * @param name 用户名
     */
    public void registerUser(String name) {
        // execute 方法会自动开启事务
        transactionTemplate.execute(status -> {
            try {
                // 插入用户
                userMapper.insert(name);

                // 模拟异常回滚
                if(name.equals("error")) {
                    status.setRollbackOnly(); // 手动回滚事务
                }

            } catch (Exception e) {
                // 捕获异常也可以手动回滚
                status.setRollbackOnly();
            }
            return null; // 可返回操作结果,这里不需要
        });
    }
}

2. 使用 PlatformTransactionManager

@Service
public class UserService2 {

    @Autowired
    private PlatformTransactionManager txManager; // 事务管理器

    @Autowired
    private UserMapper userMapper; // 数据操作接口

    /**
     * 手动控制事务示例
     * @param name 用户名
     */
    public void registerUserManual(String name) {
        // 定义事务属性
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); // 隔离级别
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 传播行为

        // 开启事务
        TransactionStatus status = txManager.getTransaction(def);

        try {
            // 执行数据库操作
            userMapper.insert(name);

            // 模拟异常回滚
            if(name.equals("error")) {
                throw new RuntimeException("模拟异常");
            }

            // 提交事务
            txManager.commit(status);

        } catch (Exception e) {
            // 回滚事务
            txManager.rollback(status);
        }
    }
}

5. 总结

  1. 使用 @Autowired 直接注入事务管理器和 TransactionTemplate,无需写构造方法。
  2. TransactionTemplate.execute() 简洁易用,自动管理事务开启和提交。
  3. PlatformTransactionManager 更灵活,可手动控制事务的提交和回滚。
  4. 都可动态设置事务的隔离级别和传播行为。
使用方式特点
TransactionTemplate简洁、易用,事务模板负责开启和提交事务,适合简单逻辑
PlatformTransactionManager灵活、可动态控制事务属性,适合复杂逻辑或动态事务需求

Spring 声明式事务 @Transactional

1. 基本概念

@Transactional 注解可以用于 类或方法,主要作用是:

  • 在方法执行前自动开启事务
  • 方法执行完成后自动提交事务
  • 方法执行中如果出现异常(未捕获),自动回滚事务

推荐在 Service 层 使用,而不是 Controller 层。

@Service
@Transactional
public class UserService {
    // 所有 public 方法默认都有事务
}

2. 基本使用

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Transactional
    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password);
    }
}

说明

  • 默认只对 RuntimeExceptionError 回滚
  • 非运行时异常(Checked Exception)不会回滚

3. rollbackFor 与异常回滚

3.1 默认行为

@Transactional
public void registryUser(String name, String password) throws IOException {
    userInfoMapper.insert(name, password);
    throw new IOException(); // Checked Exception
}
  • 默认不会回滚,因为 IOException 不是 RuntimeException
  • 数据会被提交

3.2 指定回滚异常

@Transactional(rollbackFor = Exception.class)
public void registryUser(String name, String password) throws IOException {
    userInfoMapper.insert(name, password);
    throw new IOException(); // 数据会回滚
}

说明

  • rollbackFor:指定出现哪些异常时事务回滚
  • 可以指定多个异常:rollbackFor = {IOException.class, SQLException.class}

4. 事务隔离级别 isolation

控制事务对其他事务的可见性,Spring 中对应 MySQL 四种常用隔离级别:

Spring 属性含义MySQL 对应
DEFAULT使用数据库默认隔离级别-
READ_UNCOMMITTED读未提交,可能出现脏读READ UNCOMMITTED
READ_COMMITTED读已提交,防脏读READ COMMITTED
REPEATABLE_READ可重复读,防脏读、不可重复读REPEATABLE READ(MySQL 默认)
SERIALIZABLE串行化,防幻读,性能低SERIALIZABLE
@Transactional(isolation = Isolation.READ_COMMITTED)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
}

1. @Transactional(isolation = Isolation.DEFAULT)

  • @Transactional:开启事务。

  • isolation = Isolation.DEFAULT:使用数据库默认隔离级别。

    • 意思是:事务隔离级别由数据库本身决定(MySQL 默认 REPEATABLE_READ)。
    • 优点是简单、兼容性好,不用额外指定。
    • 缺点是对脏读、不可重复读等现象的控制依赖数据库默认设置。

2. @Transactional(isolation = Isolation.READ_UNCOMMITTED)

  • isolation = Isolation.READ_UNCOMMITTED读未提交(Read Uncommitted)

    • 意思是:当前事务可以读取其他事务未提交的数据

    • 可能出现 脏读(Dirty Read)

      • 例如事务 A 插入一条用户记录还没提交,事务 B 读到了这条数据;如果 A 回滚,B 就读到了“错误数据”。
    • 优点:性能最高,因为不加锁限制。

    • 缺点:数据不可靠,一般不推荐在业务系统中使用。


3. @Transactional(isolation = Isolation.READ_COMMITTED)

  • @Transactional:表示这个方法开启事务,方法执行过程要么全部成功,要么失败回滚。

  • isolation = Isolation.READ_COMMITTED读已提交(Read Committed)

    • 意思是:当前事务只能读取其他事务已经提交的数据

    • 可以防止 脏读(Dirty Read)

      • 脏读:一个事务读到了另一个事务未提交的数据,如果对方回滚,你读到的数据就“脏了”。

Spring 的 Isolation 对应 MySQL 隔离级别:

  • READ_UNCOMMITTED → 可能脏读
  • READ_COMMITTED → 防脏读
  • REPEATABLE_READ → 防脏读 + 防不可重复读(MySQL 默认)
  • SERIALIZABLE → 最严格,防幻读,性能低


4. @Transactional(isolation = Isolation.REPEATABLE_READ)

  • isolation = Isolation.REPEATABLE_READ可重复读(Repeatable Read)

    • 意思是:同一事务中多次读取同一条记录,结果是一致的

    • 防止脏读和 不可重复读(Non-Repeatable Read)

      • 不可重复读:同一事务中,第一次读取到的数据,第二次读取可能被其他事务修改。
    • MySQL 默认隔离级别。

    • 缺点:可能出现 幻读(Phantom Read),即一次查询发现新增或删除的数据行。


5. @Transactional(isolation = Isolation.SERIALIZABLE)

  • isolation = Isolation.SERIALIZABLE串行化(Serializable)

    • 意思是:事务完全串行执行,任何事务都会对其他事务“看不到的方式”执行。
    • 可以防止 脏读、不可重复读和幻读,最严格的隔离级别。
    • 优点:数据最安全。
    • 缺点:性能低,容易锁表,吞吐量下降,一般只在特殊场景使用。

5. 事务传播机制 propagation

事务传播机制决定了一个方法被另一个方法调用时,事务如何处理。

类型含义
REQUIRED默认。如果存在事务,加入当前事务;否则新建事务
REQUIRES_NEW总是新建事务,如果有当前事务,挂起当前事务
NESTED嵌套事务,有保存点;父事务回滚,子事务也回滚;子事务回滚不影响父事务
SUPPORTS如果存在事务,就加入事务;否则非事务方式执行
MANDATORY必须在事务中执行,如果没有事务,抛异常
NOT_SUPPORTED以非事务方式执行,如果有事务,挂起事务
NEVER以非事务方式执行,如果有事务,抛异常

1. REQUIRED(默认传播)

@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample() {
    userInfoMapper.insert("Alice", "123");
    logService.requiredExample(); // 子方法共享同一事务
}

@Transactional(propagation = Propagation.REQUIRED)
public void requiredExampleChild() {
    logInfoMapper.insertLog("REQUIRED: 写日志");
    int a = 10 / 0; // 异常,整个事务回滚
}

说明:

  • 父方法和子方法共享同一个事务。
  • 如果子方法出现异常,整个事务都会回滚,包括父方法插入的数据。
  • 场景适合业务逻辑需要整体成功或失败的情况。

2. REQUIRES_NEW(新建事务)

@Transactional(propagation = Propagation.REQUIRED)
public void requiresNewExample() {
    userInfoMapper.insert("Bob", "123");
    logService.requiresNewExample(); // 新事务失败,不影响父事务
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExampleChild() {
    logInfoMapper.insertLog("REQUIRES_NEW: 写日志");
    int a = 10 / 0; // 异常,只回滚子事务
}

说明:

  • 父方法和子方法各自拥有独立事务。
  • 子方法出现异常只回滚自己事务,不影响父方法事务。
  • 场景适合日志记录或审计操作,即使失败也不影响主业务数据提交。

3. NESTED(嵌套事务)

@Transactional(propagation = Propagation.REQUIRED)
public void nestedExample() {
    userInfoMapper.insert("Carol", "123");
    logService.nestedExample(); // 嵌套事务
}

@Transactional(propagation = Propagation.NESTED)
public void nestedExampleChild() {
    logInfoMapper.insertLog("NESTED: 写日志");
    int a = 10 / 0; // 子事务回滚
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

说明:

  • 子事务在父事务中作为“嵌套事务”运行。
  • 子事务回滚只影响自己,不影响父事务提交。
  • 如果父事务出错,父事务回滚时子事务也会回滚。
  • 场景适合部分操作失败可独立回滚,但父事务仍可继续

4. SUPPORTS(存在事务就加入,否则非事务执行)

@Transactional(propagation = Propagation.REQUIRED)
public void supportsExample() {
    userInfoMapper.insert("Eve", "123");
    logService.supportsExample(); // 子方法随父事务执行
}

@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExampleChild() {
    logInfoMapper.insertLog("SUPPORTS: 写日志");
}

说明:

  • 父事务存在 → 子方法参与事务;父事务不存在 → 子方法非事务执行。
  • 场景适合可选事务操作,根据父事务存在与否决定是否参与。

5. MANDATORY(必须在事务中执行)

@Transactional(propagation = Propagation.REQUIRED)
public void mandatoryExample() {
    userInfoMapper.insert("Frank", "123");
    logService.mandatoryExample(); // 父事务存在,子方法执行正常
}

@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExampleChild() {
    logInfoMapper.insertLog("MANDATORY: 写日志");
}

说明:

  • 子方法必须在事务中执行,否则会抛异常。
  • 场景适合关键业务操作必须依赖事务,避免非事务调用。

6. NOT_SUPPORTED(以非事务方式执行)

@Transactional(propagation = Propagation.REQUIRED)
public void notSupportedExample() {
    userInfoMapper.insert("Grace", "123");
    logService.notSupportedExample(); // 子方法以非事务执行
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExampleChild() {
    logInfoMapper.insertLog("NOT_SUPPORTED: 写日志");
    int a = 10 / 0; // 异常不会回滚父事务
}

说明:

  • 子方法不使用事务,如果父方法有事务,会被挂起。
  • 子方法异常不会影响父事务(单独操作数据库)。
  • 适合日志记录、统计、读操作等不希望被父事务回滚影响的场景。

7. NEVER(不允许事务)

@Transactional(propagation = Propagation.NEVER)
public void neverExample() {
    userInfoMapper.insert("David", "123"); // 外部有事务会报异常
}

说明:

  • 方法不允许事务存在,如果调用时外部已有事务,会抛异常。
  • 场景适合非事务操作,如只做简单查询或日志记录,不希望被事务管理。

6. 其他常用属性

属性含义
timeout超时时间,单位秒,超过则回滚
readOnly只读事务,优化性能
noRollbackFor指定哪些异常不回滚

示例:

@Transactional(timeout = 5, readOnly = true, noRollbackFor = NullPointerException.class)
public void readOnlyMethod() {
    userInfoMapper.selectAll();
}

7. 总结

  1. 声明式事务最常用,在 Service 层添加 @Transactional

  2. 异常控制事务回滚

    • 默认只回滚 RuntimeException / Error
    • rollbackFor 可回滚任意异常
    • 捕获异常会导致事务提交 → 可用 setRollbackOnly() 回滚
  3. 隔离级别控制脏读、不可重复读、幻读

  4. 传播机制控制方法间事务的加入、新建或嵌套

  5. NESTED vs REQUIRED:嵌套事务可部分回滚

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值