Quartz reuse existing data source connection pool.

Quartz is a de facto scheduling system for java which has enterprise scale functionality and scales very well. This tutorial will explain how to reuse existing datasource using sprint boot application and quartz. Most real world application has datasource as singleton scope and wanted to share the same object with Quartz.

Step 1 – Data Source configuration

First, we have configured data source with hostname, port, username, password..etc. This DataSource will be a singleton scope and make sure it has enough connections to support your application load and quartz – usually, it will be equal to a total number of quartz worker threads.

package quartz;

import com.mysql.cj.jdbc.MysqlDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * @author CandidJava.com
 */
@Configuration
public class DataSourceFactory {
   @Bean
   public DataSource getDataSource() {
      MysqlDataSource dataSource = new MysqlDataSource();
      dataSource.setServerName("localhost");
      dataSource.setPort(3306);
      dataSource.setDatabaseName("database1");
      dataSource.setUser("user1");
      dataSource.setPassword("password1");
      return dataSource;
   }
}

 

Step 2-  Configure Connnection Provider

Here, we inject the data source from step 1 and implement ConnectionProvider interface from Quartz.

package quartz;

import org.quartz.utils.ConnectionProvider;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

import static java.util.Objects.requireNonNull;

/**
 * Implement ConnectionProvider from quartz library.
 */
public final class ConnectionProviderImpl implements ConnectionProvider {

   private DataSource dataSource;

   ConnectionProviderImpl(DataSource dataSource) {
      this.dataSource = dataSource;
   }

   @Override
   public Connection getConnection() throws SQLException {
      return dataSource.getConnection();
   }

   @Override
   public void shutdown() throws SQLException {
      // No-op
   }

   @Override
   public void initialize() throws SQLException {
      requireNonNull(dataSource, "DataSource initialization failed");
   }
}

 

Step 3 - Configure Quartz Scheduler.

Configure Scheduler system and start the service.

package quartz;

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.DirectSchedulerFactory;
import org.quartz.impl.jdbcjobstore.JobStoreTX;
import org.quartz.simpl.SimpleThreadPool;
import org.quartz.utils.DBConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * @author CandidJava.com
 * */
@Configuration
public class QuartzSchedulerFactory {

   public static final String INSTANCE_NAME = "Candid-Java-Scheduler";
   public static final String INSTANCE_ID = INSTANCE_NAME + "1"; // Use UUID if running as cluster.
   public static final String DATA_SOURCE = "sharedDataSource";
   private final DataSource dataSource;
   private Scheduler scheduler;

   @Autowired
   public QuartzSchedulerFactory(DataSource dataSource) {
      this.dataSource = dataSource;
   }

   @Bean
   public Scheduler getScheduler() throws SchedulerException {

      DBConnectionManager.getInstance().addConnectionProvider(DATA_SOURCE, new ConnectionProviderImpl(dataSource)); 
      
      // Configure jdbc store
      JobStoreTX jdbcJobStore = new JobStoreTX();
      jdbcJobStore.setInstanceName(INSTANCE_NAME);
      jdbcJobStore.setDataSource(DATA_SOURCE);
      jdbcJobStore.setClusterCheckinInterval(20000L);
      jdbcJobStore.setDbRetryInterval(15000L);
      jdbcJobStore.setIsClustered(true);

      // Create a scheduler.
      DirectSchedulerFactory.getInstance().createScheduler(INSTANCE_NAME, INSTANCE_ID,
            new SimpleThreadPool(5, Thread.NORM_PRIORITY), jdbcJobStore);
      this.scheduler = DirectSchedulerFactory.getInstance().getScheduler(INSTANCE_NAME);

      // Start the scheduler.
      scheduler.start();

      return this.scheduler;
   }


}

 

Step 4 - Expose Scheduler using Controller in spring boot style.

package quartz;

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author CandidJava.com
 * */
@Controller
public class QuartzTester {
   private final Scheduler scheduler;

   @Autowired
   public QuartzTester(Scheduler scheduler) {
      this.scheduler = scheduler;
   }

   @RequestMapping("/")
   @ResponseBody
   String getSchedulerDetails() throws SchedulerException {
      return String.format("Scheduler name %s started ?. %s", 
            scheduler.getSchedulerName(), String.valueOf(scheduler.isStarted()).toUpperCase());
   }
}

 

Step 5 - Start the application as Spring boot app.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


@Configuration
@EnableAutoConfiguration
@ComponentScan("quartz")
public class ApplicationBoot { 
   public static void main(String[] args) {
      SpringApplication.run(ApplicationBoot.class, args);
   }
}

Once you start the application(main method), and hit localhost:8080 in the browser, you will see the scheduler is running by sharing the existing data-source for database connection.

"Scheduler name Candid-Java-Scheduler started ?. TRUE"