深入解析 @Transactional——Spring 事务管理的核心

简介: 本文深入解析了 Spring Boot 中 `@Transactional` 的工作机制、常见陷阱及最佳实践。作为事务管理的核心注解,`@Transactional` 确保数据库操作的原子性,避免数据不一致问题。文章通过示例讲解了其基本用法、默认回滚规则(仅未捕获的运行时异常触发回滚)、因 `try-catch` 或方法访问修饰符不当导致失效的情况,以及数据库引擎对事务的支持要求。最后总结了使用 `@Transactional` 的五大最佳实践,帮助开发者规避常见问题,提升项目稳定性与可靠性。

theme: cyanosis

深入解析 @Transactional——Spring 事务管理的核心

@Transactional 是什么?

在开发 Spring Boot 项目的时候,我们往往会遇到这样的场景:一个方法里执行了多步数据库操作,但中间某一步出错了,导致数据出现了“半成功半失败”的情况。这样的数据不一致问题,可能会带来严重的业务风险。而 @Transactional 就是为了解决这个问题而生的。

它的核心作用就是 要么所有操作都成功,要么全部作废,保证数据库的完整性。但很多人用 @Transactional 时,总会遇到“事务没生效”、“回滚失败”、“操作部分成功”等问题,这背后其实有很多坑需要避开。今天我们就来深入拆解 @Transactional 的工作机制、常见陷阱以及最佳实践,帮你用好 Spring 的事务管理能力。

image.png

@Transactional 的基本用法

先来看一个最简单的示例,假设我们有一个删除部门的方法:

@Service
public class DeptServiceImpl implements DeptService {
   
    @Autowired
    private DeptMapper deptMapper;

    @Transactional
    public void deleteDept(Long id) {
   
        deptMapper.delete(id);
        int error = 1 / 0; // 故意制造异常
    }
}

这个方法的逻辑很简单:删除部门后,我们故意制造了一个除零错误,看看事务是否会回滚。如果 @Transactional 正常生效,那么删除操作就不会被提交。但如果事务没生效,那就尴尬了,部门被删掉了,但异常依然抛出了。

Spring 事务默认的回滚规则

有时候,你可能会发现明明 @Transactional 加上了,但事务就是没回滚。Spring 默认的规则是:只有未捕获的 RuntimeException(运行时异常)或 Error 才会触发回滚,而普通的 Exception(检查异常)不会触发回滚

举个例子:

@Transactional
public void deleteDept(Long id) throws IOException {
   
    deptMapper.delete(id);
    throw new IOException("不会回滚!");
}

这里抛出了 IOException,但数据库的删除操作依然提交了。这是因为 IOException检查异常(Checked Exception),Spring 默认不会回滚。如果你希望无论什么异常都触发回滚,需要这样写:

@Transactional(rollbackFor = Exception.class)
public void deleteDept(Long id) throws IOException {
   
    deptMapper.delete(id);
    throw new IOException("现在会回滚了!");
}

所以,如果你希望所有异常都能回滚,最好加上 rollbackFor = Exception.class,避免出现“事务看起来生效了,但并没有真正回滚”的情况。

try-catch 导致事务失效

你可能会写这样的代码:

@Transactional
public void deleteDept(Long id) {
   
    try {
   
        deptMapper.delete(id);
        int x = 1 / 0; // 触发异常
    } catch (Exception e) {
   
        System.out.println("发生异常,但事务未回滚");
    }
}

看上去是个挺合理的异常处理,但问题来了:事务不会回滚!

为什么?因为异常被 catch 了,Spring 根本感知不到异常的发生,认为你的方法执行得好好的,于是就把事务提交了。

正确的做法是要么 让异常抛出去,要么 手动抛出一个新的 RuntimeException

@Transactional
public void deleteDept(Long id) {
   
    try {
   
        deptMapper.delete(id);
        int x = 1 / 0;
    } catch (Exception e) {
   
        throw new RuntimeException("手动抛出异常,确保事务回滚", e);
    }
}

或者直接这样写,让异常自然传播:

@Transactional
public void deleteDept(Long id) throws Exception {
   
    deptMapper.delete(id);
    int x = 1 / 0; // 事务会回滚
}

总结一下:Spring 只有在方法抛出异常时,才会触发回滚。如果你在 catch 里吞掉了异常,那事务也就不会回滚了

事务为什么有时候会失效?

除了 try-catch,还有一些常见情况会让 @Transactional 失效,看看你是否踩过这些坑。

1. 方法不是 public

@Transactional 只会作用于 public 方法,如果你加在 privateprotected 方法上,事务不会生效:

@Transactional
private void deleteDept(Long id) {
    } // 事务不会生效!

Spring 事务是通过 代理机制 实现的,而 JDK 动态代理只能代理 public 方法,所以其他访问级别的方法都不行。正确写法是:

@Transactional
public void deleteDept(Long id) {
    }

2. 同一类里,方法互相调用

看下面的代码:

@Service
public class DeptServiceImpl {
   
    @Autowired
    private DeptMapper deptMapper;

    @Transactional
    public void deleteDept(Long id) {
   
        this.deleteEmp(id); // 事务不会生效!
    }

    @Transactional
    public void deleteEmp(Long id) {
   
        empMapper.delByDeptId(id);
    }
}

