Spring Boot using Quartz in mode Cluster
By: Rafael Faita
Tags: Quartz, Job Scheduling, Spring Boot, Web Development, Architecture

Spring Boot configuration for Quartz

To start using Quartz in your project we must add to you pom.xml the following libs:

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-quartz</artifactId>

</dependency>



The library is the Quartz it-self for Spring Boot and it brings auto-configuration and new classes to our project and allows us to create scheduled jobs.

With the libraries configured, now we can start creating your first job. There are other ways to create jobs, but I chose to create a new job extending the class QuartzJobBean.

As you can see, I created a simple job, which one only has a service. When the job triggers the method execute internally will be called.

The service is very simple(below) and your implementation of the method run has a 1/3 chance to throw an exception. I created this logic to validate the scheduled jobs with errors, in this case, I wanted to check whether the job execution will continue when a new exception is thrown. Below is the source code:

With the job created, we must create the configuration class.

The configuration class JobConfig declare two new beans:

the first one is the JobDetail : job detail specify the job we want to execute when a new trigger happens, in your case, we created the JobDetail using your job class ATestJob. The identity is the identifier of the JobDetail and will be the “name” of your job. The use of storeDurably tells Quartz to persist this job when is impossible.

: job detail specify the job we want to execute when a new trigger happens, in your case, we created the JobDetail using your job class The is the identifier of the JobDetail and will be the “name” of your job. The use of tells Quartz to persist this job when is impossible. the second one is the Trigger: the trigger is responsible for the configuration of the execution of the job, in your case, we configured your job(ATestJob) to be executed every two seconds. Again the identity is the identifier but in this case the identifier of your Trigger.

Now we can start your application, and probably you will see in your console log, same messages like this:

2020-09-18 16:34:26.026 INFO 8 --- [eduler_Worker-4] com.example.quartz.job.ATestJob : ATestJob Running

2020-09-18 16:34:26.026 INFO 8 --- [eduler_Worker-4] com.example.quartz.service.TestService : Running job on supervisor, job id ATestJob

2020-09-18 16:34:26.026 INFO 8 --- [eduler_Worker-4] com.example.quartz.service.TestService : Completed job on supervisor, job id ATestJob

But if you start a new instance of the same implementation, a new console log of the new execution will show the same messages.

Making the magic happen

To configure the Quartz for work correct with cluster, we have to return to our pom.xml and add two more libs:

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-jdbc</artifactId>

</dependency>

These libraries aren’t directly linked with Quartz but are the way to persist the jobs in the same place, where all the orchestration to parallel executions happens.

With the libraries configured, we can configure your application.yml:

I added some comments to the file, but I want to call your attention to the following configurations:

datasource: it is the place where we configure your database, where all the jobs, triggers, executions, etc of the Quartz will be persisted.

jobStore.tablePrefix: use different tablePrefix to different groups of jobs/services, if you use the same tablePrefix to all of your jobs, there is a chance your Quartz doesn’t work correctly(Important Tip).

jobStore.isClustered: where the cluster mode is enabled.

You probably note the profile docker-compose, I created this profile to support the execution of all the environment with a docker-compose.yml file, I will show below how I made this.

The docker-compose has two main sections:

the MySQL service: I chose to create a custom image of MySQL, which one starts executing a SQL script to create all the infrastructure in the database. You can check the Dockerfile and SQL script on links.

service: I chose to create a custom image of MySQL, which one starts executing a SQL script to create all the infrastructure in the database. You can check the Dockerfile and SQL script on links. the qtest and qtest2 services: These are the Spring Boot services running Quartz, the Dockerfile and auxiliary scripts are here.

If we do all the things correctly, now we can compile your code:

mvn clean instal

And now start up your environment:

docker-compose up --build

Wait some seconds, and if everything starts correctly, we probably will see the following log on the console:

qtest2_1 | 2020-09-18 16:34:24.021 INFO 8 --- [eduler_Worker-3] com.example.quartz.service.TestService : Running job on supervisor, job id ATestJob

qtest2_1 | 2020-09-18 16:34:24.021 INFO 8 --- [eduler_Worker-3] com.example.quartz.service.TestService : Completed job on supervisor, job id ATestJob

qtest2_1 | 2020-09-18 16:34:26.026 INFO 8 --- [eduler_Worker-4] com.example.quartz.job.ATestJob : ATestJob Running

qtest2_1 | 2020-09-18 16:34:26.026 INFO 8 --- [eduler_Worker-4] com.example.quartz.service.TestService : Running job on supervisor, job id ATestJob

qtest2_1 | 2020-09-18 16:34:26.026 INFO 8 --- [eduler_Worker-4] com.example.quartz.service.TestService : Completed job on supervisor, job id ATestJob

qtest2_1 | 2020-09-18 16:34:28.044 INFO 8 --- [eduler_Worker-5] com.example.quartz.job.ATestJob : ATestJob Running

In your case, the qtest2_1 container assumes the execution of your job, but if we open a new terminal and execute a kill command on this container:

docker rm -f quartz_qtest2_1

After some seconds we will start seeing a new log on the console:

qtest1_1 | 2020-09-18 16:36:42.046 INFO 7 --- [duler_Worker-10] com.example.quartz.service.TestService : Running job on supervisor, job id ATestJob

qtest1_1 | 2020-09-18 16:36:42.046 INFO 7 --- [duler_Worker-10] com.example.quartz.service.TestService : Completed job on supervisor, job id ATestJob

qtest1_1 | 2020-09-18 16:36:44.032 INFO 7 --- [eduler_Worker-1] com.example.quartz.job.ATestJob : ATestJob Running

qtest1_1 | 2020-09-18 16:36:44.033 INFO 7 --- [eduler_Worker-1] com.example.quartz.service.TestService : Running job on supervisor, job id ATestJob

qtest1_1 | 2020-09-18 16:36:44.033 INFO 7 --- [eduler_Worker-1] com.example.quartz.service.TestService : Completed job on supervisor, job id ATestJob

As you can see, the qtest1_1 container assumes the execution of the job without problems.

To clean up all the environment, hit Ctrl+C, and execute the following command:

docker-compose rm

I hope I helped, see you soon.