面试官:请列举 Spring 的事务会失效的场景
在日常工作中,如果对 Spring 的事务管理功能使用不当,则会造成 Spring 事务不生效的问题。而针对 Spring 事务不生效的问题,也是在跳槽面试中被问的比较频繁的一个问题。
今天,我们就一起梳理下有哪些场景会导致 Spring 事务失效。
Spring 事务失效的8中场景

下面就举例说明这8种失效场景及解决方法
1.使用不支持事务的存储引擎
Spring 事务生效的前提是所连接的数据库要支持事务,如果底层的数据库都不支持事务,则 Spring 的事务肯定会失效。例如,如果使用的数据库为 MySQL,并且选用了 MyISAM 存储引擎,则 Spring 的事务就会失效。
解决方法:使用MySQL中的InnoDB存储引擎就支持事务
2.抛出检查异常导致事务不能正确回滚
以下是一个示例,演示了抛出检查异常导致事务不能正确回滚的情况:
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveUser(User user) {
try {
jdbcTemplate.update("INSERT INTO users (name, age) VALUES (?, ?)", user.getName(), user.getAge());
// 抛出检查异常,事务将不会回滚
throw new Exception("模拟检查异常");
} catch (Exception e) {
// 异常处理逻辑
e.printStackTrace();
}
}
}
在上面的示例中,saveUser()方法被标记为@Transactional,并指定了propagation = Propagation.REQUIRES_NEW传播行为。这意味着该方法必须在一个新的事务中运行。如果在执行插入操作后抛出了检查异常(Exception),事务将不会回滚。这是因为检查异常是开发者可以预见的异常,并且开发者通过捕获并处理这些异常来控制程序的流程。因此,事务管理器不会回滚事务,以保持数据库的一致性。
解决方法:使用运行时异常:在Spring框架中,建议使用RuntimeException或其子类作为事务方法中抛出的异常。RuntimeException是未检查异常的子类,因此不会导致事务回滚。相反,检查异常(即那些直接或间接继承自Exception的异常)会导致事务回滚。
3.业务方法内自己 try-catch导致事务不能正确回滚
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveUser(User user) {
try {
jdbcTemplate.update("INSERT INTO users (name, age) VALUES (?, ?)", user.getName(), user.getAge());
// 模拟抛出异常,但被try-catch捕获并静默处理
throw new Exception("模拟异常");
} catch (Exception e) {
// 异常被捕获并静默处理,事务不会回滚
e.printStackTrace();
}
}
}
在上面的示例中,saveUser()方法被标记为@Transactional,并指定了propagation = Propagation.REQUIRES_NEW传播行为。这意味着该方法必须在一个新的事务中运行。在try块中,我们执行了一个插入操作,然后模拟抛出了一个异常。这个异常被catch块捕获,并静默处理(只是打印堆栈跟踪)。由于异常被静默处理,事务不会回滚。
解决方法: 要避免这种情况,你应该确保在事务方法中捕获的异常被适当地向外抛出,以便Spring的事务管理器可以检测到异常并回滚事务。你可以选择抛出运行时异常或检查异常,但重要的是要确保异常被正确地传递给调用者,以便于调试和错误处理。
4.非public方法导致的事务时效
当事务方法被标记为非public时,会导致事务失效。这是因为在Spring的声明式事务管理机制中,代理类只能代理public方法。如果方法被声明为非public,代理类无法访问该方法,从而导致事务失效。
以下是一个示例,演示了非public方法导致的事务失效场景:
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
private void saveUser(User user) {
jdbcTemplate.update("INSERT INTO users (name, age) VALUES (?, ?)", user.getName(), user.getAge());
}
}
在上面的示例中,saveUser()方法被声明为private,导致Spring的代理类无法访问该方法。因此,事务失效,并且无法正确地回滚事务。要解决这个问题,你可以将方法声明为public,以确保Spring的代理类可以访问该方法并正确地管理事务。
解决方法: 使用public方法
5.@Transactional没有保证原子行为
当事务方法中存在SELECT方法时,Spring的@Transactional注解无法保证原子性。这是因为SELECT方法不会阻塞,事务的原子性仅仅涵盖INSERT、UPDATE、DELETE、SELECT...FOR UPDATE语句。
以下是一个示例,演示了@Transactional没有保证原子行为导致的事务失效场景:
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void updateUser(User user) {
// SELECT方法不会阻塞,事务的原子性无法保证
User existingUser = jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", user.getId());
// 更新操作
jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", user.getName(), user.getId());
}
}
在上面的示例中,事务方法中包含了一个SELECT方法,用于查询用户信息。然后执行了一个更新操作。由于SELECT方法不会阻塞,事务的原子性无法得到保证。如果其他线程在SELECT和UPDATE之间修改了数据,可能会出现数据不一致的情况。要解决这个问题,你可以考虑使用其他方式来保证原子性,例如使用数据库锁或使用Spring的事务传播行为。
解决方法:
- 使用数据库锁:通过数据库锁来保证多个操作在一个事务中的原子性。你可以使用数据库提供的锁机制,例如行锁或表锁,来确保在事务中的操作不会被其他线程干扰。
- 修改存储引擎:将数据库的存储引擎改为InnoDB,而不是默认的MyISAM。InnoDB引擎支持事务,并提供了行级锁定和外键约束等特性,可以更好地保证数据的一致性和完整性。
- 使用Spring的事务传播行为:通过设置
@Transactional注解的propagation属性,你可以指定事务的传播行为。例如,你可以设置propagation = Propagation.REQUIRES_NEW,这样每个事务方法都会运行在一个新的事务中,确保其原子性。 - 修改SELECT语句:将SELECT语句替换为SELECT...FOR UPDATE语句。这样,在查询时会对选定的行加锁,直到事务结束时才会释放锁,从而避免了其他线程的干扰。
- 使用同步机制:在事务方法中使用同步机制,确保同一时间只有一个线程可以执行该方法。这样可以避免并发争抢资源的情况,保证原子性。
6.AOP切面顺序导致事务不能正确回滚
以下是一个例子,展示了由于Spring AOP切面顺序导致事务不能正确回滚的场景:
假设你有一个服务层方法,使用@Transactional注解进行事务管理。在调用该方法之前,你希望先进行日志记录,以便记录方法的调用信息和参数。因此,你使用了AOP切面来实现日志记录功能。
@Service
public class UserService {
@Transactional
public void createUser(User user) {
// 业务逻辑代码
}
}
@Aspect
@Component
public class LoggingAspect {
// 定义日志切面
}
@Aspect
@Component
public class TransactionAspect {
// 定义事务切面
}
在上述示例中,createUser()方法被标记为@Transactional,用于管理事务。同时,你定义了两个切面:日志切面和事务切面。
日志切面:用于记录方法的调用信息和参数。
事务切面:用于管理事务的开始和回滚。
如果在Spring配置中,日志切面在事务切面前执行,那么当createUser()方法抛出异常时,日志切面可能会先捕获到异常并记录日志,而事务切面可能还没有开始事务。这样,事务切面无法正确地回滚事务,导致数据不一致和其他潜在问题。
解决方法: 为了解决这个问题,你可以在Spring配置中明确指定切面的顺序,确保事务切面在日志切面前执行。你可以使用@Order注解或通过XML配置来定义切面的顺序。例如:
@Aspect
@Component
@Order(1) // 定义日志切面的顺序为1
public class LoggingAspect {
// 定义日志切面逻辑
}
@Aspect
@Component
@Order(2) // 定义事务切面的顺序为2
public class TransactionAspect {
// 定义事务切面逻辑
}
7.调用本类方法导致传播行为失效
在Spring事务中,当一个事务方法调用了本类(同一个类)的其他方法时,可能会导致事务的传播行为失效。
下面是一个示例场景,展示了由于调用本类方法导致事务传播行为失效的问题:
假设你有一个服务类UserService,其中包含两个方法:createUser()和updateUser()。createUser()方法被标记为@Transactional,用于管理事务。
@Service
public class UserService {
@Transactional
public void createUser(User user) {
// 调用updateUser()方法
updateUser(user);
}
public void updateUser(User user) {
// 更新用户信息的逻辑代码
}
}
在上述示例中,createUser()方法被标记为@Transactional,并调用了本类的updateUser()方法。这意味着,当createUser()方法执行时,它应该在一个事务的上下文中运行。
然而,由于updateUser()方法没有被标记为@Transactional,它不会在事务的上下文中执行。这意味着,如果在updateUser()方法中发生了异常,事务不会回滚,因为事务的传播行为失效了。
解决办法:你可以将需要事务管理的所有方法都标记为@Transactional,或者使用Spring的事务传播行为来指定事务的传播行为。例如,你可以将@Transactional注解的propagation属性设置为Propagation.REQUIRES_NEW,这样每个事务方法都会运行在一个新的事务中。
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createUser(User user) {
// 调用updateUser()方法
updateUser(user);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUser(User user) {
// 更新用户信息的逻辑代码
}
}
8.@Transactional方法导致的synchronized失效
当你在Spring中使用@Transactional注解时,Spring会为你自动管理事务。但是,@Transactional注解并不会将方法同步化,也就是说,它不会将方法标记为synchronized。
以下是一个示例场景,展示了由于@Transactional方法导致的synchronized失效的场景:
假设你有两个服务类UserServiceA和UserServiceB,它们都包含一个名为updateUser()的方法,该方法使用synchronized关键字进行同步。
@Service
public class UserServiceA {
@Transactional
public synchronized void updateUser(User user) {
// 更新用户信息的逻辑代码
}
}
@Service
public class UserServiceB {
@Transactional
public synchronized void updateUser(User user) {
// 更新用户信息的逻辑代码
}
}
在上述示例中,updateUser()方法被标记为@Transactional和synchronized。这意味着在多线程环境中,同一时间只能有一个线程调用该方法。
然而,由于@Transactional注解的存在,Spring会为每个事务创建一个新的事务代理对象。这意味着,当两个线程同时调用UserServiceA.updateUser()和UserServiceB.updateUser()方法时,它们实际上是两个不同的方法,而不是同一个方法的两个实例。因此,尽管方法被标记为synchronized,但由于事务代理的存在,这两个方法的同步性失效了。
解决方法:updateUser()方法被标记为@Transactional。为了确保同一时间只有一个线程执行该方法的同步代码块,我们使用了一个同步代码块,将this对象作为锁对象。这样,当一个线程进入同步代码块时,其他线程将会被阻塞,直到第一个线程退出同步代码块。
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional
public void updateUser(User user) {
// 同步代码块,确保同一时间只有一个线程执行
synchronized (this) {
// 更新用户信息的逻辑代码
}
}
}
通过使用同步代码块,你可以确保同一时间只有一个线程能够访问共享资源,即使事务代理存在,也不会导致synchronized失效。这种方法适用于简单的同步需求,如果你的应用有更复杂的并发控制需求,可能需要考虑其他同步机制或数据库锁等更高级的解决方案。

