Spring boot、多原则、多模块、@Transactional、并行流

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

我正在尝试将大约50k条记录插入数据库。我们使用了AbstractRoutingDataSource,它使用TenantContext解析数据源,TenantContext是一个实用程序类,具有私有静态final ThreadLocal CURRENT_TENANT=new ThreadLocal()

当我使用并行流时,或者如果我试图使用@Async方法,我会得到以下错误

代码:

错误:


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
from
Runnable

试图在线程开始时注入/解析数据源:因为在
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
from
Runnable

试图在线程开始时注入/解析数据源:因为在
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));
}
您创建一个新的线程类型,它从一开始就知道租户数据。并简单地将您的执行打包到这样的线程中