有时候,我们明明在类或者方法上添加了@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. 服务器重装ip未更改,ssh连不上(WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED)

    服务器重装ip未更改,ssh连不上 前言 原因 解决方法 服务器重装ip未更改,ssh连不上 前言 重装了虚拟机,ip还保留了,但是发现连不上了 @@@@@@@@@@@@@@@@@@@@@@@@@@@ ...

  2. JVM(Java虚拟机) 整理

    JVM整体结构 本文主要说的是HotSpot虚拟机, JVM 全称是 Java Virtual Machine,中文译名:Java虚拟机 简化一下: Java字节码文件 Class文件本质上是一个以8 ...

  3. Paddle模型性能分析工具Profiler:定位瓶颈点、优化程序、提升性能

    项目链接,fork一下即可使用 https://aistudio.baidu.com/aistudio/projectdetail/4482932?contributionType=1 Paddle模 ...

  4. 从浏览器原理出发聊聊 Chrome 插件

    浏览器架构演进 单进程浏览器时代 单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里,这些模块包含了网络.插件.JavaScript 运行环境.渲染引擎和页面等.在 2007 年之前,市面上浏 ...

  5. 图像列表组件(TImageList)

    TImageList 组件是一组同样尺寸的图像列表,每一个图像由它的Index值查询. 1.TImageList组件的典型用法 图形列表用于建立和管理大量的图像,主要是配合菜单或者工具栏按钮的使用.图 ...

  6. 用superxmlparser.pas的XMLParseString----XML转Json注意

    了解XML转成Json时候用的时候多了个#号: ---------------------------------------------------------------------------- ...

  7. CF739A Alyona and mex 题解

    题目传送门 前置知识 贪心 | 构造 解法 从贪心的角度分析,最小的 \(\operatorname{mex}\) 仅会与长度最小的区间有关:另外为使得 \(\operatorname{mex}\) ...

  8. NC26257 小雨坐地铁

    题目链接 题目 题目描述 小雨所在的城市一共有 \(m\) 条地铁线,分别标号为 1 号线,2 号线,--,m 号线.整个城市一共有 \(n\) 个车站,编号为 \(1 \sim n\) .其中坐 i ...

  9. cached地址和uncached地址的区别

    cached地址和uncached地址的区别是 对cached地址的访问是委托给CPU进行的,也就是说你的操作到底是提交给真正的外设或内存,还是转到CPU缓存,是由CPU决定的.CPU有一套缓存策略来 ...

  10. MySQL5.7的账号回收权限

    因MySQL无排除表权限功能,测试隔离表回收权限以下方案: 1,代理账号(角色)方案 方案:创建一个代理账号(角色),对库的800多张表逐个赋权,58张隔离表赋只读,其他30多个账号绑定到这个代理账号 ...