vlambda博客
学习文章列表

如何避免掉进【spring事务】不生效的坑中

大家好,我是宇哥


前言

你之前开发中,有没有遇到过一些加了事务@Transactional注解,但事务没有生效的的情况?反正我之前就遇到过,今天就总结整理一下。下面我们看看都有哪些导致事务不生效,也防止入坑。


spring事务的原理

从所周知spring事务底层是基于aop思想的,对目标类中的方法,进行动态代理实现的事务控制。我们看看底层核心代码TransactionAspectSupport类

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional. TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { //1开启事务 Standard transaction demarcation with getTransaction and commit/rollback calls.      TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal; try { // This is an around advice: Invoke the next interceptor in the chain. // 2执行目标方法This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // 回滚事务target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); }      commitTransactionAfterReturning(txInfo);//3提交事务 return retVal; }

注:123处的代码注释,你自己跟一下源码,记忆会更深刻,就能找到DataourceTransactionManager


事务不生效——声明为final

 
   
   
 
@Transactional public final void add(User user) {  userMapper.insert(user); }

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


事务不生效——声明为private

@Transactional prvate void add(User user) {    userMapper.insert(user); }

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

你怎么知道的源码:TransactionalRepositoryProxyPostProcessor

private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } ....


事务不生效——内部方法调用

控制层直接调用方法是newFile

@Overridepublic RestfulResponse<String> newFile(String fileId, String extraInfo, SecurityUserInfo userDto) { // 保存文件到阿里云oss  String url = ossService.putObject(objectName, ControllerConstant.EMPTY_TABLE_COMPRESSED_SSJSON.getBytes()); // 保存文件和记录加入一个事务控制中 saveFileAndHistory(fileId, userDto, objectName, url);}@Transactional// 保存基本信息public void saveFileAndHistory(String fileId, SecurityUserInfo userDto, String targetObjectName, String url) { saveFile(fileId, null, userDto); tableService.saveOssUrlToFileHistory(fileId, 0L, url);}

前面我们说过,spring底层是基于aop的动态代理实现,所以只能针对被调用的目标对象上@Transactional注解加上事务的代码,而saveFileAndHistory方法是内部方法,this(对象)调用的,所以不会产生事务代码。


事务不生效——当前类没有被spring管理

//@Service 或者加了,没有扫描到注解public class UserService{ @Transactional  public void add(User user) {  userMapper.insert(user);  }}

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


事务不生效——错误的spring事务传播特性(一般不会碰到)

@Transactional(propagation = Propagation.NEVER)public void add(User user) {  userMapper.insert(user); }

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


事务不生效——数据库不支持事

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


事务不生效——自己吞了异常

 @Transactional  public void add(User user) {  try{ userMapper.insert(user);  } catch(Exception e){       e.printStack(); } }

一旦出现异常,就被catch捕住了,也就不会向外抛了,前面我们开spring底层源时看到这么一段代码,目标方法抛出的异常会捕获,然后回滚事务。如下代码:

catch (Throwable ex) { // 回滚事务target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex;}


事务不生效——抛出的异常不正确

 @Transactional  public void add(User user) {  try{ userMapper.insert(user);  } catch(Exception e){ throw new Exception(e); } }

有人可能发现不对啊,上面刚刚看到补的是Throwable啊,而抛的Exception是它的子,没问题啊。这个理解没问题,但是看@Transactional注解里如下

 /** * Defines zero (0) or more exception {@link Class classes}, which must be * subclasses of {@link Throwable}, indicating which exception types must cause * a transaction rollback. * <p>默认情况By default, a transaction will be rolling back on {@link RuntimeException} * and {@link Error} but not on checked exceptions(不是检查异常) (business exceptions). See * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)} * for a detailed explanation. * <p>This is the preferred way to construct a rollback rule (in contrast to * {@link #rollbackForClassName}), matching the exception class and its subclasses. * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}. * @see #rollbackForClassName * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable) */ Class<? extends Throwable>[] rollbackFor() default {};

中间一段说的DefaultTransactionAttribute此类是具体实现

@Overridepublic boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error);}

意思异常类型必须是RuntimeExceptionError这两个类型及子类型,Exception不在这个范围内,所以抛出的Exception事务是不会回滚的


事务不生效——多线程调用

@Autowiredprivate SportService sportService;@Transactional public void add(User user) {  userMapper.insert(user);  new Thread(() -> {          sportService.doLikeThing(); }).start();}
@Servicepublic class SportService{ @Transactional  public void doLikeThing(){    sportMapper.insert(x); }}

我们看到调用add方法是在主线程上,而调用doLikeThing方法另外一个线程,由于他们不在一个线程,拿到的数据库连接也不一样,因

为spring事务是基于连接来实现的,所以两个方法分别在不同的事务中。互相

不受影响


事务不生效——嵌套事务多回滚了


@Autowiredprivate SportService sportService;@Transactional public void add(User user) {    userMapper.insert(user);    sportService.doLikeThing();}

@Servicepublic class SportService{ @Transactional  public void doLikeThing(){ sportMapper.insert(x); }}

这种就是内部事务嵌套,有时我们可能想doLikeThing方法异常,只回滚它自已

但,add方法也回滚了。原因很简单就是

doLikeThing 没有捕获异常,直接向上抛到外层代理,自然外层事务也会回滚。

那么有没有什么办法,只让doLikeThing回滚,不影响外层呢,

把上面的第一段代码换成如下:

@Transactional public void add(User user) {  userMapper.insert(user);    try{ sportService.doLikeThing();   } catch(Exception e) {     log.error(e);   }}

上面捕获了就可以了,他就不会向上继续抛了。


总结:

上面的事务不生效的场景,大家有没有似曾相识的感觉 ,大部分都是比较容易入过

坑的。大家注意。仔细把这篇看完,以后基本就不会习惯性掉进深坑了。


快乐三连击:【点赞,在看,分享】 好东西又分享出来