跟着小马哥学系列之 Spring AOP(Spring 事务(源码分析)下)

学好路更宽,钱多少加班。 ——小马哥

简介

大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间《小马哥讲Spring AOP 编程思想》基础上形成的个人一些总结。希望能帮助各位小伙伴, 祝小伙伴早日学有所成。

事务对象

SmartTransactionObject

该接口能够返回内部的 rollback-only 标记,通常通过另外一个已参与并将其标记为 rollback-only 的事务。
由 DefaultTransactionStatus 自动检测,总是返回当前的 rollbackOnly 标志,即使不是由当前的 TransactionStatus 产生的。

方法描述
isRollbackOnly()返回事务是否在内部标记为回滚。例如,可以检查 JTA UserTransaction
flush()将底层会话刷新到数据存储。例如,所有受影响的 Hibernate/JPA 会话。

JdbcTransactionObjectSupport

在这里插入图片描述

字段描述
ConnectionHolder connectionHolder包装JDBC连接的资源持有者,DataSourceTransactionManager 将这个类的实例绑定到特定数据源的线程
Integer previousIsolationLevel前事务隔离级别
boolean readOnly只读事务标记,默认是 false
boolean savepointAllowed是否允许保存点标志,默认是 false
方法描述
set/get/hasConnectionHolder()设置/获取/是否有 当前事务对象的 ConnectionHolder
getPreviousIsolationLevel()返回先前保留的隔离级别(如果有的话)
is/setReadOnly()是否是/设置 只读事务
is/setSavepointAllowed()是否是/ 设置 允许保存点
SavepointManager 方法委派给 ConnectionHolder,然后再通过 ConnectionHolder 委派给关联的 Connection

DataSourceTransactionObject

在这里插入图片描述

数据源事务对象,表示 ConnectionHolder。DataSourceTransactionManager 用作事务对象

