Spring4 @Scheduled @Transaction выдает, что транзакция не выполняется при сбросе для нескольких источников данных

Моя служба (jobExecutor, которая использует основной источник данных) отлично работает при вызове из контроллера Spring MVC, однако все время выдает «TransactionRequiredException: транзакция не выполняется» при вызове из запланированного метода. Причина заключается в том, что jdbcTransaction, привязанный к потоку из ScheduleThreadPool, имеет NOT_ACTIVE как localStatus. Транзакция предназначена для основного источника данных и по умолчанию начинается с DataSourceTransactionManager.

Я использую spring-boot, spring-data и hibernate, и ниже приведены эти версии.

пружинная загрузка: 1.2.7.RELEASE

спящий режим: 4.3.11.Final

hibernate-entitymanager: 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 Data JPA для внутреннего источника данных

JobInstanceRepository.java

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

}

Конфигурация для внешнего источника данных. Это использует JpaTransactionManager и именованный адаптерTransactionManager. Репозиторий для внешнего источника данных выглядит нормально

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$1.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$0(ExportInvoicesScheduler.java:49)
    at com.textura.client.scheduler.ExportInvoicesScheduler$$Lambda$76/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.access$201(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$1.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

Когда я отлаживаю, я вижу, что localStatus jdbcTransaction НЕ_АКТИВЕН, а ниже метод из спящего режима выдает исключение

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

person Steve Park    schedule 21.10.2015    source источник
comment
У вас есть конкретная конфигурация спящего режима в вашем application.properties? А может жулик hibernate.properties или hibernate.cfg.xml? Я бы также предложил использовать фреймворк, так как сейчас вы делаете много вещей, которые Spring Boot уже делает за вас. Ваша трассировка стека показывает перехватчик транзакций, а также некоторые вспомогательные классы. Что меня действительно беспокоит, так это тот факт, что у вас есть что-то, называемое ApplicationContextProvider, которое, я бы сказал, является анти-шаблоном, и вместо этого вы должны использовать внедрение зависимостей.   -  person M. Deinum    schedule 21.10.2015
comment
Я обновил свой application.properties. Для ApplicationContextProvider у нас был особый случай передачи контекста другому проекту. Но это не вызывает проблемы, поэтому я удаляю это, чтобы избежать путаницы.   -  person Steve Park    schedule 21.10.2015
comment
На самом деле я подозреваю, что java.util.concurrent.Executors не позволяет @ Transactional начать транзакцию, и надеюсь услышать решение, если это так.   -  person Steve Park    schedule 21.10.2015
comment
Нет, это не потому, что перехватчик транзакций присутствует несколько раз. Следовательно, я подозреваю, что что-то в вашем коде или конфигурации мешает правильной интеграции tx.   -  person M. Deinum    schedule 21.10.2015
comment
Я не упомянул, но обнаружил, что это происходит только тогда, когда я использую несколько источников данных. Я использую источник данных по умолчанию для целей внутреннего аудита и настраиваю другой источник данных для синхронизации.   -  person Steve Park    schedule 22.10.2015
comment
Если вы управляете несколькими транзакциями, это на самом деле имеет решающее значение для вашей проблемы! Вы должны указать в методе @Transactional, какую транзакцию удалось использовать, иначе для этого конкретного ресурса не будет транзакции или фиксации. Итак, вместо @Transactional вам нужно @Transactional("adapterDataSource"). Это также относится к любой другой части системы, которой необходимо использовать этот диспетчер транзакций.   -  person M. Deinum    schedule 23.10.2015
comment
на самом деле для каждого @Transactional я указываю, какой менеджер транзакций использовать @Transactional(transactionManager) для службы, связанной с одним источником, и @Transactional(adapterTransactionManager) для службы, связанной с адаптером dataSource. и я тоже проверил из журнала   -  person Steve Park    schedule 23.10.2015
comment
Но то, что у вас есть в вашей конфигурации @Transactional и для конфигурации spring-data, конфликтует... Они используют разные транзакции.   -  person M. Deinum    schedule 23.10.2015
comment
Я вижу, что transactionManager по умолчанию для источника данных по умолчанию (префикс свойства spring.datasource) настроен @EnableAutoConfiguration и @EnableTransactionManagement, для адаптера dataSource и управления транзакциями я настраиваю вручную, как указано выше. Я проверил чтение и запись данных, работающих с каждым источником данных при запуске задания из пользовательского интерфейса (эти источники данных имеют разные таблицы), однако этого не происходит при запуске по планировщику.   -  person Steve Park    schedule 23.10.2015


Ответы (5)


Я решил эту проблему, изменив конфигурацию transactionManager для внутреннего источника данных. Похоже, что transactionManager по умолчанию, настроенный @EnableTransactionManagement, является DataSourceTransactionManager, и каким-то образом метод begin() в спящем режиме AbstractTransactionImpl не вызывается, если задание поступает из любого планировщика. Поэтому я изменяю DataSourceTransactionManager для внутреннего источника данных на JpaTransactionManager, как показано ниже. теперь все транзакции для обоих источников данных проходят успешно, независимо от того, поступает ли задание из планировщика или из пользовательского интерфейса. Итак, я думаю, что моя проблема, затронутая в этом посте, была исправлена.

@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();
    }

}
person Steve Park    schedule 28.10.2015

У меня была такая же проблема с методом @Transactional @Scheduled. Обнародование метода решило проблему. Я не знаю почему!

person sparse    schedule 10.01.2019
comment
Похоже, проблема связана с тем, как работают прокси-серверы Spring — baeldung.com/. Только общедоступные методы в проксируемом экземпляре могут использовать аннотацию @Transactional. - person Curtis Snowden; 27.08.2019

Простое добавление @Transactional к методу или классу у меня не сработало, мне пришлось добавить имя компонента менеджера транзакций, используя @Transactional(value="entityTransactionManager"), после чего все заработало нормально.

person SHALOM OLOMOLAIYE    schedule 09.12.2019

Я предлагаю попробовать сделать ваше репо транзакционным или отключить ваши транзакционные свойства в вашем проекте и попробовать. Это может произойти, когда вы пытаетесь получить доступ к нетранзакционному контексту из транзакционного контекста.

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

}
person Maleen Abewardana    schedule 21.10.2015
comment
Я не получил никакой разницы от добавления @Transactional в репозиторий - person Steve Park; 21.10.2015

Стив, Spring сообщил, что это может быть связано? https://jira.spring.io/browse/SPR-5082.

Попробуйте удалить аннотацию @Service из JobExecutorImpl и добавить ее в свой класс ServerConfig с помощью @Bean.

Кроме того, я бы временно удалил @EnableAspectJAutoProxy, чтобы увидеть, есть ли конфликт со сканированием сторон. По крайней мере, одна вещь, о которой не стоит беспокоиться при поиске корневой проблемы.

person user2186497    schedule 21.10.2015
comment
Я следовал тому, что вы предложили (используя @Bean и удаляя @EnableAspectJAutoProxy), но, к сожалению, не вижу никакой разницы - person Steve Park; 21.10.2015