论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
ToB门户
了解全球最新的ToB事件
博客
Blog
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
IT评测·应用市场-qidao123.com技术社区
»
论坛
›
安全
›
网络安全
›
Spring事务(二)-@Transactional事务失效的场景 ...
Spring事务(二)-@Transactional事务失效的场景
我可以不吃啊
论坛元老
|
2022-9-17 23:31:50
|
显示全部楼层
|
阅读模式
楼主
主题
1914
|
帖子
1914
|
积分
5742
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
有时候,我们明明在类或者方法上添加了@Transactional注解,却发现方法并没有按事务处理。其实,以下场景会导致Spring的@Transactional事务失效。
1、事务方法所在的类没有加载到Spring IOC容器中。
@Transactional是Spring的注解,未被Spring管理的类中的方法不受@Transactional注解控制,这个应该很好理解。
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。
@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属性设置如下三种事务传播行为,事务将不会发生回滚。
1、SUPPORTS:如果当前存在事务,则加入该事务;
如果当前没有事务,则以非事务的方式继续运行。
2、NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
3、NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
Spring事务传播行为可以查阅我之前的一篇文章:
Spring事务(一)-事务传播行为
5、不正确地捕获异常。
使用了try-catch代码块将异常捕捉了,没有向上抛出异常,事务不会回滚。
@Transactional
private void A() throws Exception {
@Autowired
UserMapper userMapper;
try {
//A方法插入数据
User u = New User();
u.setId(1);
u.setName("张三");
userMapper.insert(u);
/**
* B方法也插入数据
*/
b.insert();
} catch (Exception e) {
e.printStackTrace();
}
}
复制代码
上面的代码,如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那么,事务并不会回滚,而且会抛出如下异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
复制代码
因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常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修饰,那么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"/>
<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>
复制代码
或者,使用注解的方式。
[/code][size=4][b] 10、多线程调用[/b][/size]
[code]@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方法是不会发生回滚的。因为两个操作不在一个线程中,获取到的数据库连接不一样,从而是两个不同的事务,所以也不会回滚。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复
使用道具
举报
0 个回复
正序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
发新帖
回复
我可以不吃啊
论坛元老
这个人很懒什么都没写!
楼主热帖
如何编写一个高效的Testbench? ...
HeadPose Estimation头部姿态估计头部 ...
微信小程序
Python输出指定时间间隔内的日期 ...
Python 将 docx 转为 PDF
【笔者感悟】笔者的学习心得【七】 ...
HBuilder X 连接苹果手机(IOS)详细教程 ...
接口测试测什么?这篇文章告诉你 ...
CVE-2015-5254漏洞复现
线程池,我是谁?我在哪儿? ...
标签云
AI
运维
CIO
存储
服务器
快速回复
返回顶部
返回列表