字段描述
boolean newConnectionHolder是否是新 ConnectionHolder 标识
boolean mustRestoreAutoCommit是否必须恢复自动提交标识
方法描述
setConnectionHolder(ConectionHolder, boolean)设置 ConnectionHolder 和 newConnectionHolder 字段值
isNewConnectionHolder()返回是否是 ConnectionHolder 标识
is/setMustRestoreAutoCommit(()是否是/设置 必须恢复自动提交标识
set/isRollbackOnly()设置/是否是 回滚标志
flush()通过 TransactionSynchronizationUtils.triggerFlush()

ResourceTransactionManager

在这里插入图片描述

  • PlatformTransactionManager 接口的扩展,指示本地资源事务管理器,在单个目标资源上操作。此类事务管理器与JTA事务管理器的不同之处在于,它们不为开放数量的资源使用XA事务登记,而是专注于利用单个目标资源的本地功能和简单性
  • 该接口主要用于事务管理器的抽象自省,给客户端一个提示,告诉他们所得到的是哪种事务管理器,以及事务管理器正在对什么具体资源进行操作。
方法描述
getResourceFactory()返回该事务管理器操作的资源工厂,例如 JDBC 数据源或 JMS ConnectionFactory。这个目标资源工厂通常用作 TransactionSynchronizationManager 每个线程的资源绑定的资源键

DataSourceTransactionManager

在这里插入图片描述

  • 用于单个 JDBC 数据源的 PlatformTransactionManager 实现。这个类可以在任何环境中使用任何 JDBC 驱动程序,只要安装程序使用 javax.sql.DataSource 作为其连接工厂机制。将来自指定数据源的JDBC连接绑定到当前线程,可能允许每个数据源有一个线程来绑定连接。
  • 注意:此事务管理器操作的数据源需要返回独立的连接。连接可能来自一个池(典型情况),但是 DataSource 不能返回线程范围/请求范围的连接或类似的东西。该事务管理器将根据指定的传播行为将 Connections 与线程绑定的事务本身关联起来。它假设即使在一个正在进行的事务期间也可以获得一个独立的 Connection。
  • 应用程序代码需要通过 Datasourceutil.getconnection(DataSource) 来检索 JDBC 连接,而不是标准的 Java EE 风格的 DataSource.getconnection() 调用。像 JdbcTemplate 这样的 Spring 类隐式地使用了这个策略。如果没有与此事务管理器结合使用,DataSourceUtils 查找策略的行为与本机 DataSource 查找完全相同;’因此,它可以以便携式的方式使用。
  • 或者,您可以允许应用程序代码使用标准 Java EE 风格的查找模式 DataSource.getConnection(),例如,对于根本不知道 Spring 的遗留代码。在这种情况下,为目标数据源定义一个 TransactionAwareDataSourceProxy,并将该代理数据源传递给 DAO,当访问它时,DAO 将自动参与 Spring 管理的事务。
  • 支持自定义隔离级别和超时,这些超时将作为适当的JDBC语句超时应用。为了支持后者,应用程序代码必须使用 JdbcTemplate,调用 DataSourceUtils.applyTransactionTimeout(Statement, DataSource) 对于每个创建的 JDBC Statement,或者通过 TransactionAwareDataSourceProxy 自动创建超时的 JDBC Connection 和 Statement。
  • 考虑为目标数据源定义一个 LazyConnectionDataSourceProxy,将此事务管理器和你的 DAO 指向它。这将导致优化“空”事务的处理,即不执行任何 JDBC 语句的事务。LazyConnectionDataSourceProxy 不会从目标数据源获取实际的 JDBC 连接,直到执行 Statement,将指定的事务设置惰性地应用到目标连接。
  • 该事务管理器通过 JDBC 3.0 保存点机制支持嵌套事务。nestedTransactionAllowed 标志默认为 true,因为嵌套事务将在支持保存点的 JDBC 驱动程序(如Oracle JDBC驱动程序)上不受限制地工作。
  • 这个事务管理器可以用来作为替代 org.springframework.transaction.jta.JtaTransactionManager 在单一资源的情况下,它不需要来容器支持 JTA,通常结合本地定义的 JDBC 数据源(例如Apache Commons DBCP连接池)。在本地策略和 JTA 环境之间切换只是一个配置问题!
  • 从 4.3.4 开始,假设资源在底层 JDBC Connection 上操作,该事务管理器会在注册的事务同步上触发刷新回调(如果同步通常是活动的)。这允许类似于 JtaTransactionManager 的设置,特别是关于惰性注册的 ORM 资源(例如 Hibernate 会话)。
字段描述
DataSource dataSource数据源
boolean enforceReadOnly是否强制只读
方法描述
get/obtainDataSource()获取数据源,只不过 obtainDataSource() 要求数据源不能为空
setDataSource(DataSource)设置数据源
isEnforeReadOnly()返回是否通过事务连接上的显式语句强制事务的只读性质
setEnforeReadOnly(boolean)指定是否强制事务的只读性质(如 TransactionDefinition.isReadOnly() 通过事务连接上的显式语句:“SET transaction READ ONLY”,这是 Oracle, MySQL和Postgres 所支持的。确切的处理方法,包括在连接上执行的任何 SQL 语句,都可以通过 prepareTransactionalConnection(Connection, TransactionDefinition)。这种只读处理模式超出了 Connection.setReadOnly(boolean) 提示的默认情况下应用 Spring 的范围。与标准的 JDBC 提示相反,“SET TRANSACTION READ ONLY” 强制一种类似隔离级别的连接模式,其中严格禁止数据操作语句。此外,在 Oracle 上,这种只读模式为整个事务提供了读一致性

doGetTransaction

@Override
protected Object doGetTransaction() {
	// 创建事务对象
	DataSourceTransactionObject txObject = new DataSourceTransactionObject();
	// 设置是否允许保存点
	txObject.setSavepointAllowed(isNestedTransactionAllowed());
	// 通过事务同步管理器获取绑定当前线程的 ConnectionHolder 对象
	ConnectionHolder conHolder =
			(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
	// 把获取的 ConnectionHolder 对象与事务对象关联起来并设置 newConnectionHolder 标识为 false
	txObject.setConnectionHolder(conHolder, false);
	return txObject;
}

isExistingTransaction

@Override
protected boolean isExistingTransaction(Object transaction) {
	// 事务存在的条件就是:事务对象里面有关联的 ConnectionHolder 对象并且该关联的对象事务是激活的
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}

doBegin

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	Connection con = null;

	try {
		// 事务对象没有关联 ConnectionHolder 或者关联的 ConnectionHolder 的资源与事务同步属性是 true
		if (!txObject.hasConnectionHolder() ||
				txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
			Connection newCon = obtainDataSource().getConnection();
			if (logger.isDebugEnabled()) {
				logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
			}
			// 事务对象关联 ConnectionHolder 对象,并表示是一个新持有的连接
			txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
		}
		// 设置事务对象关联的 ConnectionHolder 资源与事务同步属性为 true
		txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
		// 获取连接
		con = txObject.getConnectionHolder().getConnection();
		// 如果 TransactionDefinition 不为空并且是只读事务,则设置当前 Connection 为只读
		// 如果当前的 Connection 的隔离级别与 TransactionDefinition 对象里面的定义的隔离级别不一致(排除默认)
		// 设置当前的 Connection 对象与 TransactinDefinition 保持一致,返回 Connection 对象的隔离级别
		Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
		txObject.setPreviousIsolationLevel(previousIsolationLevel);
		txObject.setReadOnly(definition.isReadOnly());

		// 必要时切换到手动提交。在某些 JDBC 驱动程序中,这是非常昂贵的,
		// 所以我们不想不必要地这样做(例如,如果我们已经显式地配置了连接池来设置它)。
		if (con.getAutoCommit()) {
			txObject.setMustRestoreAutoCommit(true);
			if (logger.isDebugEnabled()) {
				logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
			}
			con.setAutoCommit(false);
		}
		// 事务开始后立即准备事务连接。如果 enforceReadOnly 标志设置为 true,
		// 并且事务定义为只读事务,则默认执行 SET TRANSACTION READ ONLY  语句。
		prepareTransactionalConnection(con, definition);
		// 激活事务
		txObject.getConnectionHolder().setTransactionActive(true);
		// 如果不使用默认超时时间则设置超时时间
		int timeout = determineTimeout(definition);
		if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
			txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
		}

		// 将 ConnectionHolder 绑定到线程上。
		if (txObject.isNewConnectionHolder()) {
			TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
		}
	}

	catch (Throwable ex) {
		// 如果发生异常,事务又是新 ConnectionHolder,则释放连接,和 ConnectionHolder
		if (txObject.isNewConnectionHolder()) {
			DataSourceUtils.releaseConnection(con, obtainDataSource());
			txObject.setConnectionHolder(null, false);
		}
		throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
	}
}

doSuspend

@Override
protected Object doSuspend(Object transaction) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	// 把当前事务对象关联的 ConnectionHolder 置空
	txObject.setConnectionHolder(null);
	// 把 TransactionSynchronizationManager 绑定的资源解绑
	return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}

