Spring4 @Scheduled @Transaction 抛出多个数据源刷新时没有正在进行的事务

Spring4 @Scheduled @Transaction throws no transaction is in progress at flush for mutliple dataSources

我的服务(使用主数据源的 jobExecutor)在从 spring MVC 控制器调用时工作正常,但是在从调度方法调用时总是抛出 "TransactionRequiredException: no transaction is in progress"。原因看起来绑定到从 scheduledThreadPool 的线程上的 jdbcTransaction 具有 NOT_ACTIVE 作为 localStatus。该事务用于主数据源,默认情况下开始于 DataSourceTransactionManager。

我正在使用 spring-boot、spring-data 和 hibernate 及以下版本

spring-引导:1.2.7.RELEASE

休眠核心:4.3.11.Final

休眠实体管理器:4.3.11.Final

同样使用java配置

ServerConfig.java

@Configuration
@EnableAutoConfiguration
@EnableScheduling
@EnableAsync
@EnableAspectJAutoProxy
@ComponentScan("com.my.client")
@EnableTransactionManagement
@EntityScan(basePackages = {"com.my.database.model"})
@EnableJpaRepositories(
    transactionManagerRef = "transactionManager",
    basePackages = {"com.my.database.repository"})
public class ServerConfig extends SpringBootServletInitializer implements SchedulingConfigurer, AsyncConfigurer {

    static Logger log = Logger.getLogger(ServerModeConfig.class.getName());

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }

    /**
     * get executor for scheduling job
     * @return scheduled executor
     */
    @Bean(destroyMethod="shutdown")
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(20);
    }

    /**
     * get executor for async job
     * @return executor for asynchronous job but no time limit
     */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(200);
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyClientAsyncExceptionHandler();
    }

}

MyScheduler.java

@Component
public class MyScheduler {

    @Autowired
    protected JobExecutor jobExecutor;

    @Override
    @Scheduled(cron = "0 */1 * * * *") // every minute
    public void run() {
        log.info("Trigger job");
        jobExecutor.execute();
    }
}

服务层

JobExecutorImpl.java

@Service("jobExecutor")
@Transactional("transactionManager")
public class JobExecutorImpl implements JobExecutor {

    static Logger log = Logger.getLogger(JobExecutorImpl.class.getName());

    @Override
    public ClientJobBehaviour execute() {
        log.info("transaction exists? ".concat( String.valueOf(TransactionSynchronizationManager.isActualTransactionActive()) )); // true
        log.info("transaction sync? ".concat( String.valueOf(TransactionSynchronizationManager.isSynchronizationActive()) ));  // true

        ClientJobBehaviour job = new ClientJobBehaviour();
        JobInstance jobInstance = new JobInstance();
        jobInstance.setStatus(JobStatus.STARTED.toString());
        jobInstance = jobInstanceRepository.save(jobInstance);
        jobInstanceRepository.flush();  // throws TransactionRequiredException
        job.setInstanceId(jobInstance.getId());
        return job;
    }
}

Spring 内部数据源的数据 JPA 存储库

JobInstanceRepository.java

@Repository
public interface JobInstanceRepository extends JpaRepository<JobInstance, Long>{

}

外部数据源的配置。 这使用 JpaTransactionManager 并命名为 adapterTransactionManager。 外部数据源的存储库看起来工作正常

ExternalRepositoryConfig.java

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "adapterEntityManagerFactory",
        transactionManagerRef = "adapterTransactionManager",
        basePackages = {"com.my.adapter.database.repository"})
public class ExternalRepositoryConfig {
    @Autowired
    JpaVendorAdapter jpaVendorAdapter;

    @Value("${adapter.datasource.url}")
    private String databaseUrl;

    @Value("${adapter.datasource.username}")
    private String username;

    @Value("${adapter.datasource.password}")
    private String password;

    @Value("${adapter.datasource.hibernate.dialect}")
    private String dialect;

    public DataSource dataSource() {
        return new DriverManagerDataSource(databaseUrl, username, password);
    }

    @Bean(name = "adapterEntityManager")
    public EntityManager entityManager() {
        return entityManagerFactory().createEntityManager();
    }

    @Bean(name = "adapterEntityManagerFactory")
    public EntityManagerFactory entityManagerFactory() {
        Properties properties = new Properties();
        properties.setProperty("hibernate.dialect", dialect);

        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource());
        emf.setJpaVendorAdapter(jpaVendorAdapter);
        emf.setPackagesToScan("com.my.client.another.database.model");
        emf.setPersistenceUnitName("adapterPersistenceUnit");
        emf.setJpaProperties(properties);
        emf.afterPropertiesSet();
        return emf.getObject();
    }

    @Bean(name = "adapterTransactionManager")
    public PlatformTransactionManager transactionManager() {
        return new JpaTransactionManager(entityManagerFactory());
    }
}

