【Spring从成神到升仙系列 四】从源码分析 Spring 事务的来龙去脉

  • 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
  • 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
  • 📝联系方式:hls1793929520,加我进群,大家一起学习,一起进步👀

在这里插入图片描述

文章目录

  • Spring 事务源码解析
    • 一、引言
    • 二、事务的本质
      • 1、JDBC的事务
      • 2、Spring的事务
        • 2.1 xml配置
        • 2.2 注解配置
    • 三、Spring事务源码剖析
      • 1、TransactionManager
        • 1.1 获取事务
        • 1.2 提交事务
        • 1.3 回滚事务
      • 2、 事务AOP的实现
        • 2.1 为什么使用AOP?
        • 2.2 @EnableTransactionManagement
        • 2.3 TransactionInterceptor
        • 2.4 XML配置
      • 四、流程图
      • 五、总结

Spring 事务源码解析

一、引言

对于Java开发者而言,关于 Spring ,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。

但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。

本期 Spring 源码解析系列文章,将带你领略 Spring 源码的奥秘

本期源码文章吸收了之前 Kafka 源码文章的错误,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。

废话不多说,发车!

本篇目录如下:
请添加图片描述

本文流程图可关注公众号:爱敲代码的小黄,回复:事务 获取
贴心的小黄为大家准备的文件格式为 POS文件,方便大家直接导入 ProcessOn 修改使用

二、事务的本质

  数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

  事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。

  一个逻辑工作单元要成为事务,必须满足所谓的 ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。

image.png

1、JDBC的事务

我们来看一下在 JDBC 中对事务的操作处理:

public class JDBCTransactionExample {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt1 = null;
        PreparedStatement pstmt2 = null;

