0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

浅谈Spring事务的那些坑

Android编程精选 来源:稀土掘金技术社区 作者:苏三说技术 2022-10-11 10:31 次阅读

对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了。在某些业务场景下,如果同时有多张表的写入操作,为了保证操作的原子性(要么同时成功,要么同时失败)避免数据不一致的情况,我们一般都会使用spring事务。

没错,spring事务大多数情况下,可以满足我们的业务需求。但是今天我要告诉大家的是,它有很多坑,稍不注意事务就会失效。

不信,我们一起看看。

1.错误的访问权限

@Service
publicclassUserService{

@Autowired
privateUserMapper userMapper;

@Transactional
privatevoidadd(UserModel userModel){
userMapper.insertUser(userModel);
}
}

我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须是public的。

AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。


protectedTransactionAttribute computeTransactionAttribute(Method method, @NullableClasstargetClass){
// Don't allow no-public methods as required.
if(allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
returnnull;
}

// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if(txAttr !=null) {
returntxAttr;
}

// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if(txAttr !=null&& ClassUtils.isUserLevelMethod(method)) {
returntxAttr;
}

if(specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if(txAttr !=null) {
returntxAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if(txAttr !=null&& ClassUtils.isUserLevelMethod(method)) {
returntxAttr;
}
}

returnnull;
}

2.方法被定义成final的

@Service
publicclassUserService{

@Autowired
privateUserMapper userMapper;

@Transactional
publicfinalvoidadd(UserModel userModel){
userMapper.insertUser(userModel);
}
}

我们可以看到add方法被定义成了final的,这样会导致spring aop生成的代理对象不能复写该方法,而让事务失效。

3.方法内部调用

@Service
publicclassUserService{

@Autowired
privateUserMapper userMapper;

@Transactional
publicvoidadd(UserModel userModel){
userMapper.insertUser(userModel);
updateStatus(userModel);
}

@Transactional
publicvoidupdateStatus(UserModel userModel){
// doSameThing();
}
}

我们看到在事务方法add中,直接调用事务方法updateStatus。从前面介绍的内容可以知道,updateStatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateStatus方法不会生成事务。

4.当前实体没有被spring管理


//@Service
publicclassUserService{

@Autowired
privateUserMapper userMapper;

@Transactional
publicvoidadd(UserModel userModel){
userMapper.insertUser(userModel);
}
}

我们可以看到UserService类没有定义@Service注解,即没有交给spring管理bean实例,所以它的add方法也不会生成事务。

5.错误的spring事务传播特性


@Service
publicclassUserService{

@Autowired
privateUserMapper userMapper;

@Transactional(propagation = Propagation.NEVER)
publicvoidadd(UserModel userModel){
userMapper.insertUser(userModel);
}

}

我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。只有这三种传播特性才会创建新事务:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。

6.数据库不支持事务


msql8以前的版本数据库引擎是支持myslam和innerdb的。我以前也用过,对应查多写少的单表操作,可能会把表的数据库引擎定义成myslam,这样可以提升查询效率。但是,要千万记得一件事情,myslam只支持表锁,并且不支持事务。所以,对这类表的写入操作事务会失效。

7.自己吞掉了异常


@Slf4j
@Service
public class UserService {

@Autowired
private UserMapper userMapper;

@Transactional
public void add(UserModel userModel) {
try{
userMapper.insertUser(userModel);
}catch(Exception e) {
log.error(e.getMessage(), e);
}
}
}

这种情况下事务不会回滚,因为开发者自己捕获了异常,又没有抛出。事务的AOP无法捕获异常,导致即使出现了异常,事务也不会回滚。

8.抛出的异常不正确


@Slf4j
@Service
public class UserService {

@Autowired
private UserMapper userMapper;

@Transactional
public void add(UserModel userModel) throws Exception {
try{
userMapper.insertUser(userModel);
}catch(Exception e) {
log.error(e.getMessage(), e);
thrownewException(e);
}
}

}

这种情况下,开发人员自己捕获了异常,又抛出了异常:Exception,事务也不会回滚。因为spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),不会回滚Exception。

9.多线程调用

@Slf4j
@Service
publicclassUserService{

@Autowired
privateUserMapper userMapper;
@Autowired
privateRoleService roleService;

@Transactional
publicvoidadd(UserModel userModel)throwsException{
userMapper.insertUser(userModel);
newThread(() -> {
roleService.doOtherThing();
}).start();
}
}

@Service
publicclassRoleService{

@Transactional
publicvoiddoOtherThing(){
System.out.println("保存role表数据");
}
}

我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的,这样会导致两个事务方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。

如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。


privatestaticfinal ThreadLocal> resources =
newNamedThreadLocal<>("Transactional resources");

我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

10.嵌套事务多回滚了

