有时候,我们明明在类或者方法上添加了@Transactional注解,却发现方法并没有按事务处理。其实,以下场景会导致事务失效。

1、事务方法所在的类没有加载到Spring IOC容器中。

Spring声明式事务的实现完全依赖于Spring的AOP代理机制,未被Spring管理的类中的方法不受Spring的AOP代理管理,因此,声明式事务失效。

2、方法没有被public修饰。

众所周知,java的访问权限修饰符有:private、default、protected、public四种,但是@Transactional注解只能作用于public修饰的方法上。之所以会失效是因为在Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。

protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错。

3、在同一个类中的方法调用。

假如在同一个类中有A、B两个方法,如下:

@Service
public class UserServiceImpl { @Autowired
UserMapper userMapper; public void A() {
B();
} @Transactional
public void B() {
userMapper.deleteById(1);
int i = 10 / 0; //模拟发生异常
} }

像上面的代码,B方法使用@Transactional注解标注,在A方法中调用了B方法,在外部调用A方法时,B方法的事务不会生效。这是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

那么如果确实在同一类中调用事务方法怎么办呢?有以下3种方法解决:

  • 引入自身bean
@Service
public class UserServiceImpl { @Autowired
UserMapper userMapper; @Autowired
UserServiceImpl userServiceImpl; public void A() {
userServiceImpl.B();
} @Transactional
public void B() {
userMapper.deleteById(1);
int i = 10 / 0; //模拟发生异常
} }
  • 通过ApplicationContext引入bean
@Service
public class UserServiceImpl { @Autowired
UserMapper userMapper; @Autowired
ApplicationContext applicationContext; public void A() {
((UserServiceImpl) applicationContext.getBean("userServiceImpl")).B();
} @Transactional
public void B() {
userMapper.deleteById(1);
int i = 10 / 0; //模拟发生异常
} }
  • 通过AopContext获取当前代理类

在启动类上添加注解@EnableAspectJAutoProxy(exposeProxy = true),表示是否对外暴露代理对象,即是否可以获取AopContext。然后,在业务类上使用AopContext。

@Service
public class UserServiceImpl { @Autowired
UserMapper userMapper; public void A() {
((UserServiceImpl) AopContext.currentProxy()).B();
} @Transactional
public void B() {
userMapper.deleteById(1);
int i = 10 / 0; //模拟发生异常
} }

4、方法的事务传播类型不支持事务。

若propagation属性设置如下三种事务传播行为,事务将不会发生回滚。

  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  • NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

5、不正确地捕获异常。

使用了try-catch代码块将异常捕捉了,没有向上抛出异常,事务不会回滚。

@Transactional
private void A() throws Exception {
@Autowired
UserMapper userMapper; @Autowired
B b; try {
//A方法插入数据
User u = New User();
u.setId(1);
u.setName("张三");
userMapper.insert(u); /**
* 这里调用外部方法插入数据
*/
b.insert();
} catch (Exception e) {
e.printStackTrace();
}
}

上面的代码,如果B类insert方法内部抛了异常,而A方法此时try catch了B类方法的异常,那么,事务并不会回滚,而且会抛出如下异常:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

因为当B类insert方法中抛出了一个异常以后,标识当前事务需要rollback。但是A方法中由于手动的捕获这个异常并进行处理,A方法认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

6、属性rollbackFor设置错误。

rollbackFor可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务。其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要设置 rollbackFor属性。

7、数据库不支持事务。

事务本来就是数据库的功能,如果数据库本身不支持事务,那任凭代码上如何设置也是没用的。以MySQL为例,InnoDB引擎是支持事务的,而像MyISAM、MEMORY等是不支持事务的。从MySQL5.5.5开始默认的存储引擎是InnoDB,之前默认都是MyISAM。

8、方法使用final修饰。

如果一个方法不想被子类重写,那么我们就可以把他写成final修饰的方法。如果事务方法使用final修饰,那么Spring AOP就无法在代理类中重写该方法,事务就不会生效。同样的,static修饰的方法也无法通过代理变成事务方法。

9、未开启事务。

如果是SpringBoot项目,那么SpringBoot通过DataSourceTransactionManagerAutoConfiguration自动配置类帮我们开启了事务。如果是传统的Spring项目,则需要我们自己配置。在Spring配置文件配置如下:

<!--事务管理器配置-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED"/>
<!-- 所有insert开头的方法,以下同理 -->
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true" expose-proxy="true">
<!-- 只对业务逻辑层实施事务 -->
<aop:pointcut id="txPointcut"
expression="execution( * com.posun..service.impl..*+.*(..)) || execution(* com.posun..task..*+.*(..))
|| execution(* com.posun.report.ReportJdbc.*(..)) "/>
<aop:advisor id="txAdvisor" advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

或者,使用注解的方式。

<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean> <!-- 注解式事务声明配置-->
<tx:annotation-driven transaction-manager="transactionManager" />

10、多线程调用

@Service
public class UserServiceImpl { @Autowired
UserMapper userMapper; @Transactional
public void A() {
userMapper.deleteById(1);
new Thread(()->{
userMapper.deleteById(2);
int i = 10/0; //模拟发生异常
}).start();
} }

以上代码,A方法中,启动了一个新的线程,并在新的线程中发生了异常,这样A方法是不会发生回滚的。因为两个操作不在一个线程中,获取到的数据库连接不一样,从而是两个不同的事务,所以也不会回滚。