        try {
            // 加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
            // 关闭自动提交,开启事务
            conn.setAutoCommit(false);

            // 创建SQL语句
            String sql1 = "UPDATE account SET balance = balance - ? WHERE id = ?";
            String sql2 = "UPDATE account SET balance = balance + ? WHERE id = ?";

            // 创建PreparedStatement对象
            pstmt1 = conn.prepareStatement(sql1);
            pstmt2 = conn.prepareStatement(sql2);

            // 设置参数
            pstmt1.setDouble(1, 1000);
            pstmt1.setInt(2, 1);
            pstmt2.setDouble(1, 1000);
            pstmt2.setInt(2, 2);

            // 执行更新操作
            int count1 = pstmt1.executeUpdate();
            int count2 = pstmt2.executeUpdate();

            if (count1 > 0 && count2 > 0) {
                System.out.println("转账成功");
                // 提交事务
                conn.commit();
            } else {
                System.out.println("转账失败");
                // 回滚事务
                conn.rollback();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            try {
                if (conn != null) {
                    // 回滚事务
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            try {
                if (pstmt1 != null) {
                    pstmt1.close();
                }
                if (pstmt2 != null) {
                    pstmt2.close();
                }
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

上面的代码,我相信大部分的人都应该接触过,这里也就不多说了

主要我们看几个重点步骤:

  • 获取连接:Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password")
  • 关闭自动提交,开启事务:conn.setAutoCommit(false)
  • 提交事务:conn.commit()
  • 回滚事务:conn.rollback()

2、Spring的事务

我们在日常生产项目中,项目由 ControllerSerivceDao 三层进行构建。

image.png

我们从上图中可以了解到:

对于 addUser 方法实际对于数据调用来说,分别调用了 insertUser()insertLog 方法,对数据库的操作为两次

我们要保证 addUser 方法是符合事务定义的。

2.1 xml配置

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
	<!-- 开启扫描 -->
	<context:component-scan base-package="com.dpb.*"></context:component-scan>

	<!-- 配置数据源 -->
	<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
		<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
		<property name="username" value="pms"/>
		<property name="password" value="pms"/>
	</bean>

	<!-- 配置JdbcTemplate -->
	<bean class="org.springframework.jdbc.core.JdbcTemplate" >
		<constructor-arg name="dataSource" ref="dataSource"/>
	</bean>

	<!-- 
	Spring中,使用XML配置事务三大步骤:  
		1. 创建事务管理器  
		2. 配置事务方法  
		3. 配置AOP
	 -->
	 <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
	 	<property name="dataSource" ref="dataSource"/>
	 </bean>
	 <tx:advice id="advice" transaction-manager="transactionManager">
	 	<tx:attributes>
	 		<tx:method name="fun*" propagation="REQUIRED"/>
	 	</tx:attributes>
	 </tx:advice>
	 <!-- aop配置 -->
	 <aop:config>
		 <aop:pointcut expression="execution(* *..service.*.*(..))" id="tx"/>
	 	 <aop:advisor advice-ref="advice" pointcut-ref="tx"/>
	 </aop:config>
</beans>

2.2 注解配置

首先必须要添加 @EnableTransactionManagement 注解,保证事务注解生效

@EnableTransactionManagement
public class AnnotationMain {
    public static void main(String[] args) {
    }
}

其次,在方法上添加 @Transactional 代表注解生效

@Transactional
public int insertUser(User user) {
    userDao.insertUser();
    userDao.insertLog();
    return 1;
}

上面的操作涉及两个重点:

  • 事务的传播属性

  • 事务的隔离级别

三、Spring事务源码剖析

本次剖析源码我们会尽量挑重点来讲,因为事务源码本身就是依靠 AOP 实现的,我们之前已经很详细的讲过 IOCAOP 的源码实现了,这次带大家一起过一遍事务即可。

因为从博主本身而言,我感觉 Spring 事务其实没有那么的重要,面试也不常考,所以不会花大量的时间去剖析细节源码。

1、TransactionManager

首先我们看一下这个接口的一些组成配置:

image.png****

这里我们重点看 PlatformTransactionManager 的实现,其实现一共三个方法:

  • 获取事务:TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
  • 提交事务:void commit(TransactionStatus status)
  • 回滚事务:void rollback(TransactionStatus status)

我们分别看一下其如何实现的

1.1 获取事务

我们想一下,在获取事务这一阶段,我们会做什么功能呢?

参考上述我们 JDBC 的步骤,这个阶段应该会 创建连接并且开启事务

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition){
    
   // PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED都需要新建事务
   if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
         def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
         def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
      //没有当前事务的话,REQUIRED,REQUIRES_NEW,NESTED挂起的是空事务,然后创建一个新事务
      SuspendedResourcesHolder suspendedResources = suspend(null);
      try {
         // 看这里重点:开始事务
         return startTransaction(def, transaction, debugEnabled, suspendedResources);
      }
      catch (RuntimeException | Error ex) {
         // 恢复挂起的事务
         resume(null, suspendedResources);
         throw ex;
      }
   }
}

private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
    // 是否需要新同步
	 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
	 // 创建新的事务
	 DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
	// 【重点】开启事务和连接
	doBegin(transaction, definition);
	// 新同步事务的设置,针对于当前线程的设置
	prepareSynchronization(status, definition);
	return status;
}

protected void doBegin(Object transaction, TransactionDefinition definition) {
    		// 判断事务对象没有数据库连接持有器
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				// 【重点】通过数据源获取一个数据库连接对象
				Connection newCon = obtainDataSource().getConnection();
				// 把我们的数据库连接包装成一个ConnectionHolder对象 然后设置到我们的txObject对象中去
             // 再次进来时,该 txObject 就已经有事务配置了
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}
    		
    		// 【重点】获取连接
    		con = txObject.getConnectionHolder().getConnection();
    
    
			// 为当前的事务设置隔离级别【数据库的隔离级别】
			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			// 设置先前隔离级别
			txObject.setPreviousIsolationLevel(previousIsolationLevel);
			// 设置是否只读
			txObject.setReadOnly(definition.isReadOnly());

			// 关闭自动提交
			if (con.getAutoCommit()) {
				//设置需要恢复自动提交
				txObject.setMustRestoreAutoCommit(true);
				// 【重点】关闭自动提交
				con.setAutoCommit(false);
			}

			// 判断事务是否需要设置为只读事务
			prepareTransactionalConnection(con, definition);
			// 标记激活事务
			txObject.getConnectionHolder().setTransactionActive(true);

			// 设置事务超时时间
			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}

			// 绑定我们的数据源和连接到我们的同步管理器上,把数据源作为key,数据库连接作为value 设置到线程变量中
			if (txObject.isNewConnectionHolder()) {
				// 将当前获取到的连接绑定到当前线程
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			}
		}
}