这里 deleteDept 方法调用了 deleteEmp,但 deleteEmp 上的 @Transactional 不会生效!原因是:Spring 的事务是基于代理的,this.deleteEmp(id) 直接调用了本类的方法,没有经过 Spring 代理,所以事务不会生效。

正确的做法是 通过 Spring 管理的 Bean 调用

@Service
public class DeptServiceImpl {
   
    @Autowired
    private DeptServiceImpl self;

    @Transactional
    public void deleteDept(Long id) {
   
        self.deleteEmp(id); // 事务生效!
    }

    @Transactional
    public void deleteEmp(Long id) {
   
        empMapper.delByDeptId(id);
    }
}

或者使用 ApplicationContext 获取代理对象,再调用方法。

3. 数据库引擎不支持事务

如果你用的 MySQL 表引擎是 MyISAM,事务是不可能生效的,因为 MyISAM 根本不支持事务!要确保你的表是 InnoDB:

SHOW TABLE STATUS WHERE Name = 'dept';
ALTER TABLE dept ENGINE = InnoDB;

@Transactional 的最佳实践

  1. 确保事务方法是 public,否则事务不会生效。
  2. 避免同一类内部调用 @Transactional 方法,可以使用 self.xxx() 代理调用。
  3. 异常要让 Spring 感知到,不要 try-catch 后直接吞掉。
  4. 检查异常默认不会回滚,如果需要回滚,使用 rollbackFor = Exception.class
  5. 数据库表必须支持事务,MyISAM 不支持事务,建议用 InnoDB。

总结

Spring 事务管理的核心思想是 原子性,让数据库操作要么全成功,要么全失败。但如果用 @Transactional 时不小心踩了坑,可能会导致事务失效,影响数据一致性。希望这篇文章能帮你理解 @Transactional 的运行原理,让你的 Spring Boot 开发更加稳定可靠!

目录
相关文章
|
1月前
|
数据采集 人工智能 Java
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
DevDocs是一款基于智能爬虫技术的开源工具,支持1-5层深度网站结构解析,能将技术文档处理时间从数周缩短至几小时,并提供Markdown/JSON格式输出与AI工具无缝集成。
110 1
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
|
1月前
|
安全 Java API
深入解析 Spring Security 配置中的 CSRF 启用与 requestMatchers 报错问题
本文深入解析了Spring Security配置中CSRF启用与`requestMatchers`报错的常见问题。针对CSRF,指出默认已启用,无需调用`enable()`,只需移除`disable()`即可恢复。对于`requestMatchers`多路径匹配报错,分析了Spring Security 6.x中方法签名的变化,并提供了三种解决方案:分次调用、自定义匹配器及降级使用`antMatchers()`。最后提醒开发者关注版本兼容性,确保升级平稳过渡。
124 2
|
2月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `<appender>` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `<logger>` 和 `<root>` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
213 1
|
1月前
|
前端开发 安全 Java
Spring Boot 便利店销售系统项目分包设计解析
本文深入解析了基于Spring Boot的便利店销售系统分包设计,通过清晰的分层架构(表现层、业务逻辑层、数据访问层等)和模块化设计,提升了代码的可维护性、复用性和扩展性。具体分包结构包括`controller`、`service`、`repository`、`entity`、`dto`、`config`和`util`等模块,职责分明,便于团队协作与功能迭代。该设计为复杂企业级应用开发提供了实践参考。
74 0
|
4天前
|
安全 Java API
Spring Boot 功能模块全解析:构建现代Java应用的技术图谱
Spring Boot不是一个单一的工具,而是一个由众多功能模块组成的生态系统。这些模块可以根据应用需求灵活组合,构建从简单的REST API到复杂的微服务系统,再到现代的AI驱动应用。
99 7
|
1月前
|
缓存 安全 Java
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
149 5
|
1月前
|
安全 Java 数据安全/隐私保护
Spring Security: 深入解析 AuthenticationSuccessHandler
本文深入解析了 Spring Security 中的 `AuthenticationSuccessHandler` 接口,它用于处理用户认证成功后的逻辑。通过实现该接口,开发者可自定义页面跳转、日志记录等功能。文章详细讲解了接口方法参数及使用场景,并提供了一个根据用户角色动态跳转页面的示例。结合 Spring Security 配置,展示了如何注册自定义的成功处理器,帮助开发者灵活应对认证后的多样化需求。
66 2
|
1月前
|
前端开发 IDE Java
Spring MVC 中因导入错误的 Model 类报错问题解析
在 Spring MVC 或 Spring Boot 开发中,若导入错误的 `Model` 类(如 `ch.qos.logback.core.model.Model`),会导致无法解析 `addAttribute` 方法的错误。正确类应为 `org.springframework.ui.Model`。此问题通常因 IDE 自动导入错误类引起。解决方法包括:删除错误导入、添加正确包路径、验证依赖及清理缓存。确保代码中正确使用 Spring 提供的 `Model` 接口以实现前后端数据传递。
77 0
|
4月前
|
XML Java 开发者
Spring底层架构核心概念解析
理解 Spring 框架的核心概念对于开发和维护 Spring 应用程序至关重要。IOC 和 AOP 是其两个关键特性,通过依赖注入和面向切面编程实现了高效的模块化和松耦合设计。Spring 容器管理着 Beans 的生命周期和配置,而核心模块为各种应用场景提供了丰富的功能支持。通过全面掌握这些核心概念,开发者可以更加高效地利用 Spring 框架开发企业级应用。
147 18
|
5月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能