‌Spring 事务传播属性和隔离级别

‌**一、事务传播属性(Propagation)**‌

传播属性定义事务方法之间的调用规则。Spring 支持 7 种传播行为,核心代码如下:


‌**1. REQUIRED(默认)**‌
  • 规则‌:如果当前存在事务,则加入该事务;否则新建一个事务。

  • 场景‌:适用于大多数业务逻辑(如订单创建和库存扣减)。

  • 代码示例 ‌:

    java 复制代码
    @Service
    public class OrderService {
    
        @Autowired
        private UserService userService;
    
        @Transactional(propagation = Propagation.REQUIRED)
        public void createOrder() {
            // 1. 如果外层无事务,此处新建事务;否则加入外层事务
            jdbcTemplate.update("INSERT INTO orders (amount) VALUES (100)");
    
            // 2. 调用另一个事务方法(默认传播行为为 REQUIRED)
            userService.updateUser(); // 加入当前事务
        }
    }
    
    @Service
    public class UserService {
    
        @Transactional(propagation = Propagation.REQUIRED)
        public void updateUser() {
            // 3. 加入 OrderService 的事务
            jdbcTemplate.update("UPDATE user SET order_count = order_count + 1");
        }
    }

    行为说明 ‌:

    • 如果 createOrder() 方法触发异常,整个事务(订单和用户操作)均会回滚。

‌**2. REQUIRES_NEW**‌
  • 规则‌:无论当前是否存在事务,均新建独立事务。

  • 场景‌:用于日志记录、审计等需独立提交的操作。

  • 代码示例 ‌:

    java 复制代码
    @Transactional(propagation = Propagation.REQUIRED)
    public void processOrder() {
        jdbcTemplate.update("INSERT INTO orders (amount) VALUES (200)"); // 外层事务
    
        // 调用 REQUIRES_NEW 方法
        auditService.logOperation("Order created"); // 新建独立事务
    }
    
    @Service
    public class AuditService {
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void logOperation(String message) {
            // 独立事务:即使外层事务回滚,此操作仍提交
            jdbcTemplate.update("INSERT INTO audit_log (message) VALUES (?)", message);
        }
    }

    行为说明 ‌:

    • processOrder() 方法回滚,logOperation() 的事务仍会提交。

‌**3. NESTED**‌
  • 规则‌:在当前事务中嵌套子事务(通过数据库 Savepoint 实现)。

  • 场景‌:部分操作需要独立回滚(如批量处理中的单条失败)。

  • 代码示例 ‌:

    java 复制代码
    @Service
    public class BatchService {
        @Transactional(propagation = Propagation.REQUIRED)
        public void batchProcess(List<Item> items) {
            for (Item item : items) {
                try {
                    processItem(item); // 嵌套事务
                } catch (Exception e) {
                    // 仅回滚当前 item 的操作
                }
            }
        }
    
        @Transactional(propagation = Propagation.NESTED)
        public void processItem(Item item) {
            // 处理单个 item
        }
    }

    行为说明 ‌:

    • 子事务回滚不影响外层事务,但外层事务回滚会导致子事务一同回滚。
4. 其他传播行为
  • ‌**SUPPORTS**‌:当前存在事务则加入,否则以非事务方式执行。
  • ‌**NOT_SUPPORTED**‌:以非事务方式执行,若当前存在事务则挂起。
  • ‌**MANDATORY**‌:强制要求当前存在事务,否则抛出异常。
  • ‌**NEVER**‌:强制要求当前无事务,否则抛出异常。

‌**二、事务隔离级别(Isolation)**‌

隔离级别控制事务间的数据可见性,解决并发问题。Spring 支持 5 种隔离级别:


‌**1. READ_UNCOMMITTED(读未提交)**‌
  • 问题‌:允许脏读(读取未提交的数据)。

  • 代码示例 ‌:

    java 复制代码
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void transfer() {
        // 事务A:读取未提交数据
        Integer balance = jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE id = 1", Integer.class
        );
        // 若事务B正在修改 balance 但未提交,此处可能读到中间状态
    }

    测试用例 ‌:

    java 复制代码
    @Test
    public void testDirtyRead() throws InterruptedException {
        // 线程1:更新数据但不提交
        new Thread(() -> {
            transactionTemplate.execute(status -> {
                jdbcTemplate.update("UPDATE account SET balance = 200 WHERE id = 1");
                return null; // 不提交,模拟长时间事务
            });
        }).start();
    
        Thread.sleep(500); // 等待线程1执行更新
    
        // 线程2:读取未提交数据
        Integer balance = transactionTemplate.execute(status -> 
            jdbcTemplate.queryForObject("SELECT balance FROM account WHERE id = 1", Integer.class)
        );
        assert balance == 200; // 脏读
    }

