‌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。
    • 通过日志和测试验证事务行为。
相关推荐
阳光九叶草LXGZXJ5 小时前
达梦数据库-报错-15-列【XXX】长度超出定义
linux·运维·数据库·sql·学习
字节渡客6 小时前
Redis键明明过期了,业务还在读到旧数据
数据库·redis·spring
daidaidaiyu7 小时前
ThingsBoard 集群的核心逻辑源码分析
java
我可能是个假开发7 小时前
FTP Unicode 文件名导致 `550` 的问题分析与解决方案
java·开发语言·spring
caoerzhong8 小时前
中小企业上 WMS 该先上哪几块:JeeWMS 开源 Java 仓库管理系统的分批上线清单
java·python·开源
OnlineProxy8 小时前
亚马逊多账号运营:如何在规避“关联封号”风险的同时实现电商规模化扩张
服务器·数据库·redis
【JAVA】玩家9 小时前
Spring核心原理全解析:从零到生产实战
java·后端·spring
Wang's Blog9 小时前
Java框架 SpringCloud 快速入门: 实现 Feign 最佳实践(抽取方式)
java·spring cloud
辻弋20110 小时前
五年前的旅行视频糊成马赛克?Video2X用Real-ESRGAN逐帧重建细节,但只支持Windows、集显用户建议直接放弃
服务器·数据库·windows·游戏引擎·电脑
MandalaO_O10 小时前
IDEA 开发(快捷键 + 调试 + 序列化)
java·ide·intellij-idea