From 17382fe07957bfb43bc34d22f20e63f00a3ddd6b Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 16 Jul 2025 15:21:49 +0200 Subject: [PATCH] Re-initialize Quartz ConnectionProvider on context restart Closes gh-35208 --- .../quartz/LocalDataSourceJobStore.java | 61 +++++++++++-------- .../quartz/SchedulerFactoryBean.java | 37 ++++++++--- .../scheduling/quartz/QuartzSupportTests.java | 2 + .../scheduling/quartz/databasePersistence.xml | 16 ++--- 4 files changed, 73 insertions(+), 43 deletions(-) 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 2f36bf849d..69a14a6d69 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 b4a17bdec0..69c19d04a6 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 schedulerFactoryClass = StdSchedulerFactory.class; + private Class 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 c6aaabc945..87adeaed3e 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 9b7b97c07c..c9591ab188 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"> - - + + - - + + - + - - + + - +