服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Spring事务失效的各种场景总结及源码分析

Spring事务失效的各种场景总结及源码分析

2024-01-29 17:06Spring全家桶实战案例源码 Java教程

在本文中,深入探讨了Spring事务失效的各种情况。通过了解这些情况,我们可以更好地理解事务管理在Spring框架中的重要性,以及如何避免和解决事务失效的问题。

环境:Spring5.3.23

1. 简介

在Spring框架中,事务管理是保障数据一致性和系统可靠性的重要手段。但在实际开发中,Spring事务失效的问题却时有发生。本文将总结并分析Spring事务失效的各种场景,帮助你全面了解事务失效的原因和解决方案,让你不再被事务问题困扰。。让我们一起揭开Spring事务失效的神秘面纱,迎接更稳健、高效的系统开发之旅!

2. 事务失效场景

2.1 非public方法

@Transactional
protected void save() {
  Person person = new Person();
  person.setAge(36);
  person.setName("张三");
  int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(),
      person.getName());
  System.out.println("save Db Update " + result + " 次");
  System.out.println(1 / 0) ;
}

以上方法是protected修饰的,事务将失效,默认Spring支持支public修饰的方法。如何让Spring支持非public方法呢?可以通过如下方法修改

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
  // 设置为false,这样protected及默认修饰的方法都将支持事务功能
  return new AnnotationTransactionAttributeSource(false) ;
}

该要想上面bean生效,你还需要开启如下功能

GenericApplicationContext context = new GenericApplicationContext();
// 允许Bean覆盖,后面的BeanDefintion能覆盖前面的
// 我们定义的transactionAttributeSource bena能够覆盖系统默认的
context.setAllowBeanDefinitionOverriding(true) ;

2.2 异常被吞

@Transactional
protected void save() {
  try {
    // ...
    int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(),
        person.getName());
    System.out.println(1 / 0) ;
  } catch (Exception e) {
    e.printStackTrace() ;
  }
}

上面代码将异常信息捕获了后并没有再进行抛出。Spring 事务的原理就是根据你代码执行时是否发生了异常来控制事务是否回滚。源码如下:

Spring事务的核心拦截器TransactionInterceptor

public abstract class TransactionAspectSupport {
  protected Object invokeWithinTransaction(...) throws Throwable {
    TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
    Object retVal;
      try {
        // 执行实际的业务代码调用
        retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
        // 执行事务回滚
        completeTransactionAfterThrowing(txInfo, ex);
        // 继续抛出,终止向下执行
        throw ex;
      }
      finally {
        cleanupTransactionInfo(txInfo);
      }
      // 没有异常则进行事务的提交
      commitTransactionAfterReturning(txInfo);
  }
}

2.3 回滚异常类设置错误

Spring事务回滚策略是只会回滚RuntimeException与Error类型的异常和错误。

@Transactional
protected void save() throws Exception {
  try {
    Person person = new Person();
    person.setAge(36);
    person.setName("张三");
    int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(),
        person.getName());
    System.out.println("save Db Update " + result + " 次");
    System.out.println(1 / 0) ;
  } catch (Exception e) {
    e.printStackTrace() ;
    throw new Exception(e) ;
  }
}

这里并没有设置rollbackFor属性,所以这里事务不会被回滚。回滚逻辑处理如下:

public abstract class TransactionAspectSupport {
  protected Object invokeWithinTransaction() {
    try {
      retVal = invocation.proceedWithInvocation();
    }
    catch (Throwable ex) {
      // 回滚处理
      completeTransactionAfterThrowing(txInfo, ex);
      throw ex;
    }
  }
  protected void completeTransactionAfterThrowing() {
    // 检查异常
    if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
      try {
        txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
      }
    }
  }
}
public abstract class DelegatingTransactionAttribute {
  // 实现类是下面的RuleBasedTransactionAttribute
  private final TransactionAttribute targetAttribute;
  public boolean rollbackOn(Throwable ex) {
    return this.targetAttribute.rollbackOn(ex);
  }
}
public class RuleBasedTransactionAttribute {
  public boolean rollbackOn(Throwable ex) {
    RollbackRuleAttribute winner = null;
    int deepest = Integer.MAX_VALUE;


    // 遍历处理你配置的rollbackFor属性配置
    if (this.rollbackRules != null) {
      for (RollbackRuleAttribute rule : this.rollbackRules) {
        int depth = rule.getDepth(ex);
        if (depth >= 0 && depth < deepest) {
          deepest = depth;
          winner = rule;
        }
      }
    }
    
    // 如果上没有找到异常,则进行默认行为的处理,检查异常类型
    if (winner == null) {
      return super.rollbackOn(ex);
    }


    return !(winner instanceof NoRollbackRuleAttribute);
  }
  public boolean rollbackOn(Throwable ex) {
    // 回滚是运行时及Error类型的异常或错误
    return (ex instanceof RuntimeException || ex instanceof Error);
  }
}