doResume

@Override
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
	// 把挂起的资源重新绑定到 TransactionSynchronizationManager
	TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources);
}

doCommit


@Override
protected void doCommit(DefaultTransactionStatus status) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
	// 获取连接
	Connection con = txObject.getConnectionHolder().getConnection();
	if (status.isDebug()) {
		logger.debug("Committing JDBC transaction on Connection [" + con + "]");
	}
	try {
		// 使用连接来提交事务
		con.commit();
	}
	catch (SQLException ex) {
		throw new TransactionSystemException("Could not commit JDBC transaction", ex);
	}
}

doRollback

@Override
protected void doRollback(DefaultTransactionStatus status) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
	// 获取连接
	Connection con = txObject.getConnectionHolder().getConnection();
	if (status.isDebug()) {
		logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
	}
	try {
		// 使用连接来执行回滚
		con.rollback();
	}
	catch (SQLException ex) {
		throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
	}
}

doSetRollbackOnly

@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
	if (status.isDebug()) {
		logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
				"] rollback-only");
	}
	// 设置事务对象回滚标志
	txObject.setRollbackOnly();
}

doCleanupAfterCompletion

@Override
protected void doCleanupAfterCompletion(Object transaction) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

	// 从当前线程上解绑 ConnectionHolder,如果暴露。
	if (txObject.isNewConnectionHolder()) {
		TransactionSynchronizationManager.unbindResource(obtainDataSource());
	}

	// 重置连接
	Connection con = txObject.getConnectionHolder().getConnection();
	try {
		if (txObject.isMustRestoreAutoCommit()) {
			con.setAutoCommit(true);
		}
		DataSourceUtils.resetConnectionAfterTransaction(
				con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
	}
	catch (Throwable ex) {
		logger.debug("Could not reset JDBC Connection after transaction", ex);
	}
	// 如果是新 ConnectionHolder 则释放连接
	if (txObject.isNewConnectionHolder()) {
		if (logger.isDebugEnabled()) {
			logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
		}
		DataSourceUtils.releaseConnection(con, this.dataSource);
	}
	// 清除此资源持有者的事务状态
	txObject.getConnectionHolder().clear();
}


使用 xml 配置事务

	<!-- 连接池 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
		<property name="driverClassName" value="${driverClassName}" />
		<property name="url" value="${url}" />
		<property name="username" value="${username}" />
		<property name="password" value="${password}" />
	</bean>


	<!-- DataSourceTransactionManager -->
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="get*" read-only="true" />
			<tx:method name="list*" read-only="true" />
			<tx:method name="query*" read-only="true" />
			<tx:method name="*" />
		</tx:attributes>
	</tx:advice>

	<aop:config>
		<aop:pointcut id="pc" expression="" />
		<aop:advisor advice-ref="txAdvice" pointcut-ref="pc" />
	</aop:config>

