Scheduling (Spring Boot)
Introduction
This page is about Spring scheduling.
It emphasizes how to achieve that a schedule method only runs in one of the application nodes, using Shedlock.
ShedLock works only in environments with a shared database by declaring a proper LockProvider. It creates a table or document in the database where it stores the information about the current locks.
ShedLock supports Mongo, Redis, Hazelcast, ZooKeeper, and anything with a JDBC driver.
References
ShedLock
https://github.com/lukas-krecan/ShedLock
Guide to ShedLock with Spring
https://www.baeldung.com/shedlock-spring
The @Scheduled Annotation in Spring
https://www.baeldung.com/spring-scheduled-tasks
@Scheduled syntax
Cron sample:
/** At 02:07:27am every day (UTC) */
@Scheduled(cron = "27 07 02 * * ?", zone = "UTC")
/** At 02:07:02am every day (Europe/Madrid) */
@Scheduled(cron = "2 7 2 * * ?", zone = "Europe/Madrid")
Fixed delay, w/ initial delay, sample:
@Scheduled(fixedDelay = 24 * 60 * 60 * 1000, initialDelay = 24 * 60 * 60 * 1000)
Step by step
The following sample was tested on a Spring Boot 2.6.0 application with OpenJDK 11, using both: H2 and PostgreSQL.
POM configuration (pom.xml)
<properties>
<shedlock.version>4.44.0</shedlock.version>
</properties>
...
<!-- ShedLock: handle scheduler synchronization over multiple instances -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>${shedlock.version}</version>
</dependency>
<!-- ShedLock: LockProvider JdbcTemplate -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>${shedlock.version}</version>
</dependency>
ShedLock database table
Create a database table for ShedLock to keep information about scheduler locks.
-Using SQL:
Sample syntax compatible with PostgreSQL DBMS:
CREATE TABLE shedlock(
name VARCHAR(64) NOT NULL,
lock_until TIMESTAMP NOT NULL,
locked_at TIMESTAMP NOT NULL,
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name));
-Using Liquibase:
databaseChangeLog:
- changeSet:
id: "021"
author: thatsme
failOnError: true
comment: "Creation of table: shedlock"
changes:
- createTable:
tableName: shedlock
columns:
- column:
name: name
type: varchar(64)
constraints:
nullable: false
primaryKey: true
remarks: "Record id"
- column:
name: lock_until
type: timestamp(3)
constraints:
nullable: false
remarks: "-"
- column:
name: locked_at
type: timestamp(3)
defaultValueComputed: current_timestamp(3)
constraints:
nullable: false
remarks: "-"
- column:
name: locked_by
type: varchar(255)
constraints:
nullable: false
remarks: "-"
ShedLockConfig.java
package edu.cou.myapp;
import java.time.Clock;
import java.time.ZoneId;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import net.javacrumbs.shedlock.core.ClockProvider;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
/**
* ShedLockConfig.<br>
* <br>
* a) It configures ShedLock<br>
* <br>
* Reference:<br>
* https://github.com/lukas-krecan/ShedLock
*/
@Configuration
public class ShedLockConfig {
/**
* Configure ShedLock JDBC lock provider
*
* @param dataSource -
* @return -
*/
@Bean
LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(//
JdbcTemplateLockProvider.Configuration.builder()//
.withJdbcTemplate(new JdbcTemplate(dataSource))//
.usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2
.build());
}
}
ScheduledConfig.java
import javax.sql.DataSource;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
@EnableScheduling
@EnableSchedulerLock(defaultLockAtLeastFor = "PT4S", defaultLockAtMostFor = "PT32S")
@Configuration
public class ScheduledConfig {
@Scheduled(fixedDelay = 24 * 60 * 60 * 1000, initialDelay = 24 * 60 * 60 * 1000)
@SchedulerLock(name = "appRequestClean")
public void appRequestClean() {
//whatever
}
@Scheduled(cron = "2 7 2 * * ?", zone = "Europe/Madrid")
@SchedulerLock(name = "dailyNightProcess", lockAtLeastFor = "PT16M",
lockAtMostFor = "PT16H")
public void dailyNightProcess() {
//whatever
}
}