Job Schedule Library in a Spring Boot Project - java

I am looking for a scheduler library which can perform a simple task of invoking other REST APIs at a specific time of a day. Please advise on which is a good library to use in a Spring Boot project. I am basically looking for something which logs job config information to DB automatically and has a UI interface to check the status of jobs (preferable but not mandatory).
I did come across this but as I have no prior experience with any of them but Quartz so I am not able to make the call: http://blog.dreamcss.com/tools/java-based-job-scheduler/
Note: I did use Quartz in my previous project but I ran into multiple issue with it as it seems to have issues with not logging job related info to DB. Specifically it would not log proper info into DB about last run time and whether the last job run completed successfully or not. Also, I have seen that the Jobs in Quartz gets blocked if the previous job takes longer to complete.

In spring boot you have an embedded simple engine for scheduling.
Use #Scheduled annotation in your #Component for example.
And don't remember to enable scheduling by using #EnableScheduling annotation.
You can read more about this topic in this article spring.io link

Using a Trigger you can calculate the next execution time on the fly.
Something like this should do the trick (adapted from the Javadoc for #EnableScheduling):
#Configuration
#EnableScheduling
public class MyAppConfig implements SchedulingConfigurer {
#Autowired
Environment env;
#Bean
public MyBean myBean() {
return new MyBean();
}
#Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
new Runnable() {
#Override public void run() {
myBean().getSchedule();
}
},
new Trigger() {
#Override public Date nextExecutionTime(TriggerContext triggerContext) {
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = triggerContext.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.MILLISECOND, env.getProperty("myRate", Integer.class)); //you can get the value from wherever you want
return nextExecutionTime.getTime();
}
}
);
}
}

Related

Spring TaskScheduler does not schedule task immediatelly

I want to execute the same task couple of times but it seems that every next invocation of my code does not execute the task immediately, for example it executes after one minute.
Since user has to schedule tasks manually I use ScheduledTaskRegistrar.TaskScheduler.
taskRegistrar.getScheduler().schedule(myTask, new Date());
What could be the reason? User clicked schedule button twice on my fronted application and backend invoked the above schedule method twice as expected. First execution of my task was immediate, second run after two minutes.
UPDATE: taskregistrar config, maybe I didn't configure it at all. my tasks are added as cron tasks on application deployment. But they also must be runnable manually if user wants to trigger it. Below is more or less the whole logic:
#Configuration
#EnableAsync
#EnableScheduling
#Component
#Slf4j
#Generated
#Getter
public class ScheduleTaskService implements SchedulingConfigurer {
#Autowired
private List< MyTask> taskList;
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
this.taskRegistrar = taskRegistrar;
taskList.stream().filter(MyTask::isOn).forEach(this::addTaskToScheduler);
}
public void addTaskToScheduler(GwoTask task) {
taskRegistrar.addCronTask(task, task.getCronExpression());
}
public void scheduleImmediateInvocation(MyTask myTask) {
taskRegistrar.getScheduler().schedule(myTask, new Date());
}
}
By referring to the source code of ScheduledTaskRegistrar,
protected void scheduleTasks() {
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
...
If we do not set taskScheduler, Executors.newSingleThreadScheduledExecutor() is used by default. Hence new task will be blocked by processing task.
For your use case in scheduleImmediateInvocation, I recommend to use another thread pool(Probably from Executors) instead as:
It isn't actually a schedule job.
More control on pool size is needed to suit your workload
If you just want to make ScheduledTaskRegistrar execute more concurrently, configure it as:
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// set the desired core pool size
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
this.taskRegistrar = taskRegistrar;
// ...
}

Spring boot: #Scheduler that updates cron espression without restarting

