当前位置: 首页 > news >正文

讲讲Spring事务

讲讲 Spring 事务

Spring 事务简介

Spring 事务管理是 Spring 框架中的一项重要功能,用于简化企业级应用中的事务管理操作。它通过声明式(Declarative)或编程式(Programmatic)的方式,帮助开发者保证数据的一致性、完整性和隔离性。

事务的核心目标是确保一组操作(如数据库读写)要么全部成功,要么全部失败(回滚),从而保证系统数据的可靠性。

事务的基本概念

  1. 事务(Transaction):事务是一组操作的集合,这些操作作为一个单元被执行。事务需要满足 ACID 特性:
    • A(原子性):事务是一个不可分割的工作单元,要么全部执行,要么全部回滚。
    • C(一致性):事务完成后,数据库必须从一个一致性状态变为另一个一致性状态。
    • I(隔离性):多个事务并发执行时,一个事务的执行不会被其他事务干扰。
    • D(持久性):事务一旦提交,其对数据库的修改就是永久的。
  2. 事务传播(Transaction Propagation):定义事务如何传播到被调用的方法。例如,如果一个方法已经启动了事务,另一个方法是否应该加入现有事务。
  3. 事务隔离级别(Transaction Isolation Level):定义多个事务并发执行时的行为,用于解决脏读、不可重复读、幻读等问题。

Spring 事务管理的方式

Spring 提供了两种事务管理方式:

  1. 声明式事务管理
    • 推荐的方式。
    • 使用 @Transactional 注解或 XML 配置事务管理。
    • 简单易用,解耦了事务管理和业务逻辑代码。
  2. 编程式事务管理
    • 通过手动调用 Spring 的事务管理 API(TransactionTemplatePlatformTransactionManager)实现。
    • 灵活但侵入性强,一般不推荐。

1、声明式事务管理

基于注解的声明式事务

@Transactional 注解是 Spring 中声明事务的核心注解,通常标注在类或方法上。

@Service
public class UserService {

    @Transactional
    public void createUser(String name, int age) {
        // 插入用户数据
        userDao.insert(name, age);

        // 模拟一个异常,触发回滚
        if (age < 0) {
            throw new IllegalArgumentException("年龄不能为负数!");
        }

        // 插入日志数据
        logDao.insert("用户创建成功:" + name);
    }
}
  • 常见属性:

    • propagation:设置事务的传播行为。
    • isolation:设置事务的隔离级别。
    • timeout:设置事务的超时时间(秒)。
    • readOnly:设置事务是否为只读。
    • rollbackFor:指定哪些异常会触发事务回滚。
    • noRollbackFor:指定哪些异常不会触发事务回滚。
事务传播行为(Propagation)

事务传播行为定义当前方法事务的处理方式。常见值如下:

描述
REQUIRED默认值。如果当前有事务存在,则加入当前事务;如果没有,则新建一个事务。
REQUIRES_NEW总是新建一个事务。如果当前有事务存在,则暂停当前事务。
SUPPORTS如果当前有事务存在,则加入当前事务;如果没有事务存在,则以非事务方式执行。
NOT_SUPPORTED总是以非事务方式执行。如果当前有事务存在,则暂停当前事务。
MANDATORY必须在一个事务中执行,如果当前没有事务存在,则抛出异常。
NEVER总是以非事务方式执行,如果当前有事务存在,则抛出异常。
NESTED如果当前有事务存在,则在当前事务中嵌套一个子事务(依赖于底层数据库是否支持保存点)。
事务隔离级别(Isolation)

事务隔离级别用于控制并发事务之间的相互影响。Spring 支持以下五种隔离级别:

描述
DEFAULT默认隔离级别,使用底层数据库的默认设置。(Mysql 默认是 可重复读)
READ_UNCOMMITTED(未提交读)允许脏读、不可重复读和幻读。性能最高,数据安全性最低。
READ_COMMITTED(提交读)防止脏读,但可能发生不可重复读和幻读。
REPEATABLE_READ(可重复读)防止脏读和不可重复读,但可能发生幻读。
SERIALIZABLE(串行化)防止脏读、不可重复读和幻读。性能最低,但数据安全性最高。

各级别比较:

隔离级别脏读不可重复读幻读性能
READ UNCOMMITTED
READ COMMITTED×较高
REPEATABLE READ×××
SERIALIZABLE×××

