事务开启过程
当一个请求内进行了多个dao层操作,且使用了Transactional事务,除了第一次会通过AbstractRoutingDataSource的getConnection方法切换数据源。后续CRUD操作,会包含在上一个事务中。不会进行数据源切换。
通过跟踪事务拦截链为:
类 | 方法 |
---|---|
TransactionAspectSupport | createTransactionIfNecessary |
AbstractPlatformTransactionManager | getTransaction |
JpaTransactionManager | doGetTransaction |
JpaTransactionManager | doBegin |
HibernateJpaDialect | beginTransaction |
LogicalConnectionManagedImpl | acquireConnectionIfNeeded |
DatasourceConnectionProviderImpl | getConnection |
问题原因在于以下三个方法
类 | 方法 |
---|---|
AbstractPlatformTransactionManager | getTransaction |
JpaTransactionManager | doGetTransaction |
LogicalConnectionManagedImpl | acquireConnectionIfNeeded |
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
Object transaction = this.doGetTransaction();
boolean debugEnabled = this.logger.isDebugEnabled();
if (definition == null) {
definition = new DefaultTransactionDefinition();
}
if (this.isExistingTransaction(transaction)) {
return this.handleExistingTransaction((TransactionDefinition)definition, transaction, debugEnabled);
} else if (((TransactionDefinition)definition).getTimeout() < -1) {
throw new InvalidTimeoutException("Invalid transaction timeout", ((TransactionDefinition)definition).getTimeout());
} else if (((TransactionDefinition)definition).getPropagationBehavior() == 2) {
throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
} else if (((TransactionDefinition)definition).getPropagationBehavior() != 0 && ((TransactionDefinition)definition).getPropagationBehavior() != 3 && ((TransactionDefinition)definition).getPropagationBehavior() != 6) {
if (((TransactionDefinition)definition).getIsolationLevel() != -1 && this.logger.isWarnEnabled()) {
this.logger.warn("Custom isolation level specified but no actual transaction initiated; isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = this.getTransactionSynchronization() == 0;
return this.prepareTransactionStatus((TransactionDefinition)definition, (Object)null, true, newSynchronization, debugEnabled, (Object)null);
} else {
AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
if (debugEnabled) {
this.logger.debug("Creating new transaction with name [" + ((TransactionDefinition)definition).getName() + "]: " + definition);
}
try {
boolean newSynchronization = this.getTransactionSynchronization() != 2;
DefaultTransactionStatus status = this.newTransactionStatus((TransactionDefinition)definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
this.doBegin(transaction, (TransactionDefinition)definition);
this.prepareSynchronization(status, (TransactionDefinition)definition);
return status;
} catch (Error | RuntimeException var7) {
this.resume((Object)null, suspendedResources);
throw var7;
}
}
}
当第一次执行crud时会走最后一个分支,通过doBegin方法的beginTransaction切换数据源,开启新的事务
Object transactionData = this.getJpaDialect().beginTransaction(em, new JpaTransactionManager.JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder()));
而当后续crud操作时,会走第一个分支,将后续操作包含在上一个事务中
if (this.isExistingTransaction(transaction)) {
return this.handleExistingTransaction((TransactionDefinition)definition, transaction, debugEnabled);
}
这是因为在判断前执行的JpaTransactionManager.doGetTransaction方法内设置了EntityManagerHolder
protected Object doGetTransaction() {
JpaTransactionManager.JpaTransactionObject txObject = new JpaTransactionManager.JpaTransactionObject();
txObject.setSavepointAllowed(this.isNestedTransactionAllowed());
EntityManagerHolder emHolder = (EntityManagerHolder)TransactionSynchronizationManager.getResource(this.obtainEntityManagerFactory());
if (emHolder != null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Found thread-bound EntityManager [" + emHolder.getEntityManager() + "] for JPA transaction");
}
txObject.setEntityManagerHolder(emHolder, false);
}
if (this.getDataSource() != null) {
ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.getDataSource());
txObject.setConnectionHolder(conHolder);
}
return txObject;
}
是否重新获取数据库链接是在LogicalConnectionManagedImpl类的acquireConnectionIfNeeded方法中
private Connection acquireConnectionIfNeeded() {
if (this.physicalConnection == null) {
this.observer.jdbcConnectionAcquisitionStart();
try {
this.physicalConnection = this.jdbcConnectionAccess.obtainConnection();
} catch (SQLException var5) {
throw this.sqlExceptionHelper.convert(var5, "Unable to acquire JDBC Connection");
} finally {
this.observer.jdbcConnectionAcquisitionEnd(this.physicalConnection);
}
}
return this.physicalConnection;
}
如果physicalConnection 为null,则重新链接数据源
总结
事务的开启过程:
如果方法上加了事务注解,spring会拦截注解、预先连接数据库,切换数据源,开启事务。后续CRUD操作则不会再切换数据源。会将后续CRUD操作包含在同一个数据源的整个事务中。
失效原因
我的情况是租户库CRUD操作用的时jdbcTemplate,会员库用的jpa。租户库的CRUD方法上加个切换数据源注解,方法执行前加ThreadLocal标识,方法执行后清除ThreadLocal标识。失效的原因是方法执行之前,预先开启事务时,ThreadLocal标识还未设定,则事务所使用的数据源为会员库,后续操作切换租户库失败。
解决办法
既然@Transactional也为注解,则可以调整注解执行的优先级。使得自定义注解优于@Transactional。自定义注解implements Ordered,调整优先级为最大
@Aspect
@Component
@Slf4j
public class DynamicDataSourceAspect implements Ordered {
/**
* 配置切入点
*/ @Pointcut("@annotation(com.mammoth.design.config.annotation.DataSource)")
public void dataSourcePointcut() {
// 该方法无方法体,主要为了让同类中其他方法使用此切入点
}
//在注解方法执行前先切换数据源
@Before("dataSourcePointcut()")
public void changeDataSource(JoinPoint point){
String formKey = ContextHolder.getContext().getFormKey();
FormOriginService formOriginService= SpringContextHolder.getBean(FormOriginService.class);
FormOriginDto formOriginDto = formOriginService.findByFormKey(formKey);
ContextHolder.getContext().setAppId(formOriginDto.getApp().getId());
}
//方法执行后删除键值对
@After("dataSourcePointcut()")
public void restoreDataSource(JoinPoint point){
ContextHolder.clearContext();
}
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
}