The environment

  • Quartz cluster pattern, using database persistence tasks
  • Spring takes over Quartz, which means that Quartz depends on Spring to run

The problem

JobPersistenceException: could not store trigger when an application calls QuartzScheduler#scheduleJob to initialize a scheduled task when a cluster of four instance nodes is started unique constraint violdated. When multiple nodes are assigned a scheduleJob at the same time, the constraint is violated.

The problem code looks like this:

public class MyQuartzConfig {
    @Autowired
    private Scheduler scheduler;
    
    public void init(a) { scheduler.scheduleJob(job, trigger); }}Copy the code

As we all know, the Quartz cluster pattern borrows the database to implement a competing lock. If a node fails to acquire a lock, it waits a certain period of time to try again until the number of retries is exceeded and an exception is thrown. The above constraint violation must have occurred because the lock did not take effect, in other words the transaction did not take effect

why

Quartz JobStore

Quartz provides two implementation classes for persistence tasks, JobStoreCMT and JobStoreTX

  • JobStoreTX is simple, with a single data source that controls persistent transaction commit and rollback
  • JobStoreCMT has two data sources, a TxDataSource that contains transactions, and a NonTxDataSource that does not contain transactions

What about JobStoreCMT’s two data sources? According to the official comment,TxDataSource transactions are managed by the container and can be used in XA situations such as JTA. NonTxDataSource transactions are not managed by the container, but by Quartz itself.

CMT’s NonTxDataSource is JobStoreTX’s data source. Quartz obtains connection and sets autoCommit=false. Quartz controls the transaction itself.

JobStoreCMT’s TxDataSource is added as an extra to give users control over these transactions, giving them more flexibility.

JobStore under different scenarios will use these two kinds of data sources, is executeInLock and executeInNonManagedTXLock respectively

  • ExecuteInLock, corresponding to TxDataSource, is basicallyProvides the API for the user to invoke
  • ExecuteInNonManagedTXLock, and NonTxDataSource correspondence,It’s all an internal Quartz API, nothing to do with the user

Spring-Quartz

Things will change a bit when Spring takes over Quartz. If the Spring-provided SchedulerFactoryBean sets up a data source, it replaces Quartz’s JobStore class with LocalDataSourceJobStore.

AutoCommit =ture;}

Real reason

In our example, we call the scheduleJob method using a TxDataSource, and the persistence class LocalDataSourceJobStore defaults to autoCommit=ture. In this case, we call the LocalDataSourceJobStore with autoCommit=ture Of course, there will be problems in the text.

To solve the problem

If it doesn’t have a transaction, just add a transaction to it, and since we’re using Spring-Quartz, adding Spring’s transaction will solve the problem

public class MyQuartzConfig {
    @Autowired
    private Scheduler scheduler;
    @Transactional  // Add transaction annotations to solve the problem
    public void init(a) { scheduler.scheduleJob(job, trigger); }}Copy the code

conclusion

Quartz provides JobStoreCMT so that users can have transaction control when calling Quartz (for example, when they want multiple Instances of Quartz to succeed or fail simultaneously).

I don’t know how to avoid this kind of problem in the future. The documentation provided by Spring doesn’t seem to address the transaction issue.