首页 > 其他分享 >Spring框架中事务控制的运行原理

Spring框架中事务控制的运行原理

时间:2023-06-06 18:55:33浏览次数:48  
标签:事务管理 事务 transaction 框架 txInfo Spring null

Photo by Tomasz Filipek from Pexels: https://www.pexels.com/photo/nature-photography-of-flower-field-1646178/

Spring Transaction 基本介绍

我们在日常开发中经常使用Spring框架来实现事务管理。事务管理是指在执行一系列操作时,保证这些操作要么全部成功,要么全部失败,不会出现中间状态。事务管理可以保证数据的一致性和完整性,避免因为异常或错误导致的数据损坏。

Spring框架中的事务管理有两种实现方式:编程式事务管理声明式事务管理。编程式事务管理是指在代码中显式地控制事务的开始、提交和回滚,这种方式需要编写大量的重复代码,而且容易出错。声明式事务管理是指通过注解或配置文件来声明哪些方法需要进行事务控制,这种方式更简洁、灵活和优雅,也是Spring框架推荐的方式。

无论是哪种方式,Spring框架中的事务管理都是基于AOP(面向切面编程)的代理机制实现的。这让我们可以在不修改原有代码的情况下,为某些方法添加额外的功能或行为,比如日志、安全、缓存等。

声明式事务管理的基本使用

当我们使用声明式事务管理时,我们可以通过 @Transactional 注解来标注哪些方法需要进行事务控制,并且可以指定该注解的属性来设置事务的属性。例如:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 10)
    public void transferMoney(int fromId, int toId, double amount) {
        userDao.decreaseMoney(fromId, amount);
        userDao.increaseMoney(toId, amount);
    }
}

上面的代码表示transferMoney 方法需要进行事务控制,并且设置了传播行为为REQUIRED(表示如果当前没有事务,则创建一个新的事务,如果当前已经有事务,则加入该事务),隔离级别为READ_COMMITTED(表示只能读取已经提交的数据,可以避免脏读,但不能避免不可重复读和幻读),超时时间为10秒(表示如果该方法执行超过10秒,则自动回滚事务)。

当Spring框架扫描到@Transactional 注解时,它会根据注解的属性创建一个 TransactionDefinition 对象,并且会为被注解的类生成一个代理对象,该代理对象会实现和目标类相同的接口,并且会拦截目标类的所有方法。当调用被注解的方法时,代理对象会先从Spring容器中获取一个事务管理器,并且根据 TransactionDefinition 对象获取一个事务状态(TransactionStatus)对象,该对象包含了事务的信息和状态。然后代理对象会执行目标方法,如果目标方法正常返回,则代理对象会调用事务管理器的commit方法来提交事务;如果目标方法抛出异常,则代理对象会调用事务管理器的rollback方法来回滚事务。这样就实现了对目标方法的事务控制。

事务的传播行为

事务传播行为(Transaction Propagation)定义了一个事务方法与其他事务方法的关系,即当一个事务方法被调用时,如何处理现有的事务。在Spring框架中,可以通过设置@Transactional注解的propagation属性来指定事务传播行为。常用的事务传播行为包括:

  1. REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。这是最常用的传播行为。
  2. REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务。如果存在当前事务,会将当前事务挂起。
  3. SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。支持事务但不强制要求。
  4. NOT_SUPPORTED:以非事务方式执行操作,如果存在当前事务,则将其挂起。
  5. MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  6. NEVER:以非事务方式执行操作,如果存在当前事务,则抛出异常。
  7. NESTED:如果当前存在事务,则在嵌套事务中执行。如果当前没有事务,则创建一个新事务。嵌套事务是当前事务的一部分,具有独立的保存点,可以进行回滚。

事务隔离级别

事务隔离级别(Transaction Isolation)定义了事务之间的隔离程度,即一个事务对其他事务的可见性。在Spring框架中,可以通过设置@Transactional注解的isolation属性来指定事务隔离级别。常用的事务隔离级别包括:

  1. DEFAULT(默认):使用底层数据库的默认隔离级别。
  2. READ_UNCOMMITTED:允许读取未提交的数据,可能会导致脏读、不可重复读和幻读。
  3. READ_COMMITTED:只允许读取已提交的数据,可以防止脏读,但仍可能出现不可重复读和幻读。
  4. REPEATABLE_READ:确保在同一事务中多次读取数据时结果始终一致,可以防止脏读和不可重复读,但仍可能出现幻读。
  5. SERIALIZABLE:事务串行执行,确保所有并发事务都看到相同的数据状态,可以防止脏读、不可重复读和幻读。

需要注意的是,隔离级别越高,事务的并发性能可能会降低,因为需要锁定更多的资源以保证数据的一致性。

其他事务属性

