‌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
    • 通过日志和测试验证事务行为。
相关推荐
YangYang9YangYan5 分钟前
2026 校招市场数据分析 JD 拆解,SQL 要求、工具与面试考点
数据库·人工智能·数据分析
重生之小比特7 分钟前
【Java SE】抽象类和接口
java·开发语言
Lyyaoo.11 分钟前
【设计模型】策略模式
java
风哥2号13 分钟前
数据库教程FGMT40‑MySQL性能优化之性能基准测试
数据库·mysql
譕痕20 分钟前
Idea 集成 智能体开发工具“千问(qwen)”
java·ide·spring·语言模型
苍梧问道24 分钟前
SQL Server阻塞与死锁排查实战:从DMV到根因分析
数据库
用户81818706274626 分钟前
第27章 消息丢失/重复消费的全链路排查(生产者→Broker→消费者)
java·后端
shirsl38 分钟前
数据开发每日面试题 Day 5
大数据·数据库·sql·big data
索隆zoro41 分钟前
Army 对 jOOQ
java·后端
二炮手亮子1 小时前
java责任链模式
java