I Need to schedule a task on Spring Boot that reads a cron espression from the database. I did this using the #Scheduled annotation and reading a property inside a database, but my client is asking to be able to update the cron expression in the database and having it affect the scheduled without restarting the application. I know this isnt possible with the #Scheduled annotation, but would It be possible to schedule another task that extracts the cron expression every hour, and then feed the updated expression to the actual scheduled that executes the task? Basically updating the variable that Is Fed to the second scheduled. If this isnt possible, do you know any alternative ways to achieve this without using the #Scheduled annotation? Thank you.
You could try doing this using your own Runnable and a ScheduledExecutorService Which starts a thread to do what you are asking once every hour.
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
public void chronJob Runner() {
final Runnable chronJobWorker = new Runnable() {
public void run() { //Request logic }
};
scheduler.scheduleAtFixedRate(beeper, 1, 60, TimeUnit.MINUTES);
Not sure if this is the best way of doing it, but is certainly one possible way of completing this task at a scheduled rate.
Solved this using SchedulingConfigurer, here's a sample:
#Configuration
#EnableScheduling
public class BatchConfig implements SchedulingConfigurer {
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(new Runnable() {
#Override
public void run() {
//run your code here
}
}, new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
//extract cron from database
CronTrigger trigger = new CronTrigger(new CronTrigger(//insert cron that you got from database));
return trigger.nextExecutionTime(triggerContext);
}
});
}
}

Why Spring use ForkPoolJoin instead of ThreadPoolTaskExecutor with #Async?

For my studies, I'm working on a Spring Boot REST API. I'm supposed to reduce the execution time of the code when it received a request. So, I thought that make the code asynchronous would be a good idea. But, unfortunately, I face some problems with Spring for this, and despite the few hours of research online to find a solution, I didn't find anything. Let me explain :
To optimize my code, I decided to use #Async Spring Annotation. For this, I created an AsyncConfiguration class who looks like this :
#Configuration
#EnableAsync
public class AsyncConfiguration {
#Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("AsynchThread-");
executor.initialize();
return executor;
}
}
Usually, the asyncExecutor() method returns the Executor class or sub-class that must be used by Spring for async calls. In this case, it's a ThreadPoolTaskExecutor. And the majority of my code is annotated with #Async to use my asyncExecutor, like this :
#Async("asyncExecutor")
public CompletableFuture<User> calculateRewards(User user) {
return CompletableFuture.supplyAsync(() -> {
logger.info("Calculating Reward for user : " + user.getUserName());
List<VisitedLocation> userLocations = user.getVisitedLocations();
List<Attraction> attractions = gps.getAllAttraction();
for(VisitedLocation visitedLocation : userLocations) {
for(Attraction attraction : attractions) {
if(user.getUserRewards().stream().filter(r -> r.attraction.attractionName.equals(attraction.attractionName)).count() == 0) {
if(nearAttraction(visitedLocation, attraction)) {
user.addUserReward(new UserReward(visitedLocation, attraction, reward.getAttractionRewardPoints(attraction.attractionId, user.getUserId())));
}
}
}
}
return user;
});
}
But, here's the point : when I run the code, Spring DON'T use my asyncExecutor() bean. How do I know that ? First, when I call an annotated method, in the terminal, here's what I see :
2022-10-27 00:26:24.688 INFO 41436 --- [onPool-worker-4] c.T.T.service.TourGuideMainService : Calculating Reward for user : internalUser919
The "[onPool-worker-4]" is the Thread name, or at least the end of the Thread name. But it's not supposed to be named like that. If you look at my asyncExecutor() method above, you can see that there is a executor.setThreadNamePrefix("AsynchThread-");. If the code was working as intended, the Thread should be called "AsynchThread-4", but It's not.
Secondly, I decided to run my code in Debug mode, and I went into the Debug menu of VS Code, and I dicovered two things :
1 - When I run my stress test who make 1000 calls of calculateRewards() simultaneously, there is only 11 Threads created. Considering the execution time of the calculateRewards() method AND the fact that the Executor have its maxPoolSize by default (i.e. Integer.MAX_VALUE), there should be more than 11 Threads ;
2 - The entire name of the Threads when they are created is "[ForkJoinPool.commonPool-worker-4]" ;
It seems that Spring is using the ForkJoinPool class to create Threads, and it don't ever consider the configuration of my Executor. I have no idea of why it's doing that, I never used ForkJoinPool at all, and like I said, I didn't find anything when searching about that online.
So, why Spring is using ForkJoinPool instead of ThreadPoolTaskExecutor for async methods in my code ? And most important : how can I address that ?
(I hope that it's understandable...)
EDIT 1 : During some random testing, I found that, if Spring seems to use some ForkJoinPool Threads to execute my code, it create a "AsynchThread" anyway but don't use it. That's even more confusing... -_-"
The problem here is that you are using CompletableFuture.supplyAsync to produce your CompletableFuture.
This method will fork off a task running in the ForkJoinPool.commonPool() and execute your Supplier in that task.
Since spring will supply the executor and run your entire method asynchronously, you shouldn't need to use the supplyAsync function with a lambda. Instead your async method should look something like this (inside of your service):
#Service
public class MyService {
...
#Async("asyncExecutor")
public CompletableFuture<User> calculateRewards(User user) {
logger.info("Calculating Reward for user : " + user.getUserName());
List<VisitedLocation> userLocations = user.getVisitedLocations();
List<Attraction> attractions = gps.getAllAttraction();
for(VisitedLocation visitedLocation : userLocations) {
for(Attraction attraction : attractions) {
if(user.getUserRewards().stream().filter(r -> r.attraction.attractionName.equals(attraction.attractionName)).count() == 0) {
if(nearAttraction(visitedLocation, attraction)) {
user.addUserReward(new UserReward(visitedLocation, attraction, reward.getAttractionRewardPoints(attraction.attractionId, user.getUserId())));
}
}
}
}
return CompletableFuture.completedFuture(user);
}
}
If you autowire your service into a CommandLineRunner that executes your method, you can use this runner to see that spring will handle executing your method asynchronously using the Executor you have defined in your configuration.
For example:
#Component
public class Runner implements CommandLineRunner {
private final MyService service;
public Runner(MyService service) {
this.service = service;
}
#Override
public void run(String... args) throws Exception {
CompletableFuture<User> user1 = service.calculateRewards(new User("user1"));
CompletableFuture<User> user2 = service.calculateRewards(new User("user2"));
CompletableFuture<User> user3 = service.calculateRewards(new User("user3"));
CompletableFuture.allOf(user1,user2,user3).join();
}
}

how to run scheduler just after inserting data into database in spring boot?

I am new to spring boot and I have a requirement in which I have to run scheduler only if new data is inserted into table. Thanks for any help
Hibernate has an interceptor mecanism that allows you to get notified, at specific times, when database events occurs.
Such events are creation/deletion/flush of the session. As you get access to the objects being subject to the given event, you have a mean to fire a process when a given object of a given class (which you can easily map to a table in your schema) is modified.
The javadoc can be found here :
https://docs.jboss.org/hibernate/orm/4.0/manual/en-US/html/events.html
You can use the interceptor mecanism along with Java's ScheduledExecutorService to schedule a task when hibernate intercepts the save operation. You can create your business logic under that interceptor.
Scheduling is not enabled by default. Before adding any scheduled jobs we need to enable scheduling explicitly by adding the #enableScheduling annotation.
Now with the help of the ScheduledTaskRegistrar#addTriggerTask method, we can add a Runnable task and a Trigger implementation to recalculate the next execution time after the end of each execution.
#EnableScheduling
public class DynamicSchedulingConfig implements SchedulingConfigurer {
#Autowired
private TickService tickService;
#Bean
public Executor taskExecutor() {
return Executors.newSingleThreadScheduledExecutor();
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
new Runnable() {
#Override
public void run() {
tickService.tick();
}
},
new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext context) {
Optional<Date> lastCompletionTime =
Optional.ofNullable(context.lastCompletionTime());
Instant nextExecutionTime =
lastCompletionTime.orElseGet(Date::new).toInstant()
.plusMillis(tickService.getDelay());
return Date.from(nextExecutionTime);
}
}
)
}
}