在Spring Framework中,事务传播行为和隔离级别是事务管理的两个重要概念。它们决定了事务方法与其他事务方法之间的关系以及并发事务之间的相互影响。除此之外,还有一些常用设置如下

  • 事务的超时时间:定义了一个事务允许执行的最长时间
  • 事务的只读标志:定义了一个事务是否只进行查询操作,而不进行修改操作
  • 事务的回滚规则:定义了哪些异常会导致事务回滚,哪些异常不会导致事务回滚

。。。。

声明式事务管理的原理分析

通过上面的分析,我们可以看到Spring框架中事务控制的运行原理是基于AOP代理机制实现的,它可以在不修改原有代码的情况下,为指定的方法添加事务控制的逻辑。这样可以大大简化我们的编程工作,也可以提高我们的代码质量和可维护性。

讲解完基本的使用方法,下面我们一起深入源码来探寻事务控制的原理。下面是一个简化的伪代码,用来说明Spring框架中声明式事务管理的运行原理:

// 生成的代理对象
public class UserServiceImplProxy implements UserService {

    // 目标对象
    private UserServiceImpl target;

    // 事务管理器
    private PlatformTransactionManager transactionManager;

    // 事务定义
    private TransactionDefinition transactionDefinition;

    public void transferMoney(int fromId, int toId, double amount) {
        // 获取事务状态
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            // 调用目标方法
            target.transferMoney(fromId, toId, amount);
            // 提交事务
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(transactionStatus);
            // 抛出异常
            throw e;
        }
    }
}

Spring框架中事务管理的核心接口和类

在事务控制模块中一些重要的概念实现如下:

  • PlatformTransactionManager接口:定义了事务管理器的基本操作
    • DataSourceTransactionManager类:实现了基于JDBC的事务管理器
    • JpaTransactionManager类:实现了基于JPA的事务管理器
  • TransactionDefinition接口:定义了事务的属性和配置
  • TransactionStatus接口:定义了事务的状态和信息
  • TransactionInterceptor:AOP事务方法拦截器

Spring框架提供了一个接口 PlatformTransactionManager 来定义事务管理器的基本操作,比如获取事务状态、提交事务和回滚事务。不同的数据源需要实现不同的事务管理器,比如 JDBC 对应的是 DataSourceTransactionManager ,JPA对应的是 JpaTransactionManager 等。

从类图上来观察继承关系

Spring框架还提供了一个接口TransactionDefinition 来定义事务的属性,比如传播行为(propagation)隔离级别(isolation)超时时间只读标志等。

事务管理的核心逻辑:Transaction Interceptor

当我们理解了本小节开头的伪代码时,再来看TransactionInterceptor 内的实现会发现其实Spring的事务管理模块并不复杂,由于它实现了MethodInterceptor 所以直接看invoke 方法,可以看到调用了父类的invokeWithinTransaction 方法

@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
    // Work out the target class: may be {@code null}.
    // The TransactionAttributeSource should be passed the target class
    // as well as the method, which may be from an interface.
    Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

    // Adapt to TransactionAspectSupport's invokeWithinTransaction...
    return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
        @Override
        @Nullable
        public Object proceedWithInvocation() throws Throwable {
            return invocation.proceed();
        }
        @Override
        public Object getTarget() {
            return invocation.getThis();
        }
        @Override
        public Object[] getArguments() {
            return invocation.getArguments();
        }
    });
}

进入 invokeWithinTransaction 方法内,可以观察到其整体逻辑是和我们之前分析的伪代码类似的:

@Nullable
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 TransactionManager tm = determineTransactionManager(txAttr);
	
    if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager rtm) {
        // 省略响应式事务管理的逻辑
    }
	
    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)) {
        // Standard transaction demarcation with getTransaction and commit/rollback calls.
        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

        Object retVal;
        try {
            // This is an around advice: Invoke the next interceptor in the chain.
            // 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);
        }

        if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
            // Set rollback-only in case of Vavr failure matching our rollback rules...
            TransactionStatus status = txInfo.getTransactionStatus();
            if (status != null && txAttr != null) {
                retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
            }
        }

        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
....
}

暂且不要深入到嵌套方法内的细节,上述代码的整体逻辑为:

  1. 获取事务管理器
  2. 视情况创建事务
  3. 执行业务方法
  4. 当捕捉到异常判断是否回滚处理
  5. 无异常时判断是否提交处理

