Skip to content

Commit 17382fe

Browse files
committed
Re-initialize Quartz ConnectionProvider on context restart
Closes gh-35208
1 parent 6f5a7ee commit 17382fe

File tree

4 files changed

+73
-43
lines changed

4 files changed

+73
-43
lines changed

spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.jdbc.datasource.DataSourceUtils;
3636
import org.springframework.jdbc.support.JdbcUtils;
3737
import org.springframework.jdbc.support.MetaDataAccessException;
38+
import org.springframework.util.Assert;
3839

3940
/**
4041
* Subclass of Quartz's {@link JobStoreCMT} class that delegates to a Spring-managed
@@ -88,6 +89,8 @@ public class LocalDataSourceJobStore extends JobStoreCMT {
8889

8990
private @Nullable DataSource dataSource;
9091

92+
private @Nullable DataSource nonTransactionalDataSource;
93+
9194

9295
@Override
9396
@SuppressWarnings("NullAway") // Dataflow analysis limitation
@@ -98,19 +101,48 @@ public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) t
98101
throw new SchedulerConfigException("No local DataSource found for configuration - " +
99102
"'dataSource' property must be set on SchedulerFactoryBean");
100103
}
104+
// Non-transactional DataSource is optional: fall back to default
105+
// DataSource if not explicitly specified.
106+
this.nonTransactionalDataSource = SchedulerFactoryBean.getConfigTimeNonTransactionalDataSource();
101107

102-
// Configure transactional connection settings for Quartz.
108+
// Configure connection settings for Quartz.
103109
setDataSource(TX_DATA_SOURCE_PREFIX + getInstanceName());
110+
setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName());
104111
setDontSetAutoCommitFalse(true);
105112

113+
initializeConnectionProvider();
114+
115+
// No, if HSQL is the platform, we really don't want to use locks...
116+
try {
117+
String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource,
118+
DatabaseMetaData::getDatabaseProductName);
119+
productName = JdbcUtils.commonDatabaseName(productName);
120+
if (productName != null && productName.toLowerCase(Locale.ROOT).contains("hsql")) {
121+
setUseDBLocks(false);
122+
setLockHandler(new SimpleSemaphore());
123+
}
124+
}
125+
catch (MetaDataAccessException ex) {
126+
logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken.");
127+
}
128+
129+
super.initialize(loadHelper, signaler);
130+
}
131+
132+
void initializeConnectionProvider() {
133+
final DataSource dataSourceToUse = this.dataSource;
134+
Assert.state(dataSourceToUse != null, "DataSource must not be null");
135+
final DataSource nonTxDataSourceToUse =
136+
(this.nonTransactionalDataSource != null ? this.nonTransactionalDataSource : dataSourceToUse);
137+
106138
// Register transactional ConnectionProvider for Quartz.
107139
DBConnectionManager.getInstance().addConnectionProvider(
108140
TX_DATA_SOURCE_PREFIX + getInstanceName(),
109141
new ConnectionProvider() {
110142
@Override
111143
public Connection getConnection() throws SQLException {
112144
// Return a transactional Connection, if any.
113-
return DataSourceUtils.doGetConnection(dataSource);
145+
return DataSourceUtils.doGetConnection(dataSourceToUse);
114146
}
115147
@Override
116148
public void shutdown() {
@@ -123,14 +155,6 @@ public void initialize() {
123155
}
124156
);
125157

126-
// Non-transactional DataSource is optional: fall back to default
127-
// DataSource if not explicitly specified.
128-
DataSource nonTxDataSource = SchedulerFactoryBean.getConfigTimeNonTransactionalDataSource();
129-
final DataSource nonTxDataSourceToUse = (nonTxDataSource != null ? nonTxDataSource : this.dataSource);
130-
131-
// Configure non-transactional connection settings for Quartz.
132-
setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName());
133-
134158
// Register non-transactional ConnectionProvider for Quartz.
135159
DBConnectionManager.getInstance().addConnectionProvider(
136160
NON_TX_DATA_SOURCE_PREFIX + getInstanceName(),
@@ -150,23 +174,6 @@ public void initialize() {
150174
}
151175
}
152176
);
153-
154-
// No, if HSQL is the platform, we really don't want to use locks...
155-
try {
156-
String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource,
157-
DatabaseMetaData::getDatabaseProductName);
158-
productName = JdbcUtils.commonDatabaseName(productName);
159-
if (productName != null && productName.toLowerCase(Locale.ROOT).contains("hsql")) {
160-
setUseDBLocks(false);
161-
setLockHandler(new SimpleSemaphore());
162-
}
163-
}
164-
catch (MetaDataAccessException ex) {
165-
logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken.");
166-
}
167-
168-
super.initialize(loadHelper, signaler);
169-
170177
}
171178

172179
@Override

spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.quartz.Scheduler;
2929
import org.quartz.SchedulerException;
3030
import org.quartz.SchedulerFactory;
31+
import org.quartz.core.QuartzScheduler;
32+
import org.quartz.core.QuartzSchedulerResources;
3133
import org.quartz.impl.RemoteScheduler;
3234
import org.quartz.impl.SchedulerRepository;
3335
import org.quartz.impl.StdSchedulerFactory;
@@ -165,7 +167,7 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
165167

166168
private @Nullable SchedulerFactory schedulerFactory;
167169

168-
private Class<? extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class;
170+
private Class<? extends SchedulerFactory> schedulerFactoryClass = LocalSchedulerFactory.class;
169171

170172
private @Nullable String schedulerName;
171173

@@ -203,6 +205,8 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
203205

204206
private @Nullable Scheduler scheduler;
205207

208+
private @Nullable LocalDataSourceJobStore jobStore;
209+
206210

207211
/**
208212
* Set an external Quartz {@link SchedulerFactory} instance to use.
@@ -223,11 +227,12 @@ public void setSchedulerFactory(SchedulerFactory schedulerFactory) {
223227

224228
/**
225229
* Set the Quartz {@link SchedulerFactory} implementation to use.
226-
* <p>Default is the {@link StdSchedulerFactory} class, reading in the standard
227-
* {@code quartz.properties} from {@code quartz.jar}. For applying custom Quartz
228-
* properties, specify {@link #setConfigLocation "configLocation"} and/or
229-
* {@link #setQuartzProperties "quartzProperties"} etc on this local
230-
* {@code SchedulerFactoryBean} instance.
230+
* <p>Default is a Spring-internal subclass of the {@link StdSchedulerFactory}
231+
* class, reading in the standard {@code quartz.properties} from
232+
* {@code quartz.jar}. For applying custom Quartz properties,
233+
* specify {@link #setConfigLocation "configLocation"} and/or
234+
* {@link #setQuartzProperties "quartzProperties"} etc on this
235+
* local {@code SchedulerFactoryBean} instance.
231236
* @see org.quartz.impl.StdSchedulerFactory
232237
* @see #setConfigLocation
233238
* @see #setQuartzProperties
@@ -508,8 +513,9 @@ public void afterPropertiesSet() throws Exception {
508513
private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
509514
SchedulerFactory schedulerFactory = this.schedulerFactory;
510515
if (schedulerFactory == null) {
511-
// Create local SchedulerFactory instance (typically a StdSchedulerFactory)
512-
schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
516+
// Create local SchedulerFactory instance (typically a LocalSchedulerFactory)
517+
schedulerFactory = (this.schedulerFactoryClass == LocalSchedulerFactory.class ?
518+
new LocalSchedulerFactory() : BeanUtils.instantiateClass(this.schedulerFactoryClass));
513519
if (schedulerFactory instanceof StdSchedulerFactory stdSchedulerFactory) {
514520
initSchedulerFactory(stdSchedulerFactory);
515521
}
@@ -778,6 +784,9 @@ public boolean isSingleton() {
778784
@Override
779785
public void start() throws SchedulingException {
780786
if (this.scheduler != null) {
787+
if (this.jobStore != null) {
788+
this.jobStore.initializeConnectionProvider();
789+
}
781790
try {
782791
startScheduler(this.scheduler, this.startupDelay);
783792
}
@@ -829,4 +838,16 @@ public void destroy() throws SchedulerException {
829838
}
830839
}
831840

841+
842+
private class LocalSchedulerFactory extends StdSchedulerFactory {
843+
844+
@Override
845+
protected Scheduler instantiate(QuartzSchedulerResources rsrcs, QuartzScheduler qs) {
846+
if (rsrcs.getJobStore() instanceof LocalDataSourceJobStore ldsjs) {
847+
SchedulerFactoryBean.this.jobStore = ldsjs;
848+
}
849+
return super.instantiate(rsrcs, qs);
850+
}
851+
}
852+
832853
}

spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,8 @@ void schedulerWithHsqlDataSource() {
391391
try (ClassPathXmlApplicationContext ctx = context("databasePersistence.xml")) {
392392
JdbcTemplate jdbcTemplate = new JdbcTemplate(ctx.getBean(DataSource.class));
393393
assertThat(jdbcTemplate.queryForList("SELECT * FROM qrtz_triggers").isEmpty()).as("No triggers were persisted").isFalse();
394+
ctx.stop();
395+
ctx.restart();
394396
}
395397
}
396398

spring-context-support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,28 @@
55
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
66

77
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
8-
<property name="triggers" ref="trigger" />
9-
<property name="dataSource" ref="dataSource" />
8+
<property name="triggers" ref="trigger"/>
9+
<property name="dataSource" ref="dataSource"/>
1010
</bean>
1111

1212
<bean id="trigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
13-
<property name="repeatInterval" value="1000" />
14-
<property name="repeatCount" value="1" />
13+
<property name="repeatInterval" value="1000"/>
14+
<property name="repeatCount" value="1"/>
1515
<property name="jobDetail">
1616
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
1717
<property name="jobDataAsMap">
1818
<map>
19-
<entry key="param" value="10" />
19+
<entry key="param" value="10"/>
2020
</map>
2121
</property>
22-
<property name="jobClass" value="org.springframework.scheduling.quartz.QuartzSupportTests$DummyJob" />
23-
<property name="durability" value="true" />
22+
<property name="jobClass" value="org.springframework.scheduling.quartz.QuartzSupportTests$DummyJob"/>
23+
<property name="durability" value="true"/>
2424
</bean>
2525
</property>
2626
</bean>
2727

2828
<jdbc:embedded-database id="dataSource" type="HSQL">
29-
<jdbc:script location="org/springframework/scheduling/quartz/quartz-hsql.sql" />
29+
<jdbc:script location="org/springframework/scheduling/quartz/quartz-hsql.sql"/>
3030
</jdbc:embedded-database>
3131

3232
</beans>

0 commit comments

Comments
 (0)