Schedule task for specific time from spring 4 web mvc

I am building a Spring 4 Rest API for a trade automation site.
An http request will contain some info along with a date-time. After inserting these info into database (using hibernate), I need to dynamically create a new cron job which will access these db info and do something. The cron job must be executed at the time specified above.
So there wont be a fixed cron expression, also the cron task must access my DAO layer annoted with #Repository.
Even after referring a lot of post in stack and other blog, which tells about #Scheduled, Spring-Quartz integration, I couldn't find out a solution for my specific need.
Java/Annotation configuration is preferred.
Please help.
Thanks
You can use Trigger and TaskScheduler interfaces. Example below. To cancel job in the future it will be needed to store ScheduledFuture instance.
#Configuration
public class AppConfiguration {
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
}
#Controller
public class TriggerService {
#Autowired
private TaskScheduler scheduler;
#Autowired
private DAOService db;
private ScheduledFuture job;
#GetMapping("/task1")
#ResponseBody
public void triggerMyTask(#RequestParam String cronExpression) {
Runnable runnable = new Runnable() {
#Override
public void run() {
log.info(new Date());
/**
* here You can do what You want with db
* using some DAOService
*/
db.count();
}
};
/**
* cancel current task if You need
*/
if(job != null) {
job.cancel(true);
}
CronTrigger trigger = new CronTrigger(cronExpression);
job = scheduler.schedule(runnable, trigger);
}
}
You can pass cron expression for example like that:
http://localhost:8080/task1?cronExpression=0/5%20*%20*%20*%20*%20*
I think you may use something like this: https://ha-jdbc.github.io/apidocs/net/sf/hajdbc/util/concurrent/cron/CronThreadPoolExecutor.html

Categories

Resources