看完了整体,再深入到细节。在AbstractPlatformTransactionManager.getTransaction 方法体内,我们可以看到事务隔离级别的实现方式,源码中用了多个条件判断来控制不同隔离级别时事务的行为:

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

    // Use defaults if no transaction definition given.
    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

    Object transaction = doGetTransaction();
    boolean debugEnabled = logger.isDebugEnabled();

    if (isExistingTransaction(transaction)) {
        // Existing transaction found -> check propagation behavior to find out how to behave.
        return handleExistingTransaction(def, transaction, debugEnabled);
    }

    // Check definition settings for new transaction.
    if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
    }

    // No existing transaction found -> check propagation behavior to find out how to proceed.
    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) {
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
        }
        try {
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }
    else {
        // Create "empty" transaction: no actual transaction, but potentially synchronization.
        if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
            logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                    "isolation level will effectively be ignored: " + def);
        }
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    }
}

TransactionAspectSupport.completeTransactionAfterThrowing 方法内,可以观察当捕捉到异常判断是否回滚处理的逻辑:

/**
 * Handle a throwable, completing the transaction.
 * We may commit or roll back, depending on the configuration.
 * @param txInfo information about the current transaction
 * @param ex throwable encountered
 */
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
                    "] after exception: " + ex);
        }
        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            }
            catch (TransactionSystemException ex2) {
                logger.error("Application exception overridden by rollback exception", ex);
                ex2.initApplicationException(ex);
                throw ex2;
            }
            catch (RuntimeException | Error ex2) {
                logger.error("Application exception overridden by rollback exception", ex);
                throw ex2;
            }
        }
        else {
            // We don't roll back on this exception.
            // Will still roll back if TransactionStatus.isRollbackOnly() is true.
            try {
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            }
            catch (TransactionSystemException ex2) {
                logger.error("Application exception overridden by commit exception", ex);
                ex2.initApplicationException(ex);
                throw ex2;
            }
            catch (RuntimeException | Error ex2) {
                logger.error("Application exception overridden by commit exception", ex);
                throw ex2;
            }
        }
    }
}

最后则是在 TransactionAspectSupport.commitTransactionAfterReturning 方法中成功执行事务的提交逻辑:

/**
 * Execute after successful completion of call, but not after an exception was handled.
 * Do nothing if we didn't create a transaction.
 * @param txInfo information about the current transaction
 */
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
}

注意事项及最佳实践

下面是一些使用Spring框架进行事务控制时需要注意的问题:

  • @Transactional 注解默认只对 RuntimeExceptionError 进行回滚,对其他异常不进行回滚。如果需要对其他异常进行回滚,可以通过设置注解的rollbackFor 属性来指定。
  • @Transactional 注解只能应用在 public 方法上,对于private、protecteddefault方法无效。这是因为Spring框架默认使用JDK动态代理来生成代理对象,而JDK动态代理只能拦截public方法。如果需要拦截非public方法,可以通过设置Spring容器的aop:config标签的proxy-target-class属性为true,让Spring框架使用CGLIB动态代理来生成代理对象,在Spring Boot中则默认启用此属性。CGLIB动态代理是通过继承目标类来生成子类作为代理对象,因此可以拦截非public方法。
  • @Transactional 注解只能对外部调用有效,对于同一个类中的内部调用无效。这是因为同一个类中的内部调用并没有经过代理对象,而是直接调用了目标方法,因此无法触发AOP拦截和增强。如果需要对内部调用也进行事务控制,可以通过使用ApplicationContextAutowireCapableBeanFactory 来获取当前类的代理对象,然后通过代理对象来调用内部方法。
  • @Transactional 注解可以同时应用在接口、类和方法上,它们之间有继承和覆盖关系。
    • 注解的作用范围:如果在接口上使用注解,它将应用于所有实现该接口的类和方法。如果在类上使用注解,它将应用于该类中的所有方法。如果在方法上使用注解,它将仅应用于该方法。
    • 继承关系:当一个类继承另一个类时,子类继承了父类的@Transactional注解。但是,子类可以选择覆盖父类的注解,并使用自己的注解配置。这意味着子类可以通过覆盖注解来修改事务的传播行为、隔离级别等属性。

在日常的开发中,也最好遵循一些业界探索出的实践经验,以确保事务的正确管理和数据的一致性:

  1. 明确标记事务边界:使用@Transactional注解明确标记需要进行事务管理的方法。将注解放在方法上,以确保在方法执行期间启用事务管理。
  2. 设置适当的事务传播行为和隔离级别:根据业务需求,选择适当的事务传播行为和隔离级别。确保在不同的方法调用中正确管理事务的传播和隔离。
  3. 限制事务作用范围:将事务的作用范围限制在需要进行事务管理的最小代码块上,而不是整个方法或类。这样可以减少锁定资源的时间和范围,提高并发性能。
  4. 避免长时间事务:长时间事务会占用数据库资源并降低系统性能。尽量将事务的执行时间控制在合理的范围内,避免长时间的事务操作。
  5. 捕获并处理异常:在事务方法中捕获并处理异常是必要的。根据业务需求,选择适当的处理方式,如回滚事务、记录日志或抛出自定义异常。
  6. 尽量避免嵌套事务:嵌套事务会增加事务管理的复杂性,并可能导致死锁等并发问题。除非必要,尽量避免使用嵌套事务。
  7. 注意事务的回滚规则:通过设置rollbackFor属性,明确指定哪些异常会触发事务的回滚。确保异常的正确处理和事务的正确回滚。
  8. 定期进行性能调优和监控:对于事务频繁的应用程序,定期进行性能调优和监控是必要的。通过监控事务执行时间、数据库锁定情况等指标,优化事务管理和数据库设计。
  9. 编写单元测试验证事务管理:编写单元测试来验证事务管理的正确性。使用Spring的测试框架和事务支持来模拟数据库操作和事务回滚,确保事务管理的正确性。