面试官:请列举 Spring 的事务会失效的场景的更多相关文章
- 跟面试官侃半小时MySQL事务,说完原子性、一致性、持久性的实现
提到MySQL的事务,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID(即原子性Atomicity.一致性Consiste ...
- 面试官:说说Spring中的事务传播行为
前言 在开发中,相信大家都使用过Spring的事务管理功能.那么,你是否有了解过,Spring的事务传播行为呢? Spring中,有7种类型的事务传播行为.事务传播行为是Spring框架提供的一种事务 ...
- 阿里面试挂了,就因为面试官说我Spring 事务管理(器)不熟练?
前言 事务管理,一个被说烂的也被看烂的话题,还是八股文中的基础股之一.但除了八股文中需要熟读并背诵的那些个传播行为之外,背后的"为什么"和核心原理更为重要. 写这篇文章之前,我 ...
- 面试官:连Spring三级缓存都答不好,自己走还是我送你?
面试官:简历上写了精通Spring,那你回答一下Spring为什么用“三级缓存”去解决循环依赖? 我:.......应该有三个缓存的map结构 面试官:具体回答一下 我:平时没认真深入过 面试官:公司 ...
- 面试官:连Spring AOP都说不明白,自己走还是我送你?
前言 因为假期原因,有一段时间没给大家更新了!和大家说个事吧,放假的时候一位粉丝和我说了下自己的被虐经历,在假期前他去某互联网公司面试,结果直接被人家面试官Spring AOP三连问给问的一脸懵逼!其 ...
- 跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现
提到MySQL的事务,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID(即原子性Atomicity.一致性Consiste ...
- 面试官一口气问了MySQL事务、锁和MVCC,我
面试官:你是怎么理解InnoDB引擎中的事务的? 候选者:在我的理解下,事务可以使「一组操作」要么全部成功,要么全部失败 候选者:事务其目的是为了「保证数据最终的一致性」. 候选者:举个例子,我给你发 ...
- 【面试】足够“忽悠”面试官的『Spring事务管理器』源码阅读梳理(建议珍藏)
PS:文章内容涉及源码,请耐心阅读. 理论实践,相辅相成 伟大领袖毛主席告诉我们实践出真知.这是无比正确的.但是也会很辛苦. 就像淘金一样,从大量沙子中淘出金子一定是一个无比艰辛的过程.但如果真能淘出 ...
- 面试官问你:MYSQL事务和隔离级别,该如何回答
一.事务 事务是由一组SQL语句组成的逻辑处理单元,是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚.事务具有以下4个属性,通常简称为事务 ...
- 面试官:什么是MySQL 事务与 MVCC 原理?
作者:小林coding 图解计算机基础网站:https://xiaolincoding.com/ 大家好,我是小林. 之前写过一篇 MySQL 的 MVCC 的工作原理,最近有读者在网站上学习的时候, ...
随机推荐
- 使用SemanticKernel 进行智能应用开发(2023-10更新)
以OpenAI 的ChatGPT 所掀起的GenAI 快速创新浪潮,其中连接LLM 和 应用之间的桥梁的两大开源项目:LangChain[1]和Semantic Kernel[2] ,在半年前写过一篇 ...
- 【知识杂谈#2】如何查看Linux的(本地与公网)IP地址与SSH服务的端口号
1. 本地Ip地址查看 使用查看linux主机是否有net-tools dpkg -l net-tools 显示以下代码就说明已安装成功 ||/ Name Version Architecture D ...
- Vue2系列(lqz)——Vue生命期钩子、组件
文章目录 Vue声明期钩子 组件 1 fetch和axios 1.1 fetche使用 1.2 axios的使用 2 计算属性 2.1 通过计算属性实现名字首字母大写 2.2 通过计算属性重写过滤案例 ...
- 19c上ADG主库sys密码修改会影响备库同步吗?
一套Oracle 19c的ADG集群要修改sys密码,由于之前遇见过11g上sys密码修改导致同步问题的情况,所以改之前特意查了下文档,发现其实12cR2开始,在主库修改密码就会自动同步到备库了,以下 ...
- vue框架,input相同标签如何定位-label定位
一.问题提出: 后台前端框架改版,之前是angularjs,现在用vue,导致input标签定位失败,只能定位到第一个input标签,查看后台源代码发现这两个标签是一模一样,如下图: 二.问题思考过程 ...
- CF707B
题目简化和分析: 这题看着玄胡很水实际. 我们需要做什么? 只需对每个工厂周围的面包店遍历一遍打擂台取最小 注意只对面包店遍历,所以对工厂设标记 如果打完擂台发现 \(ans=inf\) 则说明全是工 ...
- MMKV源码解读与理解
概述 通过 mmap 技术实现的高性能通用 key-value 组件.同时选用 protobuf 协议,进一步压缩数据存储. 标准 protobuf 不提供增量更新的能力,每次写入都必须全量写入.考虑 ...
- Jenkins软件平台安装部署
1.Jenkins软件平台概念剖解: 基于主流的Hudson/Jenkins平台工具实现全自动网站部署.网站测试.网站回滚会大大的减轻网站部署的成本,Jenkins的前身为Hudson,Hudson主 ...
- Isito 入门(八):金丝雀发布
本教程已加入 Istio 系列:https://istio.whuanle.cn 目录 6,金丝雀发布 金丝雀发布 按照流量比例划分 按照 Header 划分 6,金丝雀发布 项目总是处于不断变化之中 ...
- Rasa NLU中的组件
Rasa NLU部分主要是解决NER(序列建模)和意图识别(分类建模)这2个任务.Rasa NLP是一个基于DAG的通用框架,图中的顶点即组件.组件特征包括有顺序关系.可相互替换.可互斥和可同时使 ...