publicclassUserService{

@Autowired
privateUserMapper userMapper;

@Autowired
privateRoleService roleService;

@Transactional
publicvoidadd(UserModel userModel)throwsException{
userMapper.insertUser(userModel);
roleService.doOtherThing();
}
}

@Service
publicclassRoleService{

@Transactional(propagation = Propagation.NESTED)
publicvoiddoOtherThing(){
System.out.println("保存role表数据");
}
}

这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。。但事实是,insertUser也回滚了。

why?

因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。

怎么样才能只回滚保存点呢?


@Slf4j
@Service
public class UserService {

@Autowired
private UserMapper userMapper;

@Autowired
private RoleService roleService;

@Transactional
public void add(UserModel userModel) throws Exception {

userMapper.insertUser(userModel);
try{
roleService.doOtherThing();
}catch(Exception e) {
log.error(e.getMessage(), e);
}
}

}

在代码中手动把内部嵌套事务放在try/catch中,并且不继续往抛异常。

介绍到这里,你会发现spring事务的坑还是挺多的~

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2954

    浏览量

    104510
  • spring
    +关注

    关注

    0

    文章

    338

    浏览量

    14299

原文标题:Spring事务的这10种坑,坑坑致命!

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Spring事务失效的十种常见场景

    Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API(JPA)等事务 API,实现了一致的编程模型,而
    的头像 发表于 12-11 15:03 846次阅读

    Spring事务实现原理

    作者:京东零售 范锡军 1、引言 springspring-tx模块提供了对事务管理支持,使用spring事务可以让我们从复杂的
    的头像 发表于 11-08 10:10 693次阅读
    <b class='flag-5'>Spring</b><b class='flag-5'>事务</b>实现原理

    什么是java spring

    。在SSH项目中管理事务以及对象的注入Spring是非侵入式的:基于Spring开发的系统中的对象一般不依赖于Spring的类。组成 Spring
    发表于 09-11 11:16

    Spring的两种方式事务管理和API接口介绍

    Spring事务管理
    发表于 03-21 06:52

    Spring事务分析的实现方式

    Spring事务原理分析
    发表于 07-02 15:19

    详解Spring事务管理

    在学习spring事务管理时,我忍不住要问,spring为什么进行事务管理,spring怎么进行的事务
    发表于 07-12 06:54

    Spring事务管理详解说明

    Spring事务管理详解
    发表于 05-20 13:46

    关于 STM32 时钟配置的那些

    关于STM32时钟配置的那些
    的头像 发表于 03-08 12:06 5815次阅读

    spring中声明式事务实现原理猜想

      @Transactional注解简介 @Transactional 是spring中声明式事务管理的注解配置方式,相信这个注解的作用大家都很清楚。 @Transactional 注解可以帮助
    的头像 发表于 10-13 09:20 1608次阅读

    发现一个Spring事务的巨bug 你必须要小心了

    1.错误的访问权限 2.方法被定义成final的 3.方法内部调用 4.当前实体没有被spring管理 5.错误的spring事务传播特性 6.数据库不支持事务 7.自己吞掉了异常 8
    的头像 发表于 10-11 18:17 833次阅读

    浅谈Spring事务底层原理

    开启Spring事务本质上就是增加了一个Advisor,但我们使用@EnableTransactionManagement注解来开启Spring事务是,该注解代理的功能就是向
    的头像 发表于 12-06 09:56 670次阅读

    Spring事务在哪几种情况下会不生效?

    日常开发中,我们经常使用到spring事务。最近星球一位还有去美团面试,被问了这么一道面试题: Spring 事务在哪几种情况下会不生效?
    的头像 发表于 05-10 17:53 892次阅读
    <b class='flag-5'>Spring</b><b class='flag-5'>事务</b>在哪几种情况下会不生效?

    8个Spring事务失效的场景介绍

    作为Java开发工程师,相信大家对Spring事务的使用并不陌生。但是你可能只是停留在基础的使用层面上,在遇到一些比较特殊的场景,事务可能没有生效,直接在生产上暴露了,这可能就会导致比较严重的生产
    的头像 发表于 05-11 10:41 591次阅读
    8个<b class='flag-5'>Spring</b><b class='flag-5'>事务</b>失效的场景介绍

    spring事务失效的一些场景

    不一致的情况,我们一般都会用到spring事务。 确实,spring事务用起来贼爽,就用一个简单的注解: @Transactional ,就能轻松搞定
    的头像 发表于 10-08 14:27 421次阅读
    <b class='flag-5'>spring</b><b class='flag-5'>事务</b>失效的一些场景

    Spring事务传播性的相关知识

    本文主要介绍了Spring事务传播性的相关知识。
    的头像 发表于 01-10 09:29 395次阅读
    <b class='flag-5'>Spring</b><b class='flag-5'>事务</b>传播性的相关知识