快盘下载:好资源、好软件、快快下载吧!

快盘排行|快盘最新

当前位置:首页软件教程电脑软件教程 → spring事务失效的这10种坑,spring事务失效解决方案

spring事务失效的这10种坑,spring事务失效解决方案

时间:2022-10-11 10:44:47人气:作者:快盘下载我要评论

对于从事java开发工作的同学来说;spring的事务肯定再熟悉不过了。在某些业务场景下;如果同时有多张表的写入操作;为了保证操作的原子性;要么同时成功;要么同时失败;避免数据不一致的情况;我们一般都会使用spring事务。没错;spring事务大多数情况下;可以满足我们的业务需求。但是今天我要告诉大家的是;它有很多坑;稍不注意事务就会失效。

1.错误的访问权限

;Service
public class UserService {

    ;Autowired
    private UserMapper userMapper;
    
    ;Transactional
    private void add(UserModel userModel) {
        userMapper.insertUser(userModel);
    }
}

我们可以看到add方法的访问权限被定义成了private;这样会导致事务失效;spring要求被代理方法必须是public的。
AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断;如果目标方法不是public;则TransactionAttribute返回null;即不支持事务。

protected TransactionAttribute computeTransactionAttribute(Method method, ;Nullable Class<?> targetClass) {
    // Don;t allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }
​
    // 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) {
      return txAttr;
    }
​
    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
      return txAttr;
    }
​
    if (specificMethod != method) {
      // Fallback is to look at the original method.
      txAttr = findTransactionAttribute(method);
      if (txAttr != null) {
        return txAttr;
      }
      // Last fallback is the class of the original method.
      txAttr = findTransactionAttribute(method.getDeclaringClass());
      if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
      }
    }
​
    return null;
  }

2.方法被定义成final的

;Service
public class UserService {
​
    ;Autowired
    private UserMapper userMapper;
​
    ;Transactional
    public final void add(UserModel userModel) {
        userMapper.insertUser(userModel);
    }
}

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

3.方法内部调用

;Service
public class UserService {
​
    ;Autowired
    private UserMapper userMapper;
​
    ;Transactional
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
        updateStatus(userModel);
    }
​
    ;Transactional
    public void updateStatus(UserModel userModel) {
        // doSameThing();
    }
}

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

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

//;Service
public class UserService {
​
    ;Autowired
    private UserMapper userMapper;
​
    ;Transactional
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
    }    
}

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

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

;Service
public class UserService {
​
    ;Autowired
    private UserMapper userMapper;
​
    ;Transactional(propagation = Propagation.NEVER)
    public void add(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);
            throw new Exception(e);
        }
    }
}

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

9.多线程调用

;Slf4j
;Service
public class UserService {
​
    ;Autowired
    private UserMapper userMapper;
    ;Autowired
    private RoleService roleService;
​
    ;Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        new Thread(() -> {
            roleService.doOtherThing();
        }).start();
    }
}
​
;Service
public class RoleService {
​
    ;Transactional
    public void doOtherThing() {
        System.out.println(;保存role表数据;);
    }
}

我们可以看到事务方法add中;调用了事务方法doOtherThing;但是事务方法doOtherThing是在另外一个线程中调用的;这样会导致两个事务方法不在同一个线程中;获取到的数据库连接不一样;从而是两个不同的事务。如果想doOtherThing方法中抛了异常;add方法也回滚是不可能的。
如果看过spring事务源码的朋友;可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map;key是数据源;value是数据库连接。

private static final ThreadLocal<Map<Object, Object>> resources =
      new NamedThreadLocal<>(;Transactional resources;);

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

10.嵌套事务多回滚了

public class UserService {
​
   ;Autowired
   private UserMapper userMapper;
​
   ;Autowired
   private RoleService roleService;
​
   ;Transactional
   public void add(UserModel userModel) throws Exception {
       userMapper.insertUser(userModel);
       roleService.doOtherThing();
   }
}
​
;Service
public class RoleService {
​
   ;Transactional(propagation = Propagation.NESTED)
   public void doOtherThing() {
       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-Boot-+-Flowable-快速实现工作流

    Spring-Boot-+-Flowable-快速实现工作流,So-Easy!,被分配的老师用户执行后流向 【网关】,网关以此检查每个出口,流向符合条件的任务,比如这里老师执行任务时是同意,就流向【校长】节点,等待该任务执行。act_ru_execution:运行时流程执行实例表,记录运行中流程运行的各个分支信息(当没有子流程时,其数据与act_ru_task表数据是一一对应的);act_ru_task:运行时流程任务节点表,存储运行中流程的任务节点信息,重要,常用于查询人员或部门的待办任务时使用;带有这...
  • 带你玩转SpringCloud实战

    一篇文章带你玩转SpringCloud实战,包含Spring Cloud Netflix以及Spring Cloud Alibaba所有组件...

网友评论

快盘下载暂未开通留言功能。

关于我们| 广告联络| 联系我们| 网站帮助| 免责声明| 软件发布

Copyright 2019-2029 【快快下载吧】 版权所有 快快下载吧 | 豫ICP备10006759号公安备案:41010502004165

声明: 快快下载吧上的所有软件和资料来源于互联网,仅供学习和研究使用,请测试后自行销毁,如有侵犯你版权的,请来信指出,本站将立即改正。