Spring事务(四)-事务失效场景

有时候,我们明明在类或者方法上添加了@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事务(四)-事务失效场景的更多相关文章
- 框架应用:Spring framework (四) - 事务管理
事务控制 事务是什么?事务控制? 事务这个词最早是在数据库中进行应用,讲的用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位. 事务的管理是指一个事务的开启,内容添加, ...
- Spring中@Translational注解失效场景
今天面试被问到@Translational注解什么场景下失效,我一脸懵逼,说的恍恍惚惚的,下来我就总结一下@Translational注解失效的场景! @Transactional 注解相信大家并不陌 ...
- spring声明式事务 同一类内方法调用事务失效(转)
原文 https://blog.csdn.net/jiesa/article/details/53438342 [问题] Spring的声明式事务,我想就不用多介绍了吧,一句话“自从用了Spring ...
- Spring 自调用事务失效,你是怎么解决的?
前言 相信大家都遇到一种事务失效场景,那就是 Spring 自调用,就是在 Service 方法内,调用另一个加 @Transactional 注解的方法,发现事务失效,这时候你是怎么解决的呢? 公众 ...
- spring声明式事务 同一类内方法调用事务失效
只要避开Spring目前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在方法里使用编程式事务 [问题] Spring的声明式事务,我想就不用多介绍了吧,一句话“自从用了Spring ...
- Spring(四)-- JdbcTemplate、声明式事务
1.Spring提供的一个操作数据库的技术JdbcTemplate,是对Jdbc的封装.语法风格非常接近DBUtils. JdbcTemplate可以直接操作数据库,加快效率,而且学这个JdbcT ...
- Spring,SpringMvc配置常见的坑,注解的使用注意事项,applicationContext.xml和spring.mvc.xml配置注意事项,spring中的事务失效,事务不回滚原因
1.Spring中的applicationContext.xml配置错误导致的异常 异常信息: org.apache.ibatis.binding.BindingException: Invalid ...
- Spring(十四)之编程性事务(续)
Spring 编程式事务管理 编程式事务管理方法允许你在对你的源代码编程的帮助下管理事务.这给了你极大地灵活性,但是它很难维护. 在我们开始之前,至少要有两个数据库表,在事务的帮助下我们可以执行多种 ...
- Spring(十四)之事务
事务管理 一个数据库事务是一个被视为单一的工作单元的操作序列.这些操作应该要么完整地执行,要么完全不执行.事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性.事务的概念 ...
- Spring的四种事务特性,五种隔离级别,七种传播行为
Spring事务: 什么是事务: 事务逻辑上的一组对数据对操作,组成这些操作的各个逻辑单元,要么一起成功,要么一起失败. 事务特性(4种): 原子性(atomicity):强调事务的不可分割:一致性( ...
随机推荐
- 【4】配置和运行Opencv常见的一些问题总结,以及bug解决。
相关文章: [1]windows下安装OpenCV(4.3)+VS2017安装+opencv_contrib4.3.0配置 [2]Visual Studio 2017同时配置OpenCV2.4 以及O ...
- C++ CryptoPP使用RSA加解密
Crypto++ (CryptoPP) 是一个用于密码学和加密的 C++ 库.它是一个开源项目,提供了大量的密码学算法和功能,包括对称加密.非对称加密.哈希函数.消息认证码 (MAC).数字签名等.C ...
- 驱动开发:如何枚举所有SSDT表地址
在前面的博文<驱动开发:Win10内核枚举SSDT表基址>中已经教大家如何寻找SSDT表基地址了,找到后我们可根据序号获取到指定SSDT函数的原始地址,而如果需要输出所有SSDT表信息,则 ...
- 【Linux】root密码忘记了怎么办【技能篇】一分钟教会你重置root密码
[Linux]root密码重置 文章目录 前言介绍 操作步骤 尾声 前言介绍 那么这里博主先安利一下一些干货满满的专栏啦! 数据结构专栏:数据结构 这里包含了博主很多的数据结构学习上的总结,每一篇都是 ...
- 体验 ABP 的功能和服务
大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进. ABP是一个全栈开发框架,它在企业解决方案的各个方面都有许多构建模块.在前面三章中 ...
- Java 数字 默认是 Integer类型的问题,System.currentTimeMillis() + (180 * 24 * 60 * 60 * 1000)的问题,剖析、Long + Integer的问题
最终结论: (180 * 24 * 60 * 60) 这种计算表达式在 Java中是默认以 Integer类型来的,若不超过 Integer的最大值则没有问题,若超过则必须用 (180 * 24 * ...
- SVC服务的发布
目录 服务的发布 发布的方式 1.NodePort 方法一:创建的时候直接指定类型 方法二:在线修改(将其他类型改为NodePort) 2.LoadBalance 3.Ingress(推荐/重点) 服 ...
- 从零开始的react入门教程(三),了解react事件与使用注意项
壹 ❀ 引 在从零开始的react入门教程(二),从react组件说到props/state的联系与区别一文中,我们介绍了react组件的基本用法以及props与state的区别.其中react组件分 ...
- NC15434 wyh的迷宫
题目链接 题目 题目描述 给你一个n*m的迷宫,这个迷宫中有以下几个标识: s代表起点 t代表终点 x代表障碍物 .代表空地 现在你们涵哥想知道能不能从起点走到终点不碰到障碍物(只能上下左右进行移动, ...
- Linux中$home和波浪号~
在Linux中当前用户的主目录可以有三种表示方法,都是等效的[正常不改配置情况下]例如用户名为sy,进入当前用户的主目录/home/sy 输入cd ~ 或输入cd $HOME 或输入cd /home/ ...