‌**2. READ_COMMITTED(读已提交,默认)**‌
  • 解决脏读‌,但允许不可重复读。

  • 代码示例 ‌:

    java 复制代码
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void checkBalance() {
        // 第一次读取(值为100)
        Integer balance1 = jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE id = 1", Integer.class
        );
    
        // 事务B在此处提交更新(balance=200)
    
        // 第二次读取(值变为200,不可重复读)
        Integer balance2 = jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE id = 1", Integer.class
        );
    }

‌**3. REPEATABLE_READ(可重复读)**‌
  • 解决不可重复读‌,通过快照保证多次读取结果一致。

  • 代码示例 ‌:

    java 复制代码
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void validateTransaction() {
        // 第一次读取(值为100)
        Integer balance1 = jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE id = 1", Integer.class
        );
    
        // 事务B在此处提交更新(balance=200)
    
        // 第二次读取(仍为100)
        Integer balance2 = jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE id = 1", Integer.class
        );
    }

‌**4. SERIALIZABLE(串行化)**‌
  • 解决幻读‌,通过锁机制禁止其他事务插入新数据。

  • 代码示例 ‌:

    java 复制代码
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void batchInsert() {
        // 查询符合条件的记录(当前无数据)
        List<Account> accounts = jdbcTemplate.query(
            "SELECT * FROM account WHERE balance > 1000", new AccountRowMapper()
        );
    
        // 事务B尝试插入符合条件的记录会被阻塞
        jdbcTemplate.update("INSERT INTO account (balance) VALUES (2000)");
    }

三、配置与调试技巧
1. 事务配置模板
java 复制代码
@Configuration
@EnableTransactionManagement
public class TransactionConfig {

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

    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager manager) {
        return new TransactionTemplate(manager);
    }
}
2. 调试事务传播
  • 启用事务日志 ‌:

    bash 复制代码
    # application.properties
    logging.level.org.springframework.transaction.interceptor=TRACE
    logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
3. 避免自调用问题
java 复制代码
@Service
public class PaymentService {

    @Autowired
    private PaymentService selfProxy; // 注入代理对象

    @Transactional
    public void methodA() {
        selfProxy.methodB(); // 通过代理对象调用,事务生效
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() { ... }
}

四、总结
  • 传播属性‌:

    • REQUIRED:默认行为,适用于大多数场景。
    • REQUIRES_NEW:独立事务,用于关键日志或审计。
    • NESTED:嵌套事务,支持部分回滚(需数据库支持)。
  • 隔离级别‌:

    • READ_UNCOMMITTED:性能高,容忍脏读。
    • READ_COMMITTED:平衡性能与一致性(默认)。
    • REPEATABLE_READ:保证多次读取一致性(MySQL 默认)。
    • SERIALIZABLE:严格一致,性能最低。
  • 最佳实践‌:

    • 根据业务需求选择传播属性(如财务操作使用 REQUIRES_NEW)。
    • 高并发场景优先使用 READ_COMMITTED,关键数据使用 REPEATABLE_READ
    • 通过日志和测试验证事务行为。
相关推荐
qq_366086221 小时前
union all几个常见问题及其解决方案
数据库
搞不懂语言的程序员3 小时前
备忘录模式深度解析与实战案例
数据库·python·备忘录模式
angushine4 小时前
Gateway获取下游最终响应码
java·开发语言·gateway
爱的叹息4 小时前
关于 JDK 中的 jce.jar 的详解,以及与之功能类似的主流加解密工具的详细对比分析
java·python·jar
一一Null4 小时前
Token安全存储的几种方式
android·java·安全·android studio
AUGENSTERN_dc4 小时前
RaabitMQ 快速入门
java·后端·rabbitmq
晓纪同学4 小时前
C++ Primer (第五版)-第十三章 拷贝控制
java·开发语言·c++
小样vvv4 小时前
【源码】SpringMvc源码分析
java
nzwen6665 小时前
Redis学习笔记及总结
java·redis·学习笔记