到这里,我们的 获取事务 接口完成了 数据库连接的创建关闭自动提交(开启事务),将 Connection 注册到了缓存(resources)当中,便于获取。

1.2 提交事务

public final void commit(TransactionStatus status) throws TransactionException {
		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
		// 如果在事务链中已经被标记回滚,那么不会尝试提交事务,直接回滚
		if (defStatus.isLocalRollbackOnly()) {
			// 不可预期的回滚
			processRollback(defStatus, false);
			return;
		}

		// 设置了全局回滚
		if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
			// 可预期的回滚,可能会报异常
			processRollback(defStatus, true);
			return;
		}

		// 【重点】处理事务提交
		processCommit(defStatus);
}

// 处理提交,先处理保存点,然后处理新事务,如果不是新事务不会真正提交,要等外层是新事务的才提交,
// 最后根据条件执行数据清除,线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
private void processCommit(DefaultTransactionStatus status) throws TransactionException {;
     // 如果是独立的事务则直接提交
	  doCommit(status);
                                                                             //根据条件,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
	  cleanupAfterCompletion(status);
}

这里比较重要的有两个步骤:

  • doCommit:提交事务(直接使用 JDBC 提交即可)

    protected void doCommit(DefaultTransactionStatus status) {
       DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
       Connection con = txObject.getConnectionHolder().getConnection();
       try {
          // JDBC连接提交
          con.commit();
       }
       catch (SQLException ex) {
          throw new TransactionSystemException("Could not commit JDBC transaction", ex);
       }
    }
    
  • cleanupAfterCompletion:数据清除,与线程中的私有资源解绑,方便释放

    // 线程同步状态清除
    TransactionSynchronizationManager.clear();
    
    // 清除同步状态【这些都是线程的缓存,使用ThreadLocal的】
    public static void clear() {
        synchronizations.remove();
        currentTransactionName.remove();
        currentTransactionReadOnly.remove();
        currentTransactionIsolationLevel.remove();
        actualTransactionActive.remove();
    }
    // 如果是新事务的话,进行数据清除,线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接等
    doCleanupAfterCompletion(status.getTransaction());
    
    // 此方法做清除连接相关操作,比如重置自动提交啊,只读属性啊,解绑数据源啊,释放连接啊,清除链接持有器属性
    protected void doCleanupAfterCompletion(Object transaction) {
    		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    		// 将数据库连接从当前线程中解除绑定
    		TransactionSynchronizationManager.unbindResource(obtainDataSource());
    
    		// 释放连接
    		Connection con = txObject.getConnectionHolder().getConnection();
        
    		// 恢复数据库连接的自动提交属性
    		con.setAutoCommit(true);
          // 重置数据库连接
    	   DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
    		
    		// 如果当前事务是独立的新创建的事务则在事务完成时释放数据库连接
    		DataSourceUtils.releaseConnection(con, this.dataSource);
    		
    
    		// 连接持有器属性清除
    		txObject.getConnectionHolder().clear();
    	}
    

这就是我们提交事务的操作了,总之来说,主要就是 调用JDBC的commit提交清除一系列的线程内部数据和配置

