跟着小马哥学系列之 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 conHolder =
( ConnectionHolder ) TransactionSynchronizationManager . getResource ( obtainDataSource ( ) ) ;
txObject. setConnectionHolder ( conHolder, false ) ;
return txObject;
}
isExistingTransaction
@Override
protected boolean isExistingTransaction ( Object transaction) {
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 {
if ( ! txObject. hasConnectionHolder ( ) ||
txObject. getConnectionHolder ( ) . isSynchronizedWithTransaction ( ) ) {
Connection newCon = obtainDataSource ( ) . getConnection ( ) ;
if ( logger. isDebugEnabled ( ) ) {
logger. debug ( "Acquired Connection [" + newCon + "] for JDBC transaction" ) ;
}
txObject. setConnectionHolder ( new ConnectionHolder ( newCon) , true ) ;
}
txObject. getConnectionHolder ( ) . setSynchronizedWithTransaction ( true ) ;
con = txObject. getConnectionHolder ( ) . getConnection ( ) ;
Integer previousIsolationLevel = DataSourceUtils . prepareConnectionForTransaction ( con, definition) ;
txObject. setPreviousIsolationLevel ( previousIsolationLevel) ;
txObject. setReadOnly ( definition. isReadOnly ( ) ) ;
if ( con. getAutoCommit ( ) ) {
txObject. setMustRestoreAutoCommit ( true ) ;
if ( logger. isDebugEnabled ( ) ) {
logger. debug ( "Switching JDBC Connection [" + con + "] to manual commit" ) ;
}
con. setAutoCommit ( false ) ;
}
prepareTransactionalConnection ( con, definition) ;
txObject. getConnectionHolder ( ) . setTransactionActive ( true ) ;
int timeout = determineTimeout ( definition) ;
if ( timeout != TransactionDefinition . TIMEOUT_DEFAULT) {
txObject. getConnectionHolder ( ) . setTimeoutInSeconds ( timeout) ;
}
if ( txObject. isNewConnectionHolder ( ) ) {
TransactionSynchronizationManager . bindResource ( obtainDataSource ( ) , txObject. getConnectionHolder ( ) ) ;
}
}
catch ( Throwable ex) {
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;
txObject. setConnectionHolder ( null ) ;
return TransactionSynchronizationManager . unbindResource ( obtainDataSource ( ) ) ;
}
doResume
@Override
protected void doResume ( @Nullable Object transaction, Object suspendedResources) {
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;
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) ;
}
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>
< 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 ( ) {
registerBeanDefinitionParser ( "advice" , new TxAdviceBeanDefinitionParser ( ) ) ;
registerBeanDefinitionParser ( "annotation-driven" , new AnnotationDrivenBeanDefinitionParser ( ) ) ;
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;
}
}