Re-initialize Quartz ConnectionProvider on context restart
Closes gh-35208
This commit is contained in:
parent
6f5a7eed76
commit
17382fe079
|
@ -35,6 +35,7 @@ import org.quartz.utils.DBConnectionManager;
|
||||||
import org.springframework.jdbc.datasource.DataSourceUtils;
|
import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||||
import org.springframework.jdbc.support.JdbcUtils;
|
import org.springframework.jdbc.support.JdbcUtils;
|
||||||
import org.springframework.jdbc.support.MetaDataAccessException;
|
import org.springframework.jdbc.support.MetaDataAccessException;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclass of Quartz's {@link JobStoreCMT} class that delegates to a Spring-managed
|
* Subclass of Quartz's {@link JobStoreCMT} class that delegates to a Spring-managed
|
||||||
|
@ -88,6 +89,8 @@ public class LocalDataSourceJobStore extends JobStoreCMT {
|
||||||
|
|
||||||
private @Nullable DataSource dataSource;
|
private @Nullable DataSource dataSource;
|
||||||
|
|
||||||
|
private @Nullable DataSource nonTransactionalDataSource;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("NullAway") // Dataflow analysis limitation
|
@SuppressWarnings("NullAway") // Dataflow analysis limitation
|
||||||
|
@ -98,11 +101,40 @@ public class LocalDataSourceJobStore extends JobStoreCMT {
|
||||||
throw new SchedulerConfigException("No local DataSource found for configuration - " +
|
throw new SchedulerConfigException("No local DataSource found for configuration - " +
|
||||||
"'dataSource' property must be set on SchedulerFactoryBean");
|
"'dataSource' property must be set on SchedulerFactoryBean");
|
||||||
}
|
}
|
||||||
|
// Non-transactional DataSource is optional: fall back to default
|
||||||
|
// DataSource if not explicitly specified.
|
||||||
|
this.nonTransactionalDataSource = SchedulerFactoryBean.getConfigTimeNonTransactionalDataSource();
|
||||||
|
|
||||||
// Configure transactional connection settings for Quartz.
|
// Configure connection settings for Quartz.
|
||||||
setDataSource(TX_DATA_SOURCE_PREFIX + getInstanceName());
|
setDataSource(TX_DATA_SOURCE_PREFIX + getInstanceName());
|
||||||
|
setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName());
|
||||||
setDontSetAutoCommitFalse(true);
|
setDontSetAutoCommitFalse(true);
|
||||||
|
|
||||||
|
initializeConnectionProvider();
|
||||||
|
|
||||||
|
// No, if HSQL is the platform, we really don't want to use locks...
|
||||||
|
try {
|
||||||
|
String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource,
|
||||||
|
DatabaseMetaData::getDatabaseProductName);
|
||||||
|
productName = JdbcUtils.commonDatabaseName(productName);
|
||||||
|
if (productName != null && productName.toLowerCase(Locale.ROOT).contains("hsql")) {
|
||||||
|
setUseDBLocks(false);
|
||||||
|
setLockHandler(new SimpleSemaphore());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MetaDataAccessException ex) {
|
||||||
|
logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken.");
|
||||||
|
}
|
||||||
|
|
||||||
|
super.initialize(loadHelper, signaler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeConnectionProvider() {
|
||||||
|
final DataSource dataSourceToUse = this.dataSource;
|
||||||
|
Assert.state(dataSourceToUse != null, "DataSource must not be null");
|
||||||
|
final DataSource nonTxDataSourceToUse =
|
||||||
|
(this.nonTransactionalDataSource != null ? this.nonTransactionalDataSource : dataSourceToUse);
|
||||||
|
|
||||||
// Register transactional ConnectionProvider for Quartz.
|
// Register transactional ConnectionProvider for Quartz.
|
||||||
DBConnectionManager.getInstance().addConnectionProvider(
|
DBConnectionManager.getInstance().addConnectionProvider(
|
||||||
TX_DATA_SOURCE_PREFIX + getInstanceName(),
|
TX_DATA_SOURCE_PREFIX + getInstanceName(),
|
||||||
|
@ -110,7 +142,7 @@ public class LocalDataSourceJobStore extends JobStoreCMT {
|
||||||
@Override
|
@Override
|
||||||
public Connection getConnection() throws SQLException {
|
public Connection getConnection() throws SQLException {
|
||||||
// Return a transactional Connection, if any.
|
// Return a transactional Connection, if any.
|
||||||
return DataSourceUtils.doGetConnection(dataSource);
|
return DataSourceUtils.doGetConnection(dataSourceToUse);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
|
@ -123,14 +155,6 @@ public class LocalDataSourceJobStore extends JobStoreCMT {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Non-transactional DataSource is optional: fall back to default
|
|
||||||
// DataSource if not explicitly specified.
|
|
||||||
DataSource nonTxDataSource = SchedulerFactoryBean.getConfigTimeNonTransactionalDataSource();
|
|
||||||
final DataSource nonTxDataSourceToUse = (nonTxDataSource != null ? nonTxDataSource : this.dataSource);
|
|
||||||
|
|
||||||
// Configure non-transactional connection settings for Quartz.
|
|
||||||
setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName());
|
|
||||||
|
|
||||||
// Register non-transactional ConnectionProvider for Quartz.
|
// Register non-transactional ConnectionProvider for Quartz.
|
||||||
DBConnectionManager.getInstance().addConnectionProvider(
|
DBConnectionManager.getInstance().addConnectionProvider(
|
||||||
NON_TX_DATA_SOURCE_PREFIX + getInstanceName(),
|
NON_TX_DATA_SOURCE_PREFIX + getInstanceName(),
|
||||||
|
@ -150,23 +174,6 @@ public class LocalDataSourceJobStore extends JobStoreCMT {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// No, if HSQL is the platform, we really don't want to use locks...
|
|
||||||
try {
|
|
||||||
String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource,
|
|
||||||
DatabaseMetaData::getDatabaseProductName);
|
|
||||||
productName = JdbcUtils.commonDatabaseName(productName);
|
|
||||||
if (productName != null && productName.toLowerCase(Locale.ROOT).contains("hsql")) {
|
|
||||||
setUseDBLocks(false);
|
|
||||||
setLockHandler(new SimpleSemaphore());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (MetaDataAccessException ex) {
|
|
||||||
logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken.");
|
|
||||||
}
|
|
||||||
|
|
||||||
super.initialize(loadHelper, signaler);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -28,6 +28,8 @@ import org.jspecify.annotations.Nullable;
|
||||||
import org.quartz.Scheduler;
|
import org.quartz.Scheduler;
|
||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
import org.quartz.SchedulerFactory;
|
import org.quartz.SchedulerFactory;
|
||||||
|
import org.quartz.core.QuartzScheduler;
|
||||||
|
import org.quartz.core.QuartzSchedulerResources;
|
||||||
import org.quartz.impl.RemoteScheduler;
|
import org.quartz.impl.RemoteScheduler;
|
||||||
import org.quartz.impl.SchedulerRepository;
|
import org.quartz.impl.SchedulerRepository;
|
||||||
import org.quartz.impl.StdSchedulerFactory;
|
import org.quartz.impl.StdSchedulerFactory;
|
||||||
|
@ -165,7 +167,7 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
|
||||||
|
|
||||||
private @Nullable SchedulerFactory schedulerFactory;
|
private @Nullable SchedulerFactory schedulerFactory;
|
||||||
|
|
||||||
private Class<? extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class;
|
private Class<? extends SchedulerFactory> schedulerFactoryClass = LocalSchedulerFactory.class;
|
||||||
|
|
||||||
private @Nullable String schedulerName;
|
private @Nullable String schedulerName;
|
||||||
|
|
||||||
|
@ -203,6 +205,8 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
|
||||||
|
|
||||||
private @Nullable Scheduler scheduler;
|
private @Nullable Scheduler scheduler;
|
||||||
|
|
||||||
|
private @Nullable LocalDataSourceJobStore jobStore;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set an external Quartz {@link SchedulerFactory} instance to use.
|
* Set an external Quartz {@link SchedulerFactory} instance to use.
|
||||||
|
@ -223,11 +227,12 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the Quartz {@link SchedulerFactory} implementation to use.
|
* Set the Quartz {@link SchedulerFactory} implementation to use.
|
||||||
* <p>Default is the {@link StdSchedulerFactory} class, reading in the standard
|
* <p>Default is a Spring-internal subclass of the {@link StdSchedulerFactory}
|
||||||
* {@code quartz.properties} from {@code quartz.jar}. For applying custom Quartz
|
* class, reading in the standard {@code quartz.properties} from
|
||||||
* properties, specify {@link #setConfigLocation "configLocation"} and/or
|
* {@code quartz.jar}. For applying custom Quartz properties,
|
||||||
* {@link #setQuartzProperties "quartzProperties"} etc on this local
|
* specify {@link #setConfigLocation "configLocation"} and/or
|
||||||
* {@code SchedulerFactoryBean} instance.
|
* {@link #setQuartzProperties "quartzProperties"} etc on this
|
||||||
|
* local {@code SchedulerFactoryBean} instance.
|
||||||
* @see org.quartz.impl.StdSchedulerFactory
|
* @see org.quartz.impl.StdSchedulerFactory
|
||||||
* @see #setConfigLocation
|
* @see #setConfigLocation
|
||||||
* @see #setQuartzProperties
|
* @see #setQuartzProperties
|
||||||
|
@ -508,8 +513,9 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
|
||||||
private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
|
private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
|
||||||
SchedulerFactory schedulerFactory = this.schedulerFactory;
|
SchedulerFactory schedulerFactory = this.schedulerFactory;
|
||||||
if (schedulerFactory == null) {
|
if (schedulerFactory == null) {
|
||||||
// Create local SchedulerFactory instance (typically a StdSchedulerFactory)
|
// Create local SchedulerFactory instance (typically a LocalSchedulerFactory)
|
||||||
schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
|
schedulerFactory = (this.schedulerFactoryClass == LocalSchedulerFactory.class ?
|
||||||
|
new LocalSchedulerFactory() : BeanUtils.instantiateClass(this.schedulerFactoryClass));
|
||||||
if (schedulerFactory instanceof StdSchedulerFactory stdSchedulerFactory) {
|
if (schedulerFactory instanceof StdSchedulerFactory stdSchedulerFactory) {
|
||||||
initSchedulerFactory(stdSchedulerFactory);
|
initSchedulerFactory(stdSchedulerFactory);
|
||||||
}
|
}
|
||||||
|
@ -778,6 +784,9 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
|
||||||
@Override
|
@Override
|
||||||
public void start() throws SchedulingException {
|
public void start() throws SchedulingException {
|
||||||
if (this.scheduler != null) {
|
if (this.scheduler != null) {
|
||||||
|
if (this.jobStore != null) {
|
||||||
|
this.jobStore.initializeConnectionProvider();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
startScheduler(this.scheduler, this.startupDelay);
|
startScheduler(this.scheduler, this.startupDelay);
|
||||||
}
|
}
|
||||||
|
@ -829,4 +838,16 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class LocalSchedulerFactory extends StdSchedulerFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Scheduler instantiate(QuartzSchedulerResources rsrcs, QuartzScheduler qs) {
|
||||||
|
if (rsrcs.getJobStore() instanceof LocalDataSourceJobStore ldsjs) {
|
||||||
|
SchedulerFactoryBean.this.jobStore = ldsjs;
|
||||||
|
}
|
||||||
|
return super.instantiate(rsrcs, qs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -391,6 +391,8 @@ class QuartzSupportTests {
|
||||||
try (ClassPathXmlApplicationContext ctx = context("databasePersistence.xml")) {
|
try (ClassPathXmlApplicationContext ctx = context("databasePersistence.xml")) {
|
||||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(ctx.getBean(DataSource.class));
|
JdbcTemplate jdbcTemplate = new JdbcTemplate(ctx.getBean(DataSource.class));
|
||||||
assertThat(jdbcTemplate.queryForList("SELECT * FROM qrtz_triggers").isEmpty()).as("No triggers were persisted").isFalse();
|
assertThat(jdbcTemplate.queryForList("SELECT * FROM qrtz_triggers").isEmpty()).as("No triggers were persisted").isFalse();
|
||||||
|
ctx.stop();
|
||||||
|
ctx.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,28 +5,28 @@
|
||||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||||
|
|
||||||
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
|
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
|
||||||
<property name="triggers" ref="trigger" />
|
<property name="triggers" ref="trigger"/>
|
||||||
<property name="dataSource" ref="dataSource" />
|
<property name="dataSource" ref="dataSource"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="trigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
<bean id="trigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||||
<property name="repeatInterval" value="1000" />
|
<property name="repeatInterval" value="1000"/>
|
||||||
<property name="repeatCount" value="1" />
|
<property name="repeatCount" value="1"/>
|
||||||
<property name="jobDetail">
|
<property name="jobDetail">
|
||||||
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
|
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
|
||||||
<property name="jobDataAsMap">
|
<property name="jobDataAsMap">
|
||||||
<map>
|
<map>
|
||||||
<entry key="param" value="10" />
|
<entry key="param" value="10"/>
|
||||||
</map>
|
</map>
|
||||||
</property>
|
</property>
|
||||||
<property name="jobClass" value="org.springframework.scheduling.quartz.QuartzSupportTests$DummyJob" />
|
<property name="jobClass" value="org.springframework.scheduling.quartz.QuartzSupportTests$DummyJob"/>
|
||||||
<property name="durability" value="true" />
|
<property name="durability" value="true"/>
|
||||||
</bean>
|
</bean>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<jdbc:embedded-database id="dataSource" type="HSQL">
|
<jdbc:embedded-database id="dataSource" type="HSQL">
|
||||||
<jdbc:script location="org/springframework/scheduling/quartz/quartz-hsql.sql" />
|
<jdbc:script location="org/springframework/scheduling/quartz/quartz-hsql.sql"/>
|
||||||
</jdbc:embedded-database>
|
</jdbc:embedded-database>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
|
Loading…
Reference in New Issue