Quartz SimpleAsyncTaskExecutor «Не удалось получить сеанс с синхронизацией транзакций для текущего потока» - PullRequest
0 голосов
/ 04 ноября 2019

В моем приложении я использую quartz и quartz-jobs. Оба v2.2.1. Кажется, моя конфигурация немного испорчена, но я пока не смог найти решение. Всякий раз, когда заданию требуется доступ к базе данных, я получаю следующее исключение:

org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
    at org.springframework.orm.hibernate5.SpringSessionContext.currentSession(SpringSessionContext.java:143)
    at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:497)
    at com.example.vanguard.dataaccess.hibernate.AbstractHibernateDAO.getSession(AbstractHibernateDAO.java:306)
    at com.example.foliot.web.dao.impl.JobHistoryDaoImpl.findRunningJobs(JobHistoryDaoImpl.java:21)
    at com.example.foliot.web.bc.impl.JobBCImpl.startJob(JobBCImpl.java:20)
    at com.example.foliot.web.jobs.FoliotBaseJob.startJob(FoliotBaseJob.java:53)
    at com.example.foliot.web.jobs.FoliotBaseJob.execute(FoliotBaseJob.java:46)
    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:498)
    at org.springframework.util.MethodInvoker.invoke(MethodInvoker.java:283)
    at org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean$MethodInvokingJob.executeInternal(MethodInvokingJobDetailFactoryBean.java:267)
    at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
    at org.springframework.core.task.SimpleAsyncTaskExecutor$ConcurrencyThrottlingRunnable.run(SimpleAsyncTaskExecutor.java:275)
    at java.lang.Thread.run(Thread.java:748)

Конфигурация представляет собой XML, смешанный с @Configuration.

Конфигурация планирования (XML):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">


    <bean
        class="com.example.foliot.web.util.TriggerAndJobDetailCreator" />

    <bean
        class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
        id="schedulerFactoryBean">
        <property name="taskExecutor" ref="taskExecutor">
        </property>
        <property name="transactionManager" ref="jobTxManager" />
        <property name="triggers" ref="globalTriggerList" />
        <property name="quartzProperties">
            <props>
                <prop key="org.quartz.scheduler.skipUpdateCheck">true</prop>
            </props>
        </property>
    </bean>
    <bean id="globalTriggerList"
        class="com.example.foliot.web.util.TriggerListFactory">
    </bean>

    <bean id="taskExecutor"
        class="org.springframework.core.task.SimpleAsyncTaskExecutor">
        <property name="concurrencyLimit" value="5" />
    </bean>

    <bean abstract="true" id="baseJob">
        <property name="exceptionEmailSender"
            ref="jobExceptionEmailSender" />
         <property name="jobBC" ref="jobBC" /> 
        <property name="checkExistingJobs" value="true" />
    </bean>

</beans>

Конфигурация доступа к базе данных:

@Configuration
@EnableTransactionManagement
public class DataAccess {

    @Autowired
    private Environment env;

    @Bean(name = "dataSource")
    public BasicDataSource dataSource() {
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName(env.getProperty("db_driver"));
        ds.setUrl(env.getProperty("db_url"));
        ds.setUsername(env.getProperty("db_user"));
        ds.setPassword(env.getProperty("db_password"));
        ds.setDefaultCatalog(env.getProperty("db_default_catalog"));
        ds.setDefaultAutoCommit(env.getProperty("db_autocommit", Boolean.class, Boolean.TRUE));
        return ds;
    }

    @Bean
    public SessionFactory getSessionFactory() {
        LocalSessionFactoryBean factory = new LocalSessionFactoryBean();
        factory.setDataSource(dataSource());
        factory.setPackagesToScan("com.example.foliot.web.model");

        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty("hibernate.dialect", env.getProperty("db_hibernate_dialect"));
        hibernateProperties.setProperty("hibernate.format_sql", env.getProperty("db_hibernate_format_sql"));
        hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("db_hibernate_show_sql"));
        hibernateProperties.setProperty("hibernate.max_fetch_depth", "3");
        hibernateProperties.setProperty("hibernate.jdbc.batch_size", "0");
        hibernateProperties.setProperty("hibernate.jdbc.batch_versioned_data", "true");
        hibernateProperties.setProperty("hibernate.connection.CharSet", "utf8");
        hibernateProperties.setProperty("hibernate.connection.characterEncoding", "utf8");
        hibernateProperties.setProperty("hibernate.connection.useUnicode", "true");

        factory.setHibernateProperties(hibernateProperties);
        try {
            factory.afterPropertiesSet();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return factory.getObject();
    }

    @Bean(name = "txManager")
    public HibernateTransactionManager getTransactionManager() {
        HibernateTransactionManager manager = new HibernateTransactionManager();
        manager.setDataSource(dataSource());
        manager.setSessionFactory(getSessionFactory());
        return manager;
    }