1.3 回滚事务

public final void rollback(TransactionStatus status) throws TransactionException {
   DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
   processRollback(defStatus, false);
}

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
	 // 回滚的擦欧洲哦
    doRollback(status);
    
    // 回滚完成后回调
    triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

	 // 根据事务状态信息,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
	cleanupAfterCompletion(status);
}

protected void doRollback(DefaultTransactionStatus status) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
		Connection con = txObject.getConnectionHolder().getConnection();
		// jdbc的回滚
		con.rollback();
}

回滚事务,简单来说 调用JDBC的rollback清除数据

2、 事务AOP的实现

我们来思考一下,利用 TransactionManager 重写一下我们第一步的 JDBC 的功能

@Autowired
	private UserDao userDao;

	@Autowired
	private PlatformTransactionManager txManager;

	@Autowired
	private LogService logService;

	@Transactional
	public void insertUser(User u) {

		// 1、创建事务定义
		DefaultTransactionDefinition definition = new DefaultTransactionDefinition();

		// 2、根据定义开启事务
		TransactionStatus status = txManager.getTransaction(definition);

		try {
			this.userDao.insert(u);
			Log log = new Log(System.currentTimeMillis() + "", System.currentTimeMillis() + "-" + u.getUserName());
			// this.doAddUser(u);
			this.logService.insertLog(log);
			// 3、提交事务
			txManager.commit(status);
		} catch (Exception e) {
			// 4、异常了,回滚事务
			txManager.rollback(status);
			throw e;
		}
	}

大家看到上述代码及思考一下 AOP 的作用和源码,有没有一丝丝想法

比如我现在是面试官,问你一个问题:你怎么去设计 Spring 的事务

如果一点点想法都没有的话,也不用着急,我们来慢慢的分析

2.1 为什么使用AOP?

我们想,如果我们要实现事务,在每一个方法里面都需要进行以下三个步骤:

  • 获取事务
  • 提交事务
  • 回滚事务

是不是显得我们的代码很臃肿,那么我们能不能把这三个步骤搞成一个通用化的东西

一些代码在方法前执行,一些代码方法后执行

这个时候,你是不是就想到了 AOP(切面编程)了

当然,Spring 中也是如此做的,利用 AOP 来对事务进行了统一的包装实现

2.2 @EnableTransactionManagement

我们知道了使用 AOP 技术实现,那到底是如何实现的呢?

我们从 @EnableTransactionManagement 注解聊起,我们点进该注解:

@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

很明显,TransactionManagementConfigurationSelector 类是我们主要关注的内容

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

   /**
    * 此处是AdviceMode的作用,默认是用代理,另外一个是ASPECTJ
    */
   @Override
   protected String[] selectImports(AdviceMode adviceMode) {
      switch (adviceMode) {
         case PROXY:
            return new String[] {AutoProxyRegistrar.class.getName(),
                  ProxyTransactionManagementConfiguration.class.getName()};
         case ASPECTJ:
            return new String[] {determineTransactionAspectClass()};
         default:
            return null;
      }
   }
}