下面是从预定方法调用服务时的stackTrace

    org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:410)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:122)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy95.flush(Unknown Source)
    at com.textura.client.job.executor.JobExecutorHelperImpl.preProcess(JobExecutorHelperImpl.java:62)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy99.preProcess(Unknown Source)
    at com.textura.client.job.executor.JobExecutorImpl.execute(JobExecutorImpl.java:30)
    at com.textura.client.scheduler.ExportInvoicesScheduler.lambda[=15=](ExportInvoicesScheduler.java:49)
    at com.textura.client.scheduler.ExportInvoicesScheduler$$Lambda/1738859546.accept(Unknown Source)
    at java.util.Optional.ifPresent(Optional.java:159)
    at com.textura.client.scheduler.ExportInvoicesScheduler.run(ExportInvoicesScheduler.java:43)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access1(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.checkTransactionNeeded(AbstractEntityManagerImpl.java:1171)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1332)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:344)
    at com.sun.proxy.$Proxy86.flush(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:291)
    at com.sun.proxy.$Proxy86.flush(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:480)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:436)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:421)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:393)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$DefaultMethodInvokingMethodInterceptor.invoke(RepositoryFactorySupport.java:506)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    ... 40 common frames omitted

当我调试时,我可以看到 jdbcTransaction 的 localStatus 是 NOT_ACTIVE 并且下面的休眠方法抛出异常

AbstractEntityManagerImpl.java

private void checkTransactionNeeded() {
    if ( !isTransactionInProgress() ) {
        throw new TransactionRequiredException(
                "no transaction is in progress"
        );
    }
}

我们有两个数据源,一个供内部使用,另一个供外部使用

application.properties

# database configuration
spring.datasource.url=jdbc:h2:file:~/internal-db;AUTO_SERVER=TRUE;LOCK_TIMEOUT=10000
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.schema=schema.sql
spring.datasource.data=data.sql
spring.datasource.initialize=false
#spring.datasource.initialize=true only for first time to create table, after that switch to false

# JPA. Hibernate
spring.jpa.database=H2
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=validate
#spring.jpa.hibernate.ddl-auto=choose one of [create-drop, create, update, validate, none]
spring.jpa.hibernate.naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.show-sql=false
spring.data.jpa.repositories.enabled=true

adapter.datasource.url=jdbc:sqlserver://some.host.com:1433;databaseName=MyDBname
adapter.datasource.username=sa
adapter.datasource.password=
adapter.datasource.hibernate.dialect=org.hibernate.dialect.SQLServerDialect

我建议尝试使您的 repo 交易,或从您的项目中禁用您的交易属性并尝试一下。当您尝试从事务上下文访问非事务上下文时,可能会发生这种情况。

@Repository
@Transactional
public interface JobInstanceRepository extends JpaRepository<JobInstance, Long>{

}

史蒂夫,Spring 的报告可能与此相关? https://jira.spring.io/browse/SPR-5082.

尝试从 JobExecutorImpl 中删除 @Service 注释并将其添加到带有 @Bean 的 ServerConfig class。

此外,我会临时删除@EnableAspectJAutoProxy 以查看是否与纵横比扫描有任何冲突。在找到根本问题时,至少不要担心一件事。

我已经通过更改内部数据源的 transactionManager 配置解决了这个问题。它看起来由 @EnableTransactionManagement 配置的默认 transactionManager 是 DataSourceTransactionManager 并且如果作业来自任何调度程序,则不会调用 hibernate AbstractTransactionImpl 上的 begin() 方法。因此,我将内部数据源的 DataSourceTransactionManager 更改为 JpaTransactionManager ,如下所示。现在,无论作业来自调度程序还是 UI,两个数据源的所有事务都成功进行。所以我认为我在这个 post 上解决的问题已经解决

@Configuration
@EnableAutoConfiguration
@EnableScheduling
@EnableAsync
@EnableAspectJAutoProxy
@ComponentScan("com.my.client")
@EnableTransactionManagement
@EnableJpaRepositories(
    entityManagerFactoryRef = "entityManagerFactory",
    transactionManagerRef = "transactionManager",
    basePackages = {"com.my.database.repository"})
public class ServerConfig extends SpringBootServletInitializer implements SchedulingConfigurer, AsyncConfigurer {

    static Logger log = Logger.getLogger(ServerModeConfig.class.getName());

    @Autowired
    JpaVendorAdapter jpaVendorAdapter;

    @Autowired
    DataSource dataSource;

    @Bean(name = "entityManager")
    public EntityManager entityManager() {
        return entityManagerFactory().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManagerFactory")
    EntityManagerFactory entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource);
        emf.setJpaVendorAdapter(jpaVendorAdapter);
        emf.setPackagesToScan("com.my.client.database.model");
        emf.setPersistenceUnitName("default");
        emf.afterPropertiesSet();
        return emf.getObject();
    }

    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager tm = new JpaTransactionManager();
        tm.setEntityManagerFactory(entityManagerFactory());
        return tm;
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }

    /**
     * get executor for scheduling job
     * @return scheduled executor
     */
    @Bean(destroyMethod="shutdown")
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(20);
    }

    /**
     * get executor for async job
     * @return executor for asynchronous job but no time limit
     */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(200);
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyClientAsyncExceptionHandler();
    }

}

我在使用 @Transactional @Scheduled 方法时遇到了同样的问题。制作方法public解决了问题。我不知道为什么!

仅向方法添加 @Transactional 或 class 对我不起作用,我必须使用 @Transactional(value="entityTransactionManager") 添加事务管理器的 bean 名称 在此之后它工作正常。