2.4 同一类中方法互相调用

protected void save() {
  // ...
  this.updatePerson()
}
@Transactional
public void updatePerson() {
  // ...
}

上面的事务将会失效,因为在save中通过this调用updatePerson,而这时的this是原始对象,并不是当前容器中生成的那个代理对象,通过如下方式解决:

方式1:

protected void save() {
  // 通过AopContext获取当前代理对象
  PersonService proxy = (PersonService)AopContext.currentProxy() ;
  proxy.save() ;
}

这种方式,不推荐;这将你的代码与Spring AOP完全耦合,并使类本身意识到它正在AOP上下文中使用,这与AOP背道而驰。

方式2:

自己注入自己

@Resource
private PersonService personService ;
public void save() {
  personService.save() ;
}

2.5 方法被final修饰

@Transactional
protected final void save() {
  // ...
}

方法被final修饰,cglib是通过继承的方式实现代理,final修饰后将不能重写save方法。程序抛出NPE异常

Exception in thread "main" java.lang.NullPointerException
  at com.pack.main.transaction.TransactionNoPublicMethodMain2$PersonService.save(TransactionNoPublicMethodMain2.java:98)

因为无法重写save方法,首先是没法对方法进行增强处理,其次只能调用父类的save方法,而父类中的所有属性(需要注入的)都将是null。

2.6 传播类型设置错误

@Transactional(propagation = Propagation.NOT_SUPPORTED)
protected void save() {
  // ...
}

或者是设置为Propagation.NEVER,这都将使得事务失效。部分源码:

public abstract class TransactionAspectSupport {
  protected Object invokeWithinTransaction() {
    // 使用getTransaction和commit/rollback调用进行标准事务划分。
    TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
  }
  protected TransactionInfo createTransactionIfNecessary() {
    // 调用事务管理器获取事务对象
    status = tm.getTransaction(txAttr);
  }
}
public abstract class AbstractPlatformTransactionManager {
  public final TransactionStatus getTransaction() {
    // 根据配置的事务传播属性进行相应的处理
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
      throw new IllegalTransactionStateException(
          "No existing transaction found for transaction marked with propagation 'mandatory'");
    }
    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
        def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
        def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    } else {
      // 创建“空”事务:没有实际的事务,但可能是同步。
      boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
      return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    }
  }
}

2.7 异步线程执行

在一个事务方法中开启新的线程执行事务方法

@Transactional()
protected void save() {
  new Thread(() -> {
    Person person = new Person();
    person.setAge(36);
    person.setName("张三");
    int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(),
        person.getName());
    System.out.println("save Db Update " + result + " 次");
    System.out.println(1 / 0) ;
  }).start() ;
  try {
    TimeUnit.SECONDS.sleep(3) ;
  } catch (InterruptedException e) {}
}

上面的事务将不会生效,这是因为主线程与子线程使用的不是同一个Connection对象,Spring事务执行会为每一个执行线程绑定一个Connection对象。源码如下:

public abstract class AbstractPlatformTransactionManager {
  // 开始新的事务
  private TransactionStatus startTransaction() {
    doBegin(transaction, definition);
  }
}
public class DataSourceTransactionManager {
  protected void doBegin(...) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;
    try {
      if (!txObject.hasConnectionHolder() ||
          txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
        // 获取连接对象
        Connection newCon = obtainDataSource().getConnection();
        txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
      }
      // 将连接对象绑定到当前线程上
      if (txObject.isNewConnectionHolder()) {
        TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
      }
    } 
  }
}

