В моем приложении я использую 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
.
Что вызывает ошибку, когда кварцевый планировщик запускает задания? Что мне нужно изменить, чтобы исправить это?
Если у вас есть какие-либо вопросы, не стесняйтесь спрашивать