diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java index 2f36bf849d6..69a14a6d697 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java @@ -35,6 +35,7 @@ import org.quartz.utils.DBConnectionManager; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.MetaDataAccessException; +import org.springframework.util.Assert; /** * 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 nonTransactionalDataSource; + @Override @SuppressWarnings("NullAway") // Dataflow analysis limitation @@ -98,11 +101,40 @@ public class LocalDataSourceJobStore extends JobStoreCMT { throw new SchedulerConfigException("No local DataSource found for configuration - " + "'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()); + setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName()); 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. DBConnectionManager.getInstance().addConnectionProvider( TX_DATA_SOURCE_PREFIX + getInstanceName(), @@ -110,7 +142,7 @@ public class LocalDataSourceJobStore extends JobStoreCMT { @Override public Connection getConnection() throws SQLException { // Return a transactional Connection, if any. - return DataSourceUtils.doGetConnection(dataSource); + return DataSourceUtils.doGetConnection(dataSourceToUse); } @Override 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. DBConnectionManager.getInstance().addConnectionProvider( 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 diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java index b4a17bdec04..69c19d04a6d 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java @@ -28,6 +28,8 @@ import org.jspecify.annotations.Nullable; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; +import org.quartz.core.QuartzScheduler; +import org.quartz.core.QuartzSchedulerResources; import org.quartz.impl.RemoteScheduler; import org.quartz.impl.SchedulerRepository; import org.quartz.impl.StdSchedulerFactory; @@ -165,7 +167,7 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe private @Nullable SchedulerFactory schedulerFactory; - private Class extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class; + private Class extends SchedulerFactory> schedulerFactoryClass = LocalSchedulerFactory.class; private @Nullable String schedulerName; @@ -203,6 +205,8 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe private @Nullable Scheduler scheduler; + private @Nullable LocalDataSourceJobStore jobStore; + /** * 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. - *
Default is the {@link StdSchedulerFactory} class, reading in the standard - * {@code quartz.properties} from {@code quartz.jar}. For applying custom Quartz - * properties, specify {@link #setConfigLocation "configLocation"} and/or - * {@link #setQuartzProperties "quartzProperties"} etc on this local - * {@code SchedulerFactoryBean} instance. + *
Default is a Spring-internal subclass of the {@link StdSchedulerFactory}
+ * class, reading in the standard {@code quartz.properties} from
+ * {@code quartz.jar}. For applying custom Quartz properties,
+ * specify {@link #setConfigLocation "configLocation"} and/or
+ * {@link #setQuartzProperties "quartzProperties"} etc on this
+ * local {@code SchedulerFactoryBean} instance.
* @see org.quartz.impl.StdSchedulerFactory
* @see #setConfigLocation
* @see #setQuartzProperties
@@ -508,8 +513,9 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
SchedulerFactory schedulerFactory = this.schedulerFactory;
if (schedulerFactory == null) {
- // Create local SchedulerFactory instance (typically a StdSchedulerFactory)
- schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
+ // Create local SchedulerFactory instance (typically a LocalSchedulerFactory)
+ schedulerFactory = (this.schedulerFactoryClass == LocalSchedulerFactory.class ?
+ new LocalSchedulerFactory() : BeanUtils.instantiateClass(this.schedulerFactoryClass));
if (schedulerFactory instanceof StdSchedulerFactory stdSchedulerFactory) {
initSchedulerFactory(stdSchedulerFactory);
}
@@ -778,6 +784,9 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
@Override
public void start() throws SchedulingException {
if (this.scheduler != null) {
+ if (this.jobStore != null) {
+ this.jobStore.initializeConnectionProvider();
+ }
try {
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);
+ }
+ }
+
}
diff --git a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java
index c6aaabc9456..87adeaed3e5 100644
--- a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java
+++ b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java
@@ -391,6 +391,8 @@ class QuartzSupportTests {
try (ClassPathXmlApplicationContext ctx = context("databasePersistence.xml")) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(ctx.getBean(DataSource.class));
assertThat(jdbcTemplate.queryForList("SELECT * FROM qrtz_triggers").isEmpty()).as("No triggers were persisted").isFalse();
+ ctx.stop();
+ ctx.restart();
}
}
diff --git a/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml b/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml
index 9b7b97c07c3..c9591ab1887 100644
--- a/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml
+++ b/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml
@@ -5,28 +5,28 @@
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">