最近任务比较少,也没有找到很好的写作题材。突然想起,还有一个承诺没有兑现。今天就来兑现一下对掘友
的承诺吧。同时我们再来巩固一下事务传播行为的知识吧。
掘友:编程式事务属性存在并发问题 ?
先得看看一个掘友 5月前提的一个问题,也算是兑现了我的承诺
说一下结论吧,经过自己的测试和看源码,确实存在 掘友说的问题。传播范围确实存在现在安全问题
官方解释 :如果需要使用单例的不同配置,就需要单独创建 transcationTemplate
对象
解决方案
方案 1:为每个线程创建独立的 TransactionTemplate 实例
-
实现方式 :
在 Spring 中,可以通过
prototype
作用域的 Bean 或编程方式为每个线程创建独立的实例:java// 在配置类中定义原型 Bean @Bean @Scope("prototype") public TransactionTemplate transactionTemplate(PlatformTransactionManager txManager) { TransactionTemplate template = new TransactionTemplate(txManager); // 设置默认配置(可选) template.setPropagationBehavior(Propagation.REQUIRED.value()); return template; } // 在线程中使用时获取新实例 TransactionTemplate threadLocalTemplate = context.getBean(TransactionTemplate.class); threadLocalTemplate.setIsolationLevel(Isolation.READ_COMMITTED.value()); threadLocalTemplate.execute(...);
-
优点:完全隔离线程间的配置修改,避免竞争。
-
缺点:频繁创建实例可能增加内存开销(通常可忽略)。
方案 2:使用 ThreadLocal 绑定配置
-
实现方式 : 将
TransactionTemplate
的隔离级别与当前线程绑定:javaprivate static final ThreadLocal<TransactionTemplate> threadLocalTemplate = ThreadLocal.withInitial(() -> { TransactionTemplate template = new TransactionTemplate(txManager); template.setIsolationLevel(Isolation.DEFAULT.value()); return template; }); // 在线程中设置隔离级别 threadLocalTemplate.get().setIsolationLevel(Isolation.READ_COMMITTED.value()); threadLocalTemplate.get().execute(...);
-
优点:配置隔离彻底,无竞争风险。
-
缺点 :需要手动管理
ThreadLocal
的清理,避免内存泄漏。
方案 3:配置后不再修改
-
实现方式 :在应用启动时初始化多个
TransactionTemplate
实例,每个实例预配置不同的隔离级别或传播行为,使用时按需选择:java@Bean(name = "readCommittedTemplate") public TransactionTemplate readCommittedTemplate(PlatformTransactionManager txManager) { TransactionTemplate template = new TransactionTemplate(txManager); template.setIsolationLevel(Isolation.READ_COMMITTED.value()); return template; } @Bean(name = "repeatableReadTemplate") public TransactionTemplate repeatableReadTemplate(PlatformTransactionManager txManager) { TransactionTemplate template = new TransactionTemplate(txManager); template.setIsolationLevel(Isolation.REPEATABLE_READ.value()); return template; }
-
优点:无运行时修改,线程安全。
-
缺点:需要预定义所有可能的事务配置组合。
传播机制基础知识
传播机制
Spring的事务规定了7种事务的传播级别,默认的传播机制是REQUIRED
- REQUIRED,如果不存在事务则开启一个事务,如果存在事务则加入之前的事务,总是只有一个事务在执行
- REQUIRES_NEW,每次执行新开一个事务,如果当前存在事务,则把当前事务挂起
- SUPPORTS,有事务则加入事务,没有事务则普通执行
- NOT_SUPPORTED,有事务则暂停该事务,没有则普通执行
- MANDATORY,强制有事务,没有事务则报异常
- NEVER,有事务则报异常
- NESTED,如果之前有事务,则创建嵌套事务,嵌套事务回滚不影响父事务,反之父事务影响嵌套事务
枚举代码在
org.springframework.transaction.annotation.propagation.class 中
常见开启事务的方式,就是通过注解@Transaction
和 编程式事务 TransactionTemplate
,这两个实现方式设置事务的隔离级别的方式当然也不一样了。代码如下:
注解开启事务
java
//开启事务 设置隔离级别:REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void testTransaction(){
deleteOne();
updateTwo();
}
编程式事务
scss
@Resource
private TransactionTemplate transactionTemplate;
@Override
public void testTransaction(){
//开启事务 设置隔离级别:REQUIRES_NEVER
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
transactionTemplate.execute((TransactionCallback<Void>) status -> {
try {
deleteOne();
updateTwo();
return null;
} catch (Exception e) {
log.error("---------error---编程式事务隔离级别:{}",e);
status.setRollbackOnly();
throw e;
}
});
}
注意
❗ 注解式事务,隔离级别是设置到方法的注解上的,是线程安全的。
❗ 但是编程式事务的隔离级别是set
到transactionTemplate
的属性上的,如果我们项目共用一个transactionTemplate
岂不是又线程安全问题。
❗ 经过我测试确实是存在线程安全问题
java
private int propagationBehavior = PROPAGATION_REQUIRED;
//set的时候也是直接 set 到属性上,也没有任何线程安全的控制。
public final void setPropagationBehavior(int propagationBehavior) {
if (!constants.getValues(PREFIX_PROPAGATION).contains(propagationBehavior)) {
throw new IllegalArgumentException("Only values of propagation constants allowed");
}
this.propagationBehavior = propagationBehavior;
}
只要
transactionTemplate
能set
的属性,我们都要保证不影响其他线程,如要修改,请重新创建实例。
事务传播机制处理核心代码
Spring的事务传播行为由Propagation
枚举定义,其核心实现位于AbstractPlatformTransactionManager
及其子类(如DataSourceTransactionManager
)。关键逻辑在获取事务 、事务管理器创建事务时,根据当前是否存在事务决定行为:
获取事务
核心代码AbstractPlatformTransactionManager.java 中的 getTransaction()
方法:
java
//AbstractPlatformTransactionManager.java
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 1. 使用默认配,当事务定时为空时
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
// 2. 获取当前事务
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
// 3.存在现有事务 执行handleExistingTransaction 方法
if (isExistingTransaction(transaction)) {
// 存在现有事务,检查事务的传播行为
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}
// 4.1 不存在事务的处理逻辑
// 检车事务是否超时
// Check definition settings for new transaction.
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
// 4.2 事务隔离级别为:MANDATORY,强制有事务,没有事务则报异常
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
// 4.3 REQUIRED、REQUIRES_NEW、NESTED 处理逻辑
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
// 4.4 ISOLATION_DEFAULT 处理逻辑
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
事务已经存在
上面一节说了存在现有事务就执行 handleExistingTransaction
,下面看看handlExistingTransaction
方法吧
java
// 处理有事务的逻辑:
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
//1. NEVER 直接抛异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
// 2.NOT_SUPPORTED 处理逻辑
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
if (debugEnabled) {
logger.debug("Suspending current transaction");
}
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
// REQUIRES_NEW 调用 suspend 然后 调用 startTransaction 开启事务
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
if (debugEnabled) {
logger.debug("Suspending current transaction, creating new transaction with name [" +
definition.getName() + "]");
}
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
// NESTED 处理逻辑
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
}
if (useSavepointForNestedTransaction()) {
// Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
}
else {
// Nested transaction through nested begin and commit/rollback calls.
// Usually only for JTA: Spring synchronization might get activated here
// in case of a pre-existing JTA transaction.
return startTransaction(definition, transaction, debugEnabled, null);
}
}
// 有事务的情况SUPPORTS、REQUIRED 处理方式相同
// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
if (debugEnabled) {
logger.debug("Participating in existing transaction");
}
if (isValidateExistingTransaction()) {
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
Constants isoConstants = DefaultTransactionDefinition.constants;
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] specifies isolation level which is incompatible with existing transaction: " +
(currentIsolationLevel != null ?
isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
"(unknown)"));
}
}
if (!definition.isReadOnly()) {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] is not marked as read-only but existing transaction is");
}
}
}
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
这里就不继续深入分析了。
总结
简单分析了一下,处理事物传播范围的部分源码。同时给出了,在编程式事务中,如需修改transanctionTemplate
属性问题应该怎么实现,才能避免属性的并发问题。