2、编程式事务管理

编程式事务管理需要手动控制事务的开启、提交和回滚:

@Service
public class UserService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void createUser(String name, int age) {
        TransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);

        try {
            // 插入用户数据
            userDao.insert(name, age);

            // 模拟一个异常
            if (age < 0) {
                throw new IllegalArgumentException("年龄不能为负数!");
            }

            // 插入日志数据
            logDao.insert("用户创建成功:" + name);

            // 提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(status);
            throw e;
        }
    }
}

事务回滚的规则

  1. 默认行为

    • Spring 默认会对所有未捕获的运行时异常(RuntimeExceptionError)进行事务回滚。
    • 对受检异常(CheckedException)不回滚。
  2. 自定义回滚规则

    • 使用 @TransactionalrollbackFornoRollbackFor 属性可以自定义回滚行为。
    @Transactional(rollbackFor = Exception.class)
    public void someMethod() {
        // 代码逻辑
    }
    
  • @Transactional 注解中如果不配置 rollbackFor 属性,那么事务只会在遇到 RuntimeException 的时候才会回滚
  • 加上 rollbackFor=Exception.class, 可以让事务在遇到非运行时异常时也会回滚。

注意事项

  1. 方法必须是由 Spring 管理的【代理对象】调用,否则 @Transactional 不会生效。
  2. 事务传播与嵌套事务:理解传播机制,避免事务失控。
  3. 正确处理异常:捕获异常(不抛出)可能会导致事务失效。
  4. readOnly 属性:查询操作应设置为 readOnly = true,以优化性能。

Spring 的事务是通过代理类实现的。

什么情况下事务会失效?

导致事务失效的主要原因

1、非 public 方法

原因

  • Spring 的 @Transactional 注解只在 public 方法 上有效,因为 Spring 的事务管理基于 AOP(代理模式)。如果 @Transactional 注解在非 public 方法上,代理类无法拦截调用,事务不会生效。

解决方法

  • 确保带有 @Transactional 注解的方法是 public

2、自身方法调用(内部方法调用)

原因

  • Spring 的事务是通过代理类实现的。如果一个方法调用同类中的另一个方法(即 内部方法调用),此时不会通过代理类,而是直接调用实际方法,导致事务失效。

示例

@Service
public class TransactionService {
    @Transactional
    public void methodA() {
        methodB(); // 不会经过代理,事务失效
    }

    @Transactional
    public void methodB() {
        // 事务代码
    }
}

解决方法

  1. 将需要事务管理的方法提取到 其他类,通过类间调用确保事务代理生效。

  2. 使用 Spring 的 AopContext 手动获取代理对象调用方法:

    public void methodA() {
        ((TransactionService) AopContext.currentProxy()).methodB();
    }
    

3、异常未被正确传播

