springboot 事务(长文解析)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

前言:事务的必要性与应用场景

在开发任何涉及数据持久化的应用时,事务(Transaction)都是一个绕不开的核心概念。想象一个简单的场景:用户向银行发起转账请求,要求将资金从账户A转移到账户B。如果在转账过程中突然断电,如何确保操作要么完全成功(钱同时从A减去并加到B),要么完全失败(钱既不减少也不增加)?这就是事务存在的意义——保证操作的原子性、一致性、隔离性和持久性(ACID)

在Spring Boot框架中,事务管理被高度抽象,开发者无需直接操作底层数据库的事务控制语句(如BEGIN TRANSACTIONCOMMIT),而是通过注解和配置实现声明式事务管理。本文将从基础概念、实现方式、常见问题到实战案例,逐步讲解如何在Spring Boot中高效、安全地使用事务。


一、事务的核心概念与特性

1.1 事务的ACID原则

  • 原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不执行。例如,转账操作若中途失败,资金不会部分扣除或添加。
  • 一致性(Consistency):事务执行前后,数据必须保持合法状态。例如,转账后两个账户的总金额不变。
  • 隔离性(Isolation):多个并发事务之间互不干扰。例如,两个用户同时查询同一账户余额时,不会因操作重叠导致数据混乱。
  • 持久性(Durability):事务提交后,结果永久保存到数据库。即使系统崩溃,数据也能通过日志恢复。

1.2 事务的传播行为(Propagation)

在Spring中,事务的传播行为决定了多个方法调用时事务的关联方式。例如:

  • PROPAGATION_REQUIRED(默认):若当前存在事务,则加入该事务;若不存在,则创建新事务。
  • PROPAGATION_REQUIRES_NEW:无论是否存在事务,都创建新事务。
  • PROPAGATION_SUPPORTS:若当前存在事务,则加入;否则不使用事务。

比喻
可以将传播行为想象为快递员的工作流程:

  • PROPAGATION_REQUIRED如同快递员接到新订单时,若已有同事在配送,则加入团队;否则自己独立配送。
  • PROPAGATION_REQUIRES_NEW则像快递员每次接单都独立行动,即使有同事在场。

二、Spring Boot中事务的实现方式

2.1 基于@Transactional注解的声明式事务

Spring Boot通过@Transactional注解简化了事务管理。以下是一个典型的Service层示例:

@Service
public class AccountService {
    @Autowired
    private AccountRepository accountRepository;

    @Transactional
    public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        Account fromAccount = accountRepository.findById(fromAccountId)
                .orElseThrow(() -> new RuntimeException("账户不存在"));
        Account toAccount = accountRepository.findById(toAccountId)
                .orElseThrow(() -> new RuntimeException("账户不存在"));

        fromAccount.decreaseBalance(amount);
        accountRepository.save(fromAccount); // 操作1

        // 假设此处发生异常(如网络中断)
        if (Math.random() > 0.5) {
            throw new RuntimeException("模拟转账失败");
        }

        toAccount.increaseBalance(amount);
        accountRepository.save(toAccount); // 操作2
    }
}

关键点

  • @Transactional注解标注在方法上,表示该方法内的所有数据库操作均在同一个事务中。
  • 若方法执行过程中抛出运行时异常(RuntimeException及其子类),事务会自动回滚;若抛出受检异常(Checked Exception),需手动配置rollbackFor参数。

2.2 事务的回滚控制

通过@TransactionalrollbackFornoRollbackFor属性,可以自定义回滚策略:

@Transactional(rollbackFor = {SQLException.class, MyCustomException.class})
public void riskyOperation() {
    // 可能抛出SQLException或自定义异常的操作
}

三、常见问题与解决方案

3.1 事务边界问题

问题描述:若在同一个类中调用带有@Transactional的方法,事务可能失效。
原因:Spring的AOP代理机制要求事务方法必须通过代理对象调用,而非直接调用本类方法。

解决方案

  • 将事务方法移至另一个类中,通过依赖注入调用。
  • 或在配置类中启用CGLIB代理(默认为JDK动态代理,仅针对接口生效):
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class AppConfig {}

3.2 嵌套事务与隔离性

当方法A调用方法B,且两者均使用@Transactional时:

  • 若A的传播行为为REQUIRED,B会加入A的事务。
  • 若B的传播行为为REQUIRES_NEW,则B会启动新事务,且独立于A的提交或回滚。

示例

@Service
public class OrderService {
    @Transactional
    public void createOrder() {
        // 创建订单(操作1)
        saveOrder();

        // 新事务:无论订单是否成功,需记录日志
        logService.log("Order created"); // 假设logService方法使用PROPAGATION_REQUIRES_NEW
    }
}

3.3 事务超时与隔离级别

  • 超时设置:通过@Transactional(timeout = 5)限制事务执行时间,超时后自动回滚。
  • 隔离级别:通过isolation参数指定(如Isolation.READ_COMMITTED),避免脏读、幻读等问题。

四、实战案例:用户注册流程

4.1 场景描述

用户注册时需完成以下操作:

  1. 插入用户基本信息到user表。
  2. 插入用户角色信息到user_role表。
  3. 发送注册邮件。

若步骤1或2失败,需回滚数据库操作;但发送邮件属于外部服务,需单独处理。

4.2 代码实现

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private UserRoleRepository userRoleRepository;
    @Autowired
    private MailService mailService;

    @Transactional
    public void registerUser(User user, Role role) {
        // 1. 保存用户基本信息
        User savedUser = userRepository.save(user);

        // 2. 保存角色信息
        UserRole userRole = new UserRole(savedUser.getId(), role);
        userRoleRepository.save(userRole);

        // 3. 发送邮件(外部服务,不参与事务)
        mailService.sendWelcomeEmail(user.getEmail());
    }
}

关键点

  • 步骤1和2在同一个事务中,若任一操作失败(如主键冲突),数据库会回滚。
  • 步骤3的邮件发送若失败,需通过补偿机制(如重试队列)处理,不影响数据库一致性。

五、性能与最佳实践

5.1 减少事务范围

  • 问题:长时间持有事务锁可能导致数据库性能下降或死锁。
  • 解决方案:将事务粒度控制在最小必要范围内,例如将日志记录或外部调用移出事务。

5.2 异常处理与回滚策略

  • 避免在事务方法中捕获异常:否则Spring无法感知错误并触发回滚。
  • 显式指定回滚策略:对于自定义异常,务必通过rollbackFor声明。

结论:事务管理是系统可靠性的基石

通过本文的讲解,读者应能掌握Spring Boot中事务的基本概念、实现方法及常见问题的解决方案。事务管理并非简单的“加个注解”就能完成,而是需要结合业务场景、数据库特性及系统架构综合设计。

在实际开发中,建议遵循以下原则:

  • 明确事务边界:确保事务仅包含必须的操作。
  • 合理配置传播行为:避免因嵌套事务导致的性能问题。
  • 监控与日志:记录事务执行时间、回滚原因等,便于后续排查。

通过持续实践与优化,开发者能够将事务管理转化为提升系统健壮性的核心能力。

最新发布