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_REQUIRED)事务默认
    @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中,并且不继续往外抛出异常。

原文:

https://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&mid=2247483852&idx=1&sn=e39ff9c4778c1d948347b725e539f8d4&chksm=f9800716cef78e00a8ba890694e030fa910e1fcabe50cac5a9db3c9aaab6b368916a87fa15dd&scene=21#wechat_redirect


相关文章
|
2月前
|
Java 数据库 Spring
Spring事务失效的场景详解
Spring事务失效的场景详解
34 0
|
5月前
|
存储 Java 数据库连接
spring事务失效的几种情况与原因
spring事务失效的几种情况与原因
67 0
|
9月前
|
Java 数据库 开发者
探究Spring事务:了解失效场景及应对策略
在现代软件开发中,数据的一致性和完整性是至关重要的。为了保证这些特性,Spring框架提供了强大的事务管理机制,让开发者能够更加自信地处理数据库操作。然而,事务并非银弹,存在一些失效的情景,本文将带您深入探究Spring事务及其失效场景,并为您呈现应对策略。
133 0
探究Spring事务:了解失效场景及应对策略
|
11月前
|
Java 数据库连接 数据库
这些让Spring事务失效的操作千万别犯!
在Spring框架中,事务是一种关键机制,用于确保数据库操作的一致性和完整性。然而,有时候事务可能会失效,导致意外的结果或数据不一致。下面是一些可能导致Spring事务失效的常见场景,以及相应的代码示例和解释。
这些让Spring事务失效的操作千万别犯!
|
11月前
|
SQL 缓存 NoSQL
踩坑:以为是Redis缓存没想到却是Spring事务!
最近碰到了一个Bug,折腾了我好几天。并且这个Bug不是必现的,出现的概率比较低。一开始我以为是旧数据的问题,就让测试重新生成了一下数据,重新测试。由于后面几轮测试均未出现,我也就没太在意。
踩坑:以为是Redis缓存没想到却是Spring事务!
|
12月前
|
存储 缓存 Java
每日一博 - 常见的Spring事务失效&事务不回滚案例集锦
每日一博 - 常见的Spring事务失效&事务不回滚案例集锦
77 0
|
12月前
|
存储 缓存 Java
(万字总结)spring事务失效的12种场景, 实在太坑了
为了保障操作的原子性, 避免数据不一样的情况,我们一般都会用到spring事务.但spring事务失效的12种场景, 实在太坑了
316 0
|
Java 开发者 Spring
聊聊spring事务失效的12种场景,太坑了(下)
聊聊spring事务失效的12种场景,太坑了
聊聊spring事务失效的12种场景,太坑了(下)
|
XML 缓存 Java
引起Spring事务失效的可能原因
引起Spring事务失效的可能原因
198 0
|
Java 数据库 Spring
Spring事务失效场景
如果@Transactional 没有特别指定,Spring 只会在遇到运行时异常RuntimeException或者error时进行回滚,而IOException等检查异常不会影响回滚。
242 0
Spring事务失效场景