总之,良好的事务管理实践可以确保数据的一致性和事务的正确执行。遵循上述最佳实践和注意事项,可以提高系统的稳定性和性能。

标签:事务管理,事务,transaction,框架,txInfo,Spring,null
From: https://www.cnblogs.com/novwind/p/17461448.html

相关文章

  • JPA、Hebernate、MyBatis、Spring Data JPA 的区别
    JPA是持久化的标准,是接口协议Hebernate和MyBatis是持久化技术的具体实现SpringDataJPA是在Hibernate的基础上更上层的封装实现1、Hibernate与Jpa的关系?https://www.zhihu.com/question/30691648......
  • Unity框架中的核心类
    组件:Component在Unity中,所有的游戏对象都可以挂载组件。组件控制着游戏对象的行为和外观,例如渲染、动画、碰撞检测等。而Component就是组件的基类,提供了一些通用的方法和属性,例如gameObject、transform等。Component类提供了许多方便的方法和属性,可以帮助我们方便地对游戏对象......
  • 关于TChunkedArray和UE5的ECS框架Mass
    在虚幻引擎中,TChunkArray是一个动态数组类型。它通过分配一系列固定大小的Chunk来管理Array中的元素。每个Chunk具有以下特征:1.固定大小,通常为4096个元素。该大小在TChunkArray定义时指定,之后所有Chunk的大小都是一致的。2.可以连续或不连续的分配在内存中。TChunk......
  • idea 创建 spring boot 项目
    1.创建 2.创建信息 next 点finish 3.创建好后,项目长这样: 4.配置maven。如果侧边没有maven选项卡,参考这篇https://www.cnblogs.com/cynthia-wuqian/p/17460845.html   5.启动项目 ......
  • Taurus.mvc .Net Core 微服务开源框架发布V3.1.7:让分布式应用更高效。
    前言:自首个带微服务版本的框架发布:Taurus.MVCV3.0.3微服务开源框架发布:让.NET架构在大并发的演进过程更简单已经过去快1年了,在这近一年的时间里,版本经历了N个版本的迭代。如今,是时候写文章介绍一下了:以下介绍中,仅以.NetCore6为示例代码。框架支持在.NetFramework2.0+......
  • 若依框架循环的form表单中配置权限
    页面中循环form表单 菜单中配置权限在字典管理的备注中,写权限 formb表单中,配置权限,直接从备注中获取权限标识 ......
  • 开始学习spring 最初配置 步骤
    一:新建项目idea-----newproject----在Buildsystem在选择Maven---然后选create创建二:在file中选择ProjectStructure ---- 然后选择Modules----在Depedencies(依赖)中选择 加号 然后在本地电脑上导入所需要的jar包,记得每个jar包之前要选择打上对勾, 然后点击A......
  • spring cloud 项目
    ###项目需求客户端:针对普通用户,用户登录、用户退出、菜品订购、我的订单。后台管理系统:针对管理员,管理员登录、管理员退出、添加菜品、查询菜品、修改菜品、删除菜品、订单处理、添加用户、查询用户、删除用户。![1](/Users/southwind/我的文件/商务合作/ai/项目实战/笔记/images/......
  • 《springboot冲刺棒》application.yml篇
    $是什么意思application.yml中的jdbc:mysql://${MYSQL-HOST:127.0.0.1}的$是什么意思application.yml中的${MYSQL-HOST:127.0.0.1}实际上是SpringBoot应用程序的属性占位符,具有允许在特定位置引用应用程序中定义的属性的功能。在这种情况下,${MYSQL-HOST:127.0.0.1}引用的......
  • Java革命性ORM框架之快速上手的Jimmer
    Jimmer是一款革命性的ORM框架,它的目标是提供一个简单易用的API,帮助开发人员更加轻松地操作数据库。Jimmer使用了Java8的新特性,如Lambda表达式和StreamAPI,使得代码更加简洁、易读。本文将详细介绍Jimmer的基本用法,并提供一些Java代码案例说明。1.什么是ORM?在介绍Jimmer之前,首......