    @Bean(name = "jobTxManager")
    public HibernateTransactionManager getJobTransactionManager() {
        HibernateTransactionManager manager = new HibernateTransactionManager();
        manager.setDataSource(dataSource());
        manager.setSessionFactory(getSessionFactory());
        manager.setTransactionSynchronization(AbstractPlatformTransactionManager.SYNCHRONIZATION_ON_ACTUAL_TRANSACTION);
        return manager;
    }

    @Bean
    public JobHistoryDao getJobHistoryDao() {
        JobHistoryDaoImpl dao = new JobHistoryDaoImpl();
        dao.setSessionFactory(getSessionFactory());
        return dao;
    }

}

Конфигурация бизнес-контроллера:

@Configuration
public class BcConfig {

    @Bean(name = "foliotBC")
    public FoliotBC getFoliotBC() {
        return new FoliotBCImpl();
    }

    @Bean(name = "jobService")
    public JobService getJobService() {
        return new JobServiceImpl();
    }

    @Bean(name = "jobDelegate")
    public JobDelegate getJobDelegate() {
        return new JobDelegateImpl();
    }

    @Bean(name = "jobBC")
    public JobBC getJobBC() {
        return new JobBCImpl();
    }
}

Это все конфигурация. Ниже приведен код вызова:

public abstract class FoliotBaseJob {

    private static final Logger LOG = LoggerFactory.getLogger(FoliotBaseJob.class);
    private ExceptionEmailSender exceptionEmailSender;

    private boolean checkExistingJobs;

    private boolean enabled;

    private final String jobName;
    private final String jobGroup;
    private String cronExpression;

    private JobBC jobBC;

    public FoliotBaseJob() {
        this("DEFAULT");
    }

    public FoliotBaseJob(String jobName) {
        this(jobName, "DEFAULT");
    }

    public FoliotBaseJob(String jobName, String jobGroup) {
        this.jobName = jobName;
        this.jobGroup = jobGroup;
    }
    // Called by the scheduler
    public final JobStatus execute() { 
        if (!this.enabled) {
            LOG.debug("Job " + this.jobName + " - is disabled");
            return JobStatus.UNKNOWN;
        }
        return startJob();
    }

    private JobStatus startJob() {
        JobHistory job = null;
        try {
            job = jobBC.startJob(getJobName(), this.checkExistingJobs);
        } catch (Exception e) {
            LOG.error("Error during job creation " + getJobName(), e);
            this.exceptionEmailSender.send(e);
        }
        if (job != null) {
            try {
                LOG.debug("Starting job [{}]", getJobName());
                executeJob();
                LOG.debug("Finished job [{}]", getJobName());
                job.setJobStatus(JobStatus.FINISHED);
            } catch (Exception e) { // Exception during job excution
                String emailSubject = "Error during execution of job " + getJobName();
                this.exceptionEmailSender.send(e, emailSubject);
                LOG.error(emailSubject, e);
                job.setJobStatus(JobStatus.ERROR);
            }
            DomainObjectMetaDataInjector.fillBaseMetaDataForUpdate(job);
            this.jobBC.updateJob(job);
            return job.getJobStatus();
        }
        return JobStatus.ERROR;
    }

    protected abstract void executeJob();
}

JobBC:

@Transactional(transactionManager = "jobTxManager")
public interface JobBC {
    JobHistory startJob(String jobName, boolean checkExistingJobs);

    void updateJob(JobHistory job);
}

И реализация:

public class JobBCImpl implements JobBC {
    @Autowired
    private JobHistoryDao jobHistoryDao;

    @Override
    public JobHistory startJob(String jobName, boolean checkExistingJobs) {
        List<JobHistory> runningJobs = jobHistoryDao.findRunningJobs(jobName);
        if ((CollectionUtils.isEmpty(runningJobs) && checkExistingJobs)
                || (!CollectionUtils.isEmpty(runningJobs) && !checkExistingJobs)) {
            JobHistory history = new JobHistory();
            history.setJobName(jobName);
            history.setJobStatus(JobStatus.RUNNING);
            DomainObjectMetaDataInjector.fillBaseMetaDataForInsert(history);
            jobHistoryDao.insert(history);
            return history;
        }
        return null;
    }

    @Override
    public void updateJob(JobHistory job) {
        jobHistoryDao.update(job);
    }

}

Как видите, все, что требуетсядля транзакционной базы данных предоставляется доступ (насколько я могу судить). Все же я получаю ошибку выше.

Предисловие: приложение является веб-приложением. Исключение выдается только в том случае, если вызывающий поток равен SimpleAsyncTaskExecutor. Как только я выполняю задание через веб-приложение, оно работает нормально. Тогда поток будет http-nio-8080-exec.

Что вызывает ошибку, когда кварцевый планировщик запускает задания? Что мне нужно изменить, чтобы исправить это?


Если у вас есть какие-либо вопросы, не стесняйтесь спрашивать

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...