跟着小马哥学系列之 Spring AOP(Spring 事务(源码分析)下)
- 简介
- 事务对象
- SmartTransactionObject
- JdbcTransactionObjectSupport
- DataSourceTransactionObject
- ResourceTransactionManager
- DataSourceTransactionManager
- doGetTransaction
- isExistingTransaction
- doBegin
- doSuspend
- doResume
- doCommit
- doRollback
- doSetRollbackOnly
- doCleanupAfterCompletion
- 使用 xml 配置事务
- TxNamespaceHandler
- AnnotationDrivenBeanDefinitionParser
- TxAdviceBeanDefinitionParser
- 最佳实践
事务对象
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;
}
}