一共注册了两个:

  • AutoProxyRegistrar.class:注册AOP处理器

  • ProxyTransactionManagementConfiguration.class:代理事务配置,注册事务需要用的一些类,而且Role=ROLE_INFRASTRUCTURE都是属于内部级别的

    @Configuration(proxyBeanMethods = false)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    
       @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
       @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
       public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
             TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
    		// 【重点】注册了 BeanFactoryTransactionAttributeSourceAdvisor 的 advisor
           // 其 advice 为 transactionInterceptor
          BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
          advisor.setTransactionAttributeSource(transactionAttributeSource);
          advisor.setAdvice(transactionInterceptor);
          if (this.enableTx != null) {
             advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
          }
          return advisor;
       }
    
       @Bean
       @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
       public TransactionAttributeSource transactionAttributeSource() {
          return new AnnotationTransactionAttributeSource();
       }
    
       @Bean
       @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
       public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
          TransactionInterceptor interceptor = new TransactionInterceptor();
          interceptor.setTransactionAttributeSource(transactionAttributeSource);
          if (this.txManager != null) {
             interceptor.setTransactionManager(this.txManager);
          }
          return interceptor;
       }
    
    }
    

    到这里,看到 BeanFactoryTransactionAttributeSourceAdvisoradvisor 结尾的类,AOPDNA 应该动了,其 advicetransactionInterceptor

    到这里,我们思考一下,利用我们之前学习到的 AOP 的源码,猜测其运行逻辑:

    • 我们在方法上写上 @EnableTransactionManagement 注解,Spring 会注册一个 BeanFactoryTransactionAttributeSourceAdvisor 的类
    • 创建对应的方法 Bean 时,会和 AOP 一样,利用该 Advisor 类生成对应的代理对象
    • 最终调用方法时,会调用代理对象,并通过环绕增强来达到事务的功能

2.3 TransactionInterceptor

我们从上面看到其 advice 正是 TransactionInterceptor,那自然不用多说

从上篇 AOP 得知,代理对象运行时,会拿到所有的 advice 并进行排序,责任链递归运行

所以,我们直接看 TransactionInterceptor 这个类即可

这里先看一下 TransactionInterceptor 类图
在这里插入图片描述

然后看其源码内容:

这里的 invoke 方法怎么执行到的,我就不多介绍了,看过上篇 AOP 的文章应该都懂,不熟悉的小伙伴可以去看一下

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {
   @Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// 获取我们的代理对象的class属性
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
		/**
		 * 以事务的方式调用目标方法
		 * 在这埋了一个钩子函数 用来回调目标方法的
		 */
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}
}

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation){
	   // 获取我们的事务属性源对象
		TransactionAttributeSource tas = getTransactionAttributeSource();
		// 通过事务属性源对象获取到当前方法的事务属性信息
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		// 获取我们配置的事务管理器对象
		final TransactionManager tm = determineTransactionManager(txAttr);
    
    if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
        // 【重点】创建TransactionInfo
		  TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
        try {
				// 执行被增强方法,调用具体的处理逻辑【我们实际的方法】
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// 异常回滚
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				//清除事务信息,恢复线程私有的老的事务信息
				cleanupTransactionInfo(txInfo);
			}
        //成功后提交,会进行资源储量,连接释放,恢复挂起事务等操作
		  commitTransactionAfterReturning(txInfo);
		return retVal;
    }
}

// 创建连接 + 开启事务
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
    // 获取TransactionStatus事务状态信息
    status = tm.getTransaction(txAttr);
	
    // 根据指定的属性与status准备一个TransactionInfo,
	return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

// 存在异常时回滚事务
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    // 进行回滚
    txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}

// 调用事务管理器的提交方法
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo){
    txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}

然后…没有然后了

事务就是这样简单、朴实无华的实现了

2.4 XML配置

这里稍微讲下 xml 配置的,xml 也一样,在我们进行 xml 文件解析的时候,将 BeanFactoryTransactionAttributeSourceAdvisor 组装起来注册到 BeanDefinitionMap

这里可以参考上篇 AOP 的解析和生成

生成后也一样,调用的是代理方法,判断改方法有没有被代理,然后递归+责任链执行 Advice 即可

没什么困难的

四、流程图

大家有没有感觉事务有点水,就是用了 Spring AOP 的功能包装了一下

根本的实现还是依靠的 JDBC,但有一些不得不记的小知识点:事务的传播属性事务的隔离级别

这两个关于配置的还是要去看一下,我们本篇就不再多叙述了,网上好多资料都有的

我们画一下基本的流程图

在这里插入图片描述

整个流程也相较于简单,有兴趣的可以去更细的看一下