Spring事务(四)-事务失效场景的更多相关文章

  1. 框架应用:Spring framework (四) - 事务管理

    事务控制 事务是什么?事务控制? 事务这个词最早是在数据库中进行应用,讲的用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位. 事务的管理是指一个事务的开启,内容添加, ...

  2. Spring中@Translational注解失效场景

    今天面试被问到@Translational注解什么场景下失效,我一脸懵逼,说的恍恍惚惚的,下来我就总结一下@Translational注解失效的场景! @Transactional 注解相信大家并不陌 ...

  3. spring声明式事务 同一类内方法调用事务失效(转)

    原文 https://blog.csdn.net/jiesa/article/details/53438342 [问题] Spring的声明式事务,我想就不用多介绍了吧,一句话“自从用了Spring ...

  4. Spring 自调用事务失效,你是怎么解决的?

    前言 相信大家都遇到一种事务失效场景,那就是 Spring 自调用,就是在 Service 方法内,调用另一个加 @Transactional 注解的方法,发现事务失效,这时候你是怎么解决的呢? 公众 ...

  5. spring声明式事务 同一类内方法调用事务失效

    只要避开Spring目前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在方法里使用编程式事务 [问题] Spring的声明式事务,我想就不用多介绍了吧,一句话“自从用了Spring ...

  6. Spring(四)-- JdbcTemplate、声明式事务

    1.Spring提供的一个操作数据库的技术JdbcTemplate,是对Jdbc的封装.语法风格非常接近DBUtils.   JdbcTemplate可以直接操作数据库,加快效率,而且学这个JdbcT ...

  7. Spring,SpringMvc配置常见的坑,注解的使用注意事项,applicationContext.xml和spring.mvc.xml配置注意事项,spring中的事务失效,事务不回滚原因

    1.Spring中的applicationContext.xml配置错误导致的异常 异常信息: org.apache.ibatis.binding.BindingException: Invalid ...

  8. Spring(十四)之编程性事务(续)

    Spring 编程式事务管理 编程式事务管理方法允许你在对你的源代码编程的帮助下管理事务.这给了你极大地灵活性,但是它很难维护. 在我们开始之前,至少要有两个数据库表,在事务的帮助下我们可以执行多种 ...

  9. Spring(十四)之事务

    事务管理 一个数据库事务是一个被视为单一的工作单元的操作序列.这些操作应该要么完整地执行,要么完全不执行.事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性.事务的概念 ...

  10. Spring的四种事务特性,五种隔离级别,七种传播行为

    Spring事务: 什么是事务: 事务逻辑上的一组对数据对操作,组成这些操作的各个逻辑单元,要么一起成功,要么一起失败. 事务特性(4种): 原子性(atomicity):强调事务的不可分割:一致性( ...

随机推荐

  1. net8来了

    11 月 15 日开始的为期三天的 .NET Conf 在线活动的开幕日上,.NET 8作为微软的开源跨平台开发平台正式发布..NET 团队着重强调云.性能.全栈 Blazor.AI 和 .NET M ...

  2. C# 实现对网站Get与Post请求

    C# 是一种面向对象的编程语言,提供了强大的Web请求库和API来执行 HTTP GET 和 POST 请求.在C#中,我们可以使用 System.Net 命名空间下的 WebRequest 和 We ...

  3. AOKO奥科美2.5英寸外置硬盘盒开箱

    上次在坛子里发布了一个帖子,然后根据坛友们的反馈,换购了另一个SATA固态硬盘.另一个是配套的硬盘盒,当时在某宝上搜了一圈,最终购买了这款硬盘盒,主要是因为它的外观,旁边有散热片.这款硬盘盒在某宝上不 ...

  4. 谁更适合搭配甜点显卡?i7-13700KF、锐龙7 7800X3D对比:游戏相当 生产力Intel强了50%

    一.前言:如果搭配2000元甜点显卡 i7-13700KF和锐龙7 7800X3D谁更有性价比? 现在AMD最受欢迎的处理器无疑是拥有96MB三级缓存的锐龙7 7800X3D,这是一颗专为游戏而生的处 ...

  5. Docker从认识到实践再到底层原理(二-1)|容器技术发展史+虚拟化容器概念和简介

    前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总 然后就是博主最近最花时间的一 ...

  6. HarmonyOS 实战小项目开发(一)

    HarmonyOS 实战小项目开发(一) 日常逼逼叨 在经过一周多的Harmonyos 开发基础知识的学习后,自己通过对于Harmonyos基础知识的学习之后,结合自己的一些想法,独自完成了利用Ark ...

  7. 《ASP.NET Core 微服务实战》-- 读书笔记(第4章)

    第 4 章 后端服务 现实中的服务不可能处于真空之中,大多数服务都需要与其他服务通信才能完成功能. 我们将这些支持性服务称为后端服务,接下来我们将通过创建一个新的服务并修改之前的团队服务与这个服务通信 ...

  8. 从零开始的react入门教程(二),从react组件说到props/state的联系与区别

    壹 ❀ 引 在从零开始的react入门教程(一)一文中,我们搭建了第一个属于自己的react应用,并简单学习了jsx语法.jsx写法上与dom标签高度一致,当然我们也知道,本质上这些react元素都是 ...

  9. JS script脚本async和defer的区别

    壹 ❀ 引 我在 google recaptcha 谷歌人机身份验证使用教程 一文中有引用这样一段外部资源代码,如下: <script src="https://www.google. ...

  10. Spring Boot 加载外部配置文件

    Spring Boot 允许你从外部加载配置,这样的话,就可以在不同的环境中使用相同的代码.支持的外部配置源包括:Java属性文件.YAML文件.环境变量.命令行参数. 用@Value注解可以将属性值 ...