Spring boot、多原则、多模块、@Transactional、并行流
我正在尝试将大约50k条记录插入数据库。我们使用了AbstractRoutingDataSource,它使用TenantContext解析数据源,TenantContext是一个实用程序类,具有私有静态final ThreadLocal CURRENT_TENANT=new ThreadLocal() 当我使用并行流时,或者如果我试图使用@Async方法,我会得到以下错误 代码: 错误:Spring boot、多原则、多模块、@Transactional、并行流,spring,multithreading,spring-boot,transactions,multi-module,Spring,Multithreading,Spring Boot,Transactions,Multi Module,我正在尝试将大约50k条记录插入数据库。我们使用了AbstractRoutingDataSource,它使用TenantContext解析数据源,TenantContext是一个实用程序类,具有私有静态final ThreadLocal CURRENT_TENANT=new ThreadLocal() 当我使用并行流时,或者如果我试图使用@Async方法,我会得到以下错误 代码: 错误: org.springframework.transaction.CannotCreateTransacti
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.lang.IllegalStateException: Cannot determine target DataSource for lookup key [null]
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:305)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:378)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:474)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:289)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: Cannot determine target DataSource for lookup key [null]
at org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource.determineTargetDataSource(AbstractRoutingDataSource.java:207)
at org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource.getConnection(AbstractRoutingDataSource.java:169)
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:262)
... 10 common frames omitted
它的工作原理与您描述的完全相同:您的TenantContext完全是ThreadLocal,并且存在于一个线程中,该线程由
parallelStream()
或Async
方法启动。(实际上,Async
或forEach
方法内部的调用是run
fromRunnable
)
试图在线程开始时注入/解析数据源:因为在Runnable
进入run
方法之前,必须在线程创建时启动事务。此时您尚未指定租户,请调用TenantContext。稍后将在run
方法实现中执行setCurrentTenant(centerCd)
我建议将这种结构应用于您的代码:
class TenantAwareThread extends Thread {
public TenantAwareThread(Runnable target, TenantData tenantData) {
super(target);
TenantContext.setCurrentTenant(tenantData);
}
}
@Autowired
TaskExecutor executor;
void startTask(TenantData tenantData, RowData row) {
executor.execute(
new TenantAwareThread(() -> {
someDao.insert(row);
},
tenantData));
}
您创建一个新的线程类型,它从一开始就知道租户数据。并简单地将您的执行打包到这样的线程中 它的工作原理与您描述的完全相同:您的TenantContext完全是ThreadLocal,并且存在于一个线程中,该线程由
parallelStream()
或Async
方法启动。(实际上,Async
或forEach
方法内部的调用是run
fromRunnable
)
试图在线程开始时注入/解析数据源:因为在Runnable
进入run
方法之前,必须在线程创建时启动事务。此时您尚未指定租户,请调用TenantContext。稍后将在run
方法实现中执行setCurrentTenant(centerCd)
我建议将这种结构应用于您的代码:
class TenantAwareThread extends Thread {
public TenantAwareThread(Runnable target, TenantData tenantData) {
super(target);
TenantContext.setCurrentTenant(tenantData);
}
}
@Autowired
TaskExecutor executor;
void startTask(TenantData tenantData, RowData row) {
executor.execute(
new TenantAwareThread(() -> {
someDao.insert(row);
},
tenantData));
}
您创建一个新的线程类型,它从一开始就知道租户数据。并简单地将您的执行打包到这样的线程中