五、总结

记得校招时候,当时对 Spring 懵懂无知,转眼间也被迫看了源码

本文主要从 JDBC 组装事务过度到 Spring 的事务注解,最终通过 AOP 的技术去进行切面处理

通过这篇文章,我相信,99% 的人应该都可以理解了 Spring 事务 的来龙去脉

那么如何证明你真的理解了 Spring 事务呢,我这里出个经典的题目,大家可以想一下:如果让你设计Spring中事务的流程,你会如何设计?

如果你能看到这,那博主必须要给你一个大大的鼓励,谢谢你的支持!

喜欢的可以点个关注,后续会更新 Spring 循环依赖 的源码文章

我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,Java领域新星创作者,喜欢后端架构和中间件源码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/3578.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

YOLOv7训练自己的数据集(手把手教你)

YOLOv7训练自己的数据集整个过程主要包括&#xff1a;环境安装----制作数据集----模型训练----模型测试----模型推理 一&#xff1a;环境安装 conda create -n yolov7 python3.8 conda activate yolov7 #cuda cudnn torch等版本就不细说了&#xff0c;根据自己的显卡配置自行…

水果新鲜程度检测系统(UI界面+YOLOv5+训练数据集)

摘要&#xff1a;水果新鲜程度检测软件用于检测水果新鲜程度&#xff0c;利用深度学习技术识别腐败或损坏的水果&#xff0c;以辅助挑拣出新鲜水果&#xff0c;支持实时在线检测。本文详细介绍水果新鲜程度检测系统&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的实…

给准备面试网络工程师岗位的应届生一些建议

你听完这个故事&#xff0c;应该会有所收获。最近有一个23届毕业的大学生和我聊天&#xff0c;他现在网络工程专业大四&#xff0c;因为今年6、7月份的时候毕业&#xff0c;所以现在面临找工作的问题。不管是现在找一份实习工作&#xff0c;还是毕业后找一份正式工作&#xff0…

100天精通Python丨基础知识篇 —— 03、Python基础知识扫盲(第一个Python程序,13个小知识点)

文章目录&#x1f41c; 1、Python 初体验Pycharm 第一个程序交互式编程第一个程序&#x1f41e; 2、Python 引号&#x1f414; 3、Python 注释&#x1f985; 4、Python 保留字符&#x1f42f; 5、Python 行和缩进&#x1f428; 6、Python 空行&#x1f439; 7、Python 输出&…

Linux基础知识点总结

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…

史上最详细的改良顺序表讲解,看完不会你打我

目录 0.什么是顺序表 1.顺序表里结构体的定义 2.顺序表的初始化 3.顺序表的输入 4.增加顺序表的长度 5.1顺序表的元素查找&#xff08;按位查找&#xff09; 5.2顺序表的元素查找&#xff08;按值查找&#xff09;在顺序表进行按值查找&#xff0c;大概只能通过遍历的方…

HFish蜜罐的介绍和简单测试(三)

在学习蜜罐时&#xff0c;HFish是个不错的选择。首先是免费使用&#xff0c;其次易于安装管理&#xff0c;然后文档支持比较丰富&#xff0c;最后还有更多扩展功能。第三篇的话作为本系列的最终篇章进行总结&#xff0c;具体是看到哪里写到哪里。 0、HFish平台管理 0.1、报告…

基于SpringBoot实现冬奥会运动会科普平台【源码+论文】

基于SpringBoot实现冬奥会科普平台演示开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#…

一文吃透SpringBoot整合mybatis-plus(保姆式教程)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

23.3.26总结

康托展开 是一个全排列与自然数的映射关系&#xff0c;康托展开的实质是计算当前序列在所有从小到大的全排列中的顺序&#xff0c;跟其逆序数有关。 例如&#xff1a;对于 1,2,3,4,5 来说&#xff0c;它的康托展开值为 0*4&#xff01;0*3&#xff01;0*2&#xff01;0*1&…

