首页 > 其他分享 >Spring事务的这10种坑,坑坑致命!

Spring事务的这10种坑,坑坑致命!

时间:2022-10-10 11:33:06浏览次数:47  
标签:坑坑 10 事务 Spring userMapper public add userModel class

Spring事务的这10种坑,坑坑致命!_数据库连接

对于从事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中,并且不继续往抛异常。

标签:坑坑,10,事务,Spring,userMapper,public,add,userModel,class
From: https://blog.51cto.com/u_15430445/5742977

相关文章

  • 10.10
       因为子类继承了父类的成员变量和方法,所以在构造子类之前,必须先构造出一个父类,即调用父类的构造方法,初始化父类后,继承的子类才可以调用自身的构造函数初始化,而父类......
  • 2022-10-9科大讯飞笔试
    1.采用单缓冲时,将数据传入缓冲区110us,从缓冲区传入用户区40us,处理数据20us,采用双缓冲每次能节省多少时间? 采用双缓冲能节省下缓冲区传入用户区的时间,40us2.判断是否......
  • 代码随想录 day18|513. 找树左下角的值 112. 路径总和 113. 路径总和 II 105. 从前序
    513.找树左下角的值题目|文章1.前序遍历思路题目的要求是先是最底层最左边的节点的值,我们使用前序遍历可以保证是最左边的值,通过深度变化时对节点更新,可以保证是最底......
  • 百度翻译network里没有sug(文章发布时间2022年10月)
    百度翻译已经更新,现在的百度翻译分为两个阶段翻译,第一个阶段识别你的翻译字符是什么类型语言第二阶段生成随机sign加携带token以post表单方式上传数据,返回json数据尚......
  • SpringBoot+MyBatis Plus对Map中Date格式转换的处理
    在SpringBoot项目中,如何统一JSON格式化中的日期格式问题现在的关系型数据库例如PostgreSQL/MySQL,都已经对JSON类型提供相当丰富的功能,项目中对于不需要检索......
  • SpringBoot 整合邮件发送
    邮件发送更多参考:https://mrbird.cc/Spring-Boot-Email.html引入依赖在SpringBoot中发送邮件,需要用到spring-boot-starter-mail,引入spring-boot-starter-mail:<depend......
  • ORA-01653 表 PDM91.RAWSERVLETREQUESTSTATS 无法通过1024 (在表空间 USERS 中) 扩展
    问题解决办法第一步:查询各表空间使用率SELECTtotal.tablespace_name,Round(total.MB,2)ASTotal_MB,Round(total.MB-free.MB,2)ASU......
  • 10.9
    T1小朋友的数字题面分析动态规划求最长子段和,容易想到对于每一个数\(i\),最大子段不是\(i-1\)(之前的子段),就是以\(i\)为终点的一段前缀差,题面分数的解释很日龙......
  • #yyds干货盘点#【愚公系列】2022年10月 微信小程序-全局配置属性之入口页面
    前言一、entryPagePath1.入口文件的配置指定小程序的默认启动路径(首页),常见情景是从微信聊天列表页下拉启动、小程序列表启动等。如果不填,将默认为pages列表的第一项。......
  • 2022年10个用于时间序列分析的Python库推荐
    时间序列是数据点的序列,通常由在一段时间间隔内进行的连续测量组成。时间序列分析是使用统计技术对时间序列数据进行建模和分析,以便从中提取有意义的信息并做出预测的过程......