TxNamespaceHandler

把标签解析完之后注册成对应的 RootBeanDefinition

@Override
public void init() {
	// advice 标签解析
	registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
	// annotation-driven 标签解析
	registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
	// jta-transaction-manager 标签解析
	registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
}

AnnotationDrivenBeanDefinitionParser

功能与 TransactionManagementConfigurationSelector#selectImports 导入 AutoProxyRegistrar 一样

TxAdviceBeanDefinitionParser

功能与 TransactionManagementConfigurationSelector#selectImports 导入 ProxyTransactionManagementConfiguration 中 transactionInterceptor() + transactionAttributeSource() 方法类似。细节就是不能存在大于 1 个 attributes 标签。只不过 TransactionAttributeSource 关联的是 NameMatchTransactionAttributeSource

最佳实践


@Configuration
@ConditionalOnProperty(prefix = "transaction.aop", value = "enable", havingValue = "true", matchIfMissing = true)
public class TransactionConfiguration implements ApplicationContextAware {
    private ApplicationContext context;

    @Bean("txSource")
    public TransactionAttributeSource transactionAttributeSource() {
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true);
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
        RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED,
                Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        Integer timeout = context.getEnvironment().getProperty("transaction.aop.timeout", Integer.class, -1);
        if (timeout != -1) {
            requiredTx.setTimeout(timeout);
        }
        Map<String, TransactionAttribute> txMap           = new HashMap<>(10, 1);
        HashSet                           readonlyMethods = context.getEnvironment().getProperty("transaction.aop.readonly", HashSet.class);
        HashSet                           requiredMethods = context.getEnvironment().getProperty("transaction.aop.required", HashSet.class);
        if (readonlyMethods != null && readonlyMethods.size() > 0) {
            for (Object o : readonlyMethods) {
                txMap.put(o.toString(), readOnlyTx);
            }
        } else {
            txMap.put("get*", readOnlyTx);
            txMap.put("query*", readOnlyTx);
            txMap.put("select*", readOnlyTx);
            txMap.put("list*", readOnlyTx);
        }
        if (requiredMethods != null && requiredMethods.size() > 0) {
            for (Object o : requiredMethods) {
                txMap.put(o.toString(), requiredTx);
            }
        } else {
            txMap.put("add*", requiredTx);
            txMap.put("save*", requiredTx);
            txMap.put("insert*", requiredTx);
            txMap.put("update*", requiredTx);
            txMap.put("delete*", requiredTx);
            txMap.put("remove*", requiredTx);
            txMap.put("batch*", requiredTx);
        }

        source.setNameMap(txMap);

        return new CompositeTransactionAttributeSource(new AnnotationTransactionAttributeSource(), source);
    }

    
    @Bean
    public AspectJExpressionPointcutAdvisor pointcutAdvisor(TransactionInterceptor txInterceptor) {
        AspectJExpressionPointcutAdvisor pointcutAdvisor = new AspectJExpressionPointcutAdvisor();
        pointcutAdvisor.setAdvice(txInterceptor);
        pointcutAdvisor.setExpression("execution (* xx..service.*.*(..)) || @annotation(org.springframework.transaction.annotation.Transactional)");
        return pointcutAdvisor;
    }

   
    @Bean
    @Primary
    public TransactionInterceptor getTransactionInterceptor(PlatformTransactionManager tx) {
        return new TransactionInterceptor(tx, transactionAttributeSource()) {
            private final Logger log = LoggerFactory.getLogger(TransactionInterceptor.class);

            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                try {
                    return super.invoke(invocation);
                } catch (Throwable throwable) {
                    if (throwable instanceof BusinessException) {
                        log.error("事务回滚! {} {} {}", throwable.getStackTrace()[0], ((BusinessException) throwable).getCode(), throwable.getMessage());
                    } else {
                        log.error("事务回滚!", throwable);
                    }
                    if (TransactionAspectSupport.currentTransactionInfo() != null
                            && TransactionAspectSupport.currentTransactionInfo().hasTransaction()) {
                        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                    }
                    if (Result.class.isAssignableFrom(invocation.getMethod().getReturnType())) {
                        if (throwable instanceof BusinessException) {
                            BusinessException e = (BusinessException) throwable;
                            return Result.failure(e.getCode(), e.getMessage());
                        }
                        return Result.failure(500, throwable.getMessage());
                    } else {
                        throw throwable;
                    }
                }
            }
        };
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿大叔文海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值