你新启动的线程是拿不到主线程中的Connection。

2.8 数据库不支持

在MySQL建表时指定了错误的引擎,比如使用了MyISAM。mysql支持哪些引擎及事务支持情况如下:

支持事务的只有InnoDB。在建表时明确指定引擎。

通过上面的方式制定ENGINE=InnoDB。

2.9 关于@Transactional注解使用错误的情况

有些人说使用了错误的@javax.transaction.Transactional注解。通过源码分析

Spring在定义事务的切面时,会使用TransactionAttributeSource来判断当前的类上或者是方法上是否有@Transactional注解

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
  return new AnnotationTransactionAttributeSource();
}
public class AnnotationTransactionAttributeSource {
  private static final boolean jta12Present;
  private static final boolean ejb3Present;
  static {
    // 判断是否存在该注解类
    jta12Present = ClassUtils.isPresent("javax.transaction.Transactional", classLoader);
  }
  public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
    this.publicMethodsOnly = publicMethodsOnly;
    if (jta12Present || ejb3Present) {
      this.annotationParsers = new LinkedHashSet<>(4);
      this.annotationParsers.add(new SpringTransactionAnnotationParser());
      if (jta12Present) {
        // 如果存在会加入专门解析@javax.transaction.Transactional注解的解析器类
        this.annotationParsers.add(new JtaTransactionAnnotationParser());
      }
      if (ejb3Present) {
        this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
      }
    }
    else {
      this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
    }
  }
}

所以如果你类路径下只要存在,那么你的事务还是可以生效的。

总结:在本文中,深入探讨了Spring事务失效的各种情况。通过了解这些情况,我们可以更好地理解事务管理在Spring框架中的重要性,以及如何避免和解决事务失效的问题。

完毕!!!

原文地址:https://mp.weixin.qq.com/s/pFvX4Ybn_nu4Zrb0PapFxQ

延伸 · 阅读

精彩推荐
  • Java教程Spring Boot项目利用Redis实现集中式缓存实例

    Spring Boot项目利用Redis实现集中式缓存实例

    本篇文章主要介绍了Spring Boot项目利用Redis实现集中式缓存实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    SkyeBeFreeman2972020-11-13
  • Java教程Java Set简介_动力节点Java学院整理

    Java Set简介_动力节点Java学院整理

    Set最大的特性就是不允许在其中存放的元素是重复的。接下来通过本文给大家分享java set常用方法和原理分析,需要的的朋友参考下吧...

    动力节点2982020-09-23
  • Java教程MyBatis中OGNL的使用教程详解

    MyBatis中OGNL的使用教程详解

    有些人可能不知道MyBatis中使用了OGNL,有些人知道用到了OGNL却不知道在MyBatis中如何使用,下面这篇文章主要介绍了MyBatis中OGNL的使用教程,文中介绍的非常...

    isea5335622020-11-17
  • Java教程java基础的详细了解第二天

    java基础的详细了解第二天

    这篇文章对Java编程语言的基础知识作了一个较为全面的汇总,在这里给大家分享一下。需要的朋友可以参考,希望能给你带来帮助...

    zsr61355942021-11-11
  • Java教程java中TreeMap集合的常用方法详解

    java中TreeMap集合的常用方法详解

    本篇文章给大家带来的内容是关于java中TreeMap集合的常用方法详解,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。下面我们就来学...

    xiaostudy5172022-03-11
  • Java教程详解Java中的封装、继承、多态

    详解Java中的封装、继承、多态

    本文主要介绍了Java中的封装、继承、多态的相关知识,具有一定的参考价值,下面跟着小编一起来看下吧...

    追梦子3082020-07-28
  • Java教程Java 实现跨平台的操作方式

    Java 实现跨平台的操作方式

    这篇文章主要介绍了Java 实现跨平台的操作方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    陆佳骅的博客2352020-09-04
  • Java教程IDEA开启Run Dashboard的配置详解

    IDEA开启Run Dashboard的配置详解

    这篇文章主要介绍了IDEA开启Run Dashboard的配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下...

    脚印6312020-06-15