Android 之 打开相机 打开相册

Android 之 打开系统摄像头拍照 打开系统相册&#xff0c;并展示1&#xff0c;清单文件 AndroidManifest.xml<uses-permission android:name"android.permission.INTERNET" /><!--文件读取权限--><uses-permission android:name"android.permiss…

网络编程2(套接字编程)

套接字编程UDP协议通信&#xff1a;TCP通信&#xff1a;套接字编程&#xff1a;如何编写一个网络通信程序 1.网络通信的数据中都会包含一个完整的五元组&#xff1a; sip&#xff0c;sport&#xff0c;dip&#xff0c;dport&#xff0c;protocol&#xff08;源IP&#xff0c;源…

【linux】多线程控制详述

文章目录一、进程控制1.1 POSIX线程库1.2 创建线程pthread_create1.2.1 创建一批线程1.3 终止线程pthread_exit1.4 线程等待pthread_jion1.4.1 线程的返回值&#xff08;退出码&#xff09;1.5 取消线程pthread_cancel1.6 C多线程1.7 分离线程pthread_detach二、线程ID值三、线…

C/C++内存管理

内存管理在C中无处不在&#xff0c;内存泄漏几乎在每个C程序中都会发生。因此&#xff0c;要学好C&#xff0c;内存管理这一关势在必得&#xff01; 目录 1.C/C内存分布 2.C语言中动态内存管理方式 3.C内存管理方式 3.1.new和delete操作内置类型 3.2.new和delete操作自定义类型…

SQL注入之HTTP请求头注入

Ps&#xff1a; 先做实验&#xff0c;在有操作的基础上理解原理会更清晰更深入。 一、实验 sqli-lab 1. User-Agent注入 特点&#xff1a;登陆后返回用户的 User-Agent --> 服务器端可能记录用户User-Agent 输入不合法数据报错 payload: and updatexml(1,concat("~&…

异或相关算法

文章目录1. 异或的性质2. 题目一3. 题目二4. 题目三5. 题目四1. 异或的性质 我们知道&#xff0c;异或的定义是&#xff1a;相同为0&#xff0c;相异为1。所以也被称为无进位相加&#xff0c;根据这定义&#xff0c;我们可以得出三个性质&#xff1a; 1. N ^ N0。2. N ^ 0N。3…

13-C++面向对象(纯虚函数(抽象类)、多继承、多继承-虚函数、菱形继承、虚继承、静态成员)

虚析构函数 存在父类指针指向子类对象的情况&#xff0c;应该将析构函数声明为虚函数&#xff08;虚析构函数&#xff09; 纯虚函数 纯虚函数&#xff1a;没有函数体且初始化为0的虚函数&#xff0c;用来定义接口规范 抽象类&#xff1a; 含有纯虚函数的类&#xff0c;不可以实…

Prometheus监控实战系列十七:探针监控

目前对于应用程序的监控主要有两种方式&#xff0c;一种被称为白盒监控&#xff0c;它通过获取目标的内部信息指标&#xff0c;来监控目标的状态情况&#xff0c;我们前面介绍的主机监控、容器监控都属于此类监控。另一种则是“黑盒监控”&#xff0c;它指在程序外部通过探针的…

【Linux】Linux下权限的理解

前言&#xff1a;在之前我们已经对基本的指令进行了深入的学习&#xff0c;接下来我将带领大家学习的是关于权限的相关问题。在之前&#xff0c;我们一直是使用的【root】用户&#xff0c;即为“超级用户”&#xff0c;通过对权限的学习之后&#xff0c;我们就会慢慢的切换到普…

【数据结构】双向链表实现

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 一、什么是双向链表 二、双向链表的实现 一、什么是双向链表 双向链表也叫双链表&#xff0c;是链表的一种&#xff0c;它的每个数据节点中都有两个指针&#xff0c;分别指向直接后…