原因

  • Spring 默认只会回滚 运行时异常(RuntimeException错误(Error
  • 如果方法抛出了非运行时异常(如 CheckedException),Spring 不会自动回滚事务,导致事务提交。

示例

@Transactional
public void updateData() throws IOException {
    throw new IOException("非运行时异常");
}

解决方法

  1. 显式配置事务的回滚规则:

    @Transactional(rollbackFor = IOException.class)
    public void updateData() throws IOException {
        // ...
    }
    
  2. 将非运行时异常包装为运行时异常重新抛出:

    throw new RuntimeException(e);
    

4、捕获异常后不抛出

原因

  • 如果异常被 try-catch 块捕获并处理(不抛出),Spring 事务管理器无法感知到异常发生,事务不会回滚。

示例

@Transactional
public void updateData() {
    try {
        // 事务操作
        updateTable1();
        updateTable2(); // 抛出异常
    } catch (Exception e) {
        // 异常被捕获,事务未回滚
        System.out.println("异常被捕获:" + e.getMessage());
    }
}

解决方法

  1. 捕获异常后手动回滚:

    @Transactional
    public void updateData() {
        try {
            updateTable1();
            updateTable2();
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            throw e;
        }
    }
    
  2. 避免捕获异常,让 Spring 事务管理器自动处理。

5、多线程场景

原因

  • Spring 事务是基于 ThreadLocal 管理事务上下文的,事务默认只能在当前线程中生效。
  • 如果在事务方法中开启了新线程,线程上下文不再共享,事务失效。

示例

@Transactional
public void updateData() {
    new Thread(() -> {
        // 此处事务无效
        updateTable();
    }).start();
}

解决方法

  1. 避免直接在事务方法中启动线程。
  2. 如果需要异步操作,可以使用 Spring 的异步事务支持(例如 @Async 和事务结合)。
  3. 手动提交事务并手动处理回滚

6、数据库引擎不支持事务

原因

  • MySQL 中只有 InnoDB 引擎支持事务。如果使用的是不支持事务的存储引擎(如 MyISAM),事务相关操作将失效。

解决方法

  • 确保数据库表的存储引擎为 InnoDB

  • ALTER TABLE table_name ENGINE=InnoDB;
    

注意事项

  • MySQL 5.5 开始,MySQL 的默认存储引擎就是 InnoDB
  • 在此之前,MySQL 的默认存储引擎是 MyISAM。因此,现代版本的 MySQL 默认支持事务,因为 InnoDB 是一个支持事务的存储引擎。
  • 即使是 MySQL 5.5+ 版本,也有可能是 MyISAM。因为可手动设置。

7、方法未被代理管理

原因

  • Spring 的事务管理基于代理。
  • 如果一个方法未被 Spring 容器管理(例如直接使用 new 创建对象实例),Spring 的事务管理器不会生效。

解决方法

  • 确保事务方法是由 Spring 容器托管的,不能直接使用 new

8、事务传播配置错误

原因

  • Spring 支持多种事务传播行为(如 REQUIREDREQUIRES_NEW 等),不同传播行为在嵌套调用时可能导致事务失效。例如:
    • 如果外部事务回滚,而嵌套事务没有独立的事务上下文,嵌套事务的操作也会被回滚。

示例

@Transactional
public void methodA() {
    methodB(); // 默认传播行为为 REQUIRED,使用同一个事务
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // 独立事务,方法完成后提交
}

解决方法

  • 根据业务需要,合理选择传播行为。

正确使用事务传播行为的场景

适合使用事务传播行为的场景

  • 需要分开提交或回滚的事务:如果希望某些方法开启独立事务,与主事务隔离(如日志记录、异步任务处理等),可以使用 REQUIRES_NEW
  • 嵌套事务回滚:如果希望某些方法是主事务的子事务,可以使用 NESTED,在子事务出错时回滚到保存点。

不适合使用事务传播行为的场景

  • 解决类内方法调用的问题:如果只是为了修复类内调用导致的事务失效问题,不建议仅依赖事务传播行为,而是应通过代理调用方法。

相关文章:

  • 如何转移虚拟主机?最新虚拟主机迁移方法
  • 如何在 HTML 中使用<dialog>标签创建模态对话框,有哪些交互特性
  • MCP+Hologres+LLM 搭建数据分析 Agent
  • 23种设计模式-享元(Flyweight)设计模式
  • 安装docker版jira8.0.2
  • 【例6.6】整数区间(信息学奥赛一本通-1324)
  • ES集群安装(保姆级教学:两台虚拟机集群)
  • Android 项目缓存问题,某些依赖中的类会报错:Cannot resolve symbol
  • 多线程 --- 进程和线程的基本知识
  • mysql--socket报错
  • 本地部署 DeekSeek 指南
  • 前端传来的不同类型参数,后端 SpringMVC 怎么接收?
  • 微信小程序中使用Less样式方法
  • SSH项目负载均衡中的Session一致性解决方案‌
  • GESP2025年3月认证解析
  • Filnk运行模式
  • 智算中心系统化建设与运营框架
  • 矩阵补充,最近邻查找
  • python基础之--包和模块
  • python3面试题16个(系统编程)
  • 新一届中国女排亮相,奥运冠军龚翔宇担任队长
  • 上海数学教育及数学科普专家陈永明去世,享年85岁
  • 人社部:将会同更多部门分行业、分领域制定专项培训计划
  • 加拿大温哥华一车辆冲撞人群,造成多人伤亡
  • 2025上海浪琴环球马术冠军赛开赛在即,首批赛马今晨抵沪
  • 张家界乒乓球公开赛设干部职级门槛引关注,回应:仅限嘉宾组