Schedule multiple tasks in Spring - spring

I have 2 Tasks which implements (my) PeriodicTask which implements Runnable.
There is a code which works fine. But I expects plenty of Tasks in future and want to avoid registering each of them in TaskScheduler.
Is it possible TaskScheduler find all classes (which implement PeriodicTask) and schedule them.
There is also a problem how to set schedule params (let them be static final for beginning).
#Component
public class TaskScheduler {
private final Task1 task1;
private final Task2 test2;
public TaskScheduler(Task1 task1, Task2 test2) {
this.task1 = task1;
this.test2 = test2;
}
#Scheduled(fixedDelay = 60 * 1000, initialDelay = 1000)
public void scheduleTask1() {task1.run();}
#Scheduled(fixedDelay = 3600 * 1000, initialDelay = 5 * 1000)
public void scheduleTask2() {test2.run();}
}

You can implement it without #Scheduled annotation:
#Component
public class TaskSchedulerComponent {
#Autowired
private ThreadPoolTaskScheduler taskScheduler;
public void schedulePeriodicTask(Runnable task, long delay, long period){
PeriodicTrigger periodicTrigger
= new PeriodicTrigger(period, TimeUnit.MICROSECONDS);
periodicTrigger.setFixedRate(true);
periodicTrigger.setInitialDelay(delay);
taskScheduler.schedule(task,periodicTrigger);
}
}
Then in your main method you register all tasks, which should be executed periodically, with given delay and time period.
Note:
To make #Scheduled to use parameters, you can use values from property file like this:
#Scheduled(fixedDelayString = "${fixed.delay}",initialDelay = ${init.delay})

Related

Spring: endpoint to start a scheduled task

I have a scheduled task that works perfectly, like this:
#Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should execute on weekdays only
}
I want to create a REST endpoint that would start this task out of it's normal schedule.
How would I programatically fire-and-forget this task?
You could do something really simple.
Your schedule:
#Component
#RequiredArgsConstructor
public class MySchedule {
private final MyClassThatHasTheProcessing process;
#Scheduled(cron = "*/5 * * * * MON-FRI")
public void doSomething() {
// the actual process is made by the method doHeavyProcessing
process.doHeavyProcessing();
}
}
Your Controller
#RestController
#RequestMapping(path = "/task")
#RequiredArgsConstructor
public class MyController {
private final MyClassThatHasTheProcessing process;
// the executor used to asynchronously execute the task
private final ExecutorService executor = Executors.newSingleThreadExecutor();
#PostMapping
public ResponseEntity<Object> handleRequestOfStartTask() {
// send the Runnable (implemented using lambda) for the ExecutorService to do the async execution
executor.execute(() - >{
process.doHeavyProcessing();
});
// the return happens before process.doHeavyProcessing() is completed.
return ResponseEntity.accepted().build();
}
}
This will keep your scheduled task working as well as being able do trigger the task on demand by hitting your endpoint.
The HTTP 202 Accepted will be returned and the actual thread released, while the ExecutorService will delegate the process.doHeavyProcessing execution to another thread, which means that it will run in a 'fire and forget' style, because the thread that is serving the request will return even before the other task is finally terminated.
If you don't know what is an ExecutorService, this may help.
This can be done by writing something like below
#Controller
MyController {
#Autowired
MyService myService;
#GetMapping(value = "/fire")
#ResponseStatus(HttpStatus.OK)
public String fire() {
myService.fire();
return "Done!!";
}
}
MyService {
#Async
#Scheduled(cron="*/5 * * * * MON-FRI")
public void fire(){
// your logic here
}
}

How to configure spring batch job to run parallelly without considering the other set of jobs?

i am working in the spring-batch where i am having one situation
we have 2 set of schedulers
public class SchedulerA {
#Autowired
private Job a;
#Autowired
private Job b;
#Autowired
private Job c;
#Autowired
private Job d;
#Autowired
private SpringBatchJobHandler springBatchJobHandler;
#Autowired
private JobHandler jobHandler;
private List<String> jobName = new ArrayList<>();
#Bean
public void SchedulerLoad() {
jobName.add(a.getName());
jobName.add(b.getName());
jobName.add(c.getName());
jobName.add(d.getName());
}
#Scheduled(fixedDelay = 300000)
private void jobScheduler() throws Exception {
for (String job : jobName) {
if (!jobHandler.isJobForceStopped()) {
springBatchJobHandler.runJob(job);
}
}
}
and this is the second set of scheduler
public class SchedulerB {
#Autowired
private Job q;
#Autowired
private Job w;
#Autowired
private Job e;
#Autowired
private Job r;
#Autowired
private SpringBatchJobHandler springBatchJobHandler;
#Autowired
private JobHandler jobHandler;
private List<String> jobName = new ArrayList<>();
#Bean
public void SchedulerLoad() {
jobName.add(q.getName());
jobName.add(w.getName());
jobName.add(e.getName());
jobName.add(r.getName());
}
#Scheduled(fixedDelay = 300000)
private void jobScheduler() throws Exception {
for (String job : jobName) {
if (!jobHandler.isJobForceStopped()) {
springBatchJobHandler.runJob(job);
}
}
}
Now what we are trying is jobs within the each scheduler will run in sequential mode but both scheduler class will parallely.
Ex: Both schedulerA and schedulerB has to run at same time parallely, But the jobs within the respective classes has to be run in sequential mode only.
is that possible to achieve the above scenario?
i know above question will be so much confusing but we dont have any other choice..!!
please share your feedback for this situation..
There are two TaskExecutors involved in your scenario:
The one used by Spring Batch to run jobs when launched via a JobLauncher. By default, this uses a SyncTaskExecutor which runs submitted jobs in the calling thread. If you use the default JobLauncher in your SpringBatchJobHandler, then your jobs will be run in sequence since you submit them in a for loop.
The one used by Spring Boot to run scheduled tasks. By default, this one has a pool size of 1, see Spring Boot documentation: The thread pool uses one thread by default and those settings can be fine-tuned using the spring.task.scheduling namespace. This means if you schedule two tasks to run at the same time as in your use case, they will be run in sequence by the same thread.
Now If you want to run those scheduled tasks in parallels using different threads, you need to increase the pool size of the TaskExecutor configured by Spring Boot to 2 or more. You can do that by setting the following property:
spring.task.scheduling.pool.size=2

Tests fail with #Scheduled Task: JdbcSQLSyntaxErrorException Table "USER_ACCOUNT_CREATED_EVENT" not found

Summary & first problem
I am trying to test my user registration mechanism. When a new user account is created via my REST API, a UserAccountCreatedEvent is stored in the database. A scheduled task checks the database every 5 seconds for new UserAccountCreatedEvents and if one is present, sends an email to the registered user. When running my tests I encounter the problem that the table for the UserAccountCreatedEvent can't be found (see exception below). I used to send the email in a blocking manner in the service method, but I recently switched to this async approach. All my tests worked perfectly for the blocking approach and the only thing I changed for the async approach is to include Awaitility in the test.
2019-04-23 11:24:51.605 ERROR 7968 --- [taskScheduler-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [select useraccoun0_.id as id1_0_, useraccoun0_.completed_at as complete2_0_, useraccoun0_.created_at as created_3_0_, useraccoun0_.in_process_since as in_proce4_0_, useraccoun0_.status as status5_0_, useraccoun0_.user_id as user_id1_35_ from user_account_created_event useraccoun0_ where useraccoun0_.status=? order by useraccoun0_.created_at asc limit ?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException:
Table "USER_ACCOUNT_CREATED_EVENT" not found; SQL statement:
select useraccoun0_.id as id1_0_, useraccoun0_.completed_at as complete2_0_, useraccoun0_.created_at as created_3_0_, useraccoun0_.in_process_since as in_proce4_0_, useraccoun0_.status as status5_0_, useraccoun0_.user_id as user_id1_35_ from user_account_created_event useraccoun0_ where useraccoun0_.status=? order by useraccoun0_.created_at asc limit ? [42102-199]
Full stack trace
Second problem
As if that were not enough, the tests behave completely different when running them in debug mode. When I set a breakpoint in the method that is called by the method which is annotated with #Scheduled, it is invoked several times althogh #Scheduled is configured with a fixedDelayString (fixed delay) of 5000ms. Thanks to logging I can even see that several mails were sent. Still, my test SMTP sever (GreenMail) does not receive any emails. How is this even possible? I've intentionally set the transaction isolation to Isolation.SERIALIZABLE so that it should be impossible (as far as I understand transaction isolation) that two scheduled methods access the same Event from the database.
Third problem
To cap it all, when I rerun the failed tests, THEY WORK. But, there are different exceptions on the console (see below). But still, the app starts and the tests finish successfully. There are different test results depending on if I run all tests vs. only the class vs. only the method vs. rerun failed tests. I don't understand how such an indeterministic behaviour can be possible.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: Failed to scan classpath for unlisted entity classes
Caused by: java.nio.channels.ClosedByInterruptException: null
Full stack trace
My code
Test class (UserRegistrationTest)
#ActiveProfiles("test")
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
public class UserRegistrationTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private Routes routes;
#Autowired
private TestConfig testConfig;
#Resource(name = "validCustomerDTO")
private CustomerDTO validCustomerDTO;
#Resource(name = "validVendorDTO")
private VendorRegistrationDTO validVendorRegistrationDTO;
#Value("${schedule.sendRegistrationConfirmationEmailTaskDelay}")
private Short registrationConfirmationEmailSenderTaskDelay;
private GreenMail smtpServer;
// Setup & tear down
#Before
public void setUp() {
smtpServer = testConfig.getMailServer();
smtpServer.start();
}
#After
public void tearDown() {
smtpServer.stop();
}
// Tests
#Test
public void testCreateCustomerAccount() throws Exception {
mockMvc.perform(
post(routes.getCustomerPath())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(validCustomerDTO)))
.andExpect(status().isCreated());
// When run normally, I get a timeout from the next line
await().atMost(registrationConfirmationEmailSenderTaskDelay + 10000, MILLISECONDS).until(smtpServerReceivedOneEmail());
// Verify correct registration confirmation email was sent
MimeMessage[] receivedMessages = smtpServer.getReceivedMessages();
assertThat(receivedMessages).hasSize(1);
// other checks
// ...
}
#Test
public void testCreateVendorAccount() throws Exception {
mockMvc.perform(
post(routes.getVendorPath())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(validVendorRegistrationDTO)))
.andExpect(status().isCreated());
// When run normally, I get a timeout from the next line
await().atMost(registrationConfirmationEmailSenderTaskDelay + 10000, MILLISECONDS).until(smtpServerReceivedOneEmail());
// Verify correct registration confirmation email was sent
MimeMessage[] receivedMessages = smtpServer.getReceivedMessages();
assertThat(receivedMessages).hasSize(1);
// other checks
// ...
}
// Helper methods
private Callable<Boolean> smtpServerReceivedOneEmail() {
return () -> smtpServer.getReceivedMessages().length == 1;
}
// Test configuration
#TestConfiguration
static class TestConfig {
private static final int PORT = 3025;
private static final String HOST = "localhost";
private static final String PROTOCOL = "smtp";
GreenMail getMailServer() {
return new GreenMail(new ServerSetup(PORT, HOST, PROTOCOL));
}
#Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost(HOST);
javaMailSender.setPort(PORT);
javaMailSender.setProtocol(PROTOCOL);
javaMailSender.setDefaultEncoding("UTF-8");
return javaMailSender;
}
}
Task scheduler (BusinessTaskScheduler)
#Component
public class BusinessTaskScheduler {
private final RegistrationTask registrationTask;
#Autowired
public BusinessTaskScheduler(RegistrationTask registrationTask) {
this.registrationTask = registrationTask;
}
#Scheduled(fixedDelayString = "${schedule.sendRegistrationConfirmationEmailTaskDelay}")
public void sendRegistrationConfirmationEmail() {
registrationTask.sendRegistrationConfirmationEmail();
}
}
The code that is called by the scheduled method (RegistrationTask)
#Component
#Transactional(isolation = Isolation.SERIALIZABLE)
public class RegistrationTask {
private final EmailHelper emailHelper;
private final EventService eventService;
private final UserRegistrationService userRegistrationService;
#Autowired
public RegistrationTask(EmailHelper emailHelper, EventService eventService, UserRegistrationService userRegistrationService) {
this.emailHelper = emailHelper;
this.eventService = eventService;
this.userRegistrationService = userRegistrationService;
}
public void sendRegistrationConfirmationEmail() {
Optional<UserAccountCreatedEvent> optionalEvent = eventService.getOldestUncompletedUserAccountCreatedEvent();
if (optionalEvent.isPresent()) {
UserAccountCreatedEvent event = optionalEvent.get();
User user = event.getUser();
RegistrationVerificationToken token = userRegistrationService.createRegistrationVerificationTokenForUser(user);
emailHelper.sendRegistrationConfirmationEmail(token);
eventService.completeEvent(event);
}
}
}
The event service (EventServiceImpl)
#Service
#Transactional(isolation = Isolation.SERIALIZABLE)
public class EventServiceImpl implements EventService {
private final ApplicationEventDAO applicationEventDAO;
private final UserAccountCreatedEventDAO userAccountCreatedEventDAO;
#Autowired
public EventServiceImpl(ApplicationEventDAO applicationEventDAO, UserAccountCreatedEventDAO userAccountCreatedEventDAO) {
this.applicationEventDAO = applicationEventDAO;
this.userAccountCreatedEventDAO = userAccountCreatedEventDAO;
}
#Override
public void completeEvent(ApplicationEvent event) {
if (!event.getStatus().equals(COMPLETED) && Objects.isNull(event.getCompletedAt())) {
event.setStatus(COMPLETED);
event.setCompletedAt(LocalDateTime.now());
applicationEventDAO.save(event);
}
}
#Override
public Optional<UserAccountCreatedEvent> getOldestUncompletedUserAccountCreatedEvent() {
Optional<UserAccountCreatedEvent> optionalEvent = userAccountCreatedEventDAO.findFirstByStatusOrderByCreatedAtAsc(NEW);
if (optionalEvent.isPresent()) {
UserAccountCreatedEvent event = optionalEvent.get();
setEventInProcess(event);
return Optional.of(userAccountCreatedEventDAO.save(event));
}
return Optional.empty();
}
#Override
public void publishEvent(ApplicationEvent event) {
applicationEventDAO.save(event);
}
// Helper methods
private void setEventInProcess(ApplicationEvent event) {
event.setStatus(Status.IN_PROCESS);
event.setInProcessSince(LocalDateTime.now());
}
}
The UserAccountCreatedEvent
application.yml
schedule:
sendRegistrationConfirmationEmailTaskDelay: 5000 # delay between tasks in milliseconds
I am new to scheduling with Spring, so any help is greatly appreciated!

Spring Boot Scheduler fixedDelay and cron

I'm running a spring boot scheduled process that takes 5-10 seconds to complete. After it completes, 60 seconds elapse before the process begins again (Note that I'm not using fixedRate):
#Scheduled(fixedDelay=60_000)
Now, I want to limit it to run every minute Mon-Fri 9am to 5pm. I can accomplish this with
#Scheduled(cron="0 * 9-16 ? * MON-FRI")
Problem here is that this acts similar to fixedRate - the process triggers EVERY 60 seconds regardless of the amount of time it took to complete the previous run...
Any way to to combine the two techniques?
it worked for me like this
I created a bean that returns a specific task executor and allowed only 1 thread.
#Configuration
#EnableAsync
public class AsyncConfig implements AsyncConfigurer {
#Bean(name = "movProcTPTE")
public TaskExecutor movProcessualThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
exec.setMaxPoolSize(1);
exec.initialize();
return exec;
}
}
In my service, I injected my task executor and wrapped my logic with it, so even though my schedule runs every minute, my logic will only run when the task executor is free.
#Service
#EnableScheduling
public class ScheduledService {
#Autowired
private ReportDataService reportDataService;
#Autowired
private AsyncService async;
#Autowired
#Qualifier("movProcTPTE")
private TaskExecutor movProcTaskExecutor;
#Scheduled(cron = "0 * * 1-7 * SAT,SUN")
public void agendamentoImportacaoMovProcessual(){
movProcTaskExecutor.execute(
() -> {
reportDataService.importDataFromSaj();
}
);
}
}
try this:
#Schedules({
#Scheduled(fixedRate = 1000),
#Scheduled(cron = "* * * * * *")
})
You can try this one:
#Scheduled(cron="1 9-16 * * MON-FRI")
Also you can try write correct on this site https://crontab.guru/
You can pass fixed delay (and any other number of optional parameters) to the annotation, like so:
#Scheduled(cron="0 * 9-16 ? * MON-FRI", fixedDelay=60_000)
From the documentation: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html

How to dynamically schedule a Spring Batch job with ThreadPoolTaskScheduler

I have a Spring Batch application in which I want to schedule jobs calls.
The scheduling interval is not known at build so I can't just annotate my Job with #Scheduled.This led me to use a ThreadPoolTaskScheduler.
The thing is the method schedule takes a Runnable as a parameter. Is it possible to schedule jobs this way ?
I can call the job directly from the following service but I can't schedule it.
Here is my the background of my problem, I tried to make it simple :
#Service
public class ScheduledProcessor{
private final ThreadPoolTaskScheduler threadPoolTaskScheduler;
private Application application;
#Autowired
public ScheduledProcessor(ThreadPoolTaskScheduler threadPoolTaskScheduler, Application application){
this.threadPoolTaskScheduler = threadPoolTaskScheduler;
this.application = application;
scheduledTasks = new ArrayList();
Trigger trigger = new CronTrigger("0/6 * * * * *");
//Here I am trying to schedule my job.
//The following line is wrong because a Job can't be cast to a Runnable but I wanted to show the intended behaviour.
threadPoolTaskScheduler.schedule((Runnable) application.importUserjob, trigger);
System.out.println("Job launch !");
}
And here is the JobBuilderFactory :
#Bean
public Job importUserJob(JobBuilderFactory jobs, Step s1, Step s2) {
return jobs.get("importUserJob")
.incrementer(new RunIdIncrementer())
.flow(s1)
.end()
.build();
}
I understand (well, I'm even not sure about that) that I can't directly cast a Job to a Runnable but is it possible to convert it in any way ? Or can you give me some advice about what to use for being able to dynamically schedule spring batch jobs ?
In case that changes something, I also need to be able to restart / skip my steps, as I currently can with the threadPoolTaskScheduler.
Thank you in advance for any help or hint you could provide.
I finally got how to do it !
I created a class which implements Runnable (and for convenience, extends Thread, which avoid the need to implement all of Runnable classes).
#Component
public class MyRunnableJob extends Thread implements Runnable{
private Job job;
private JobParameters jobParameters;
private final JobOperator jobOperator;
#Autowired
public MyRunnableJob(JobOperator jobOperator) {
this.jobOperator = jobOperator;
}
public void setJob(Job job){
this.job=job;
}
#Override
public void run(){
try {
String dateParam = new Date().toString();
this.jobParameters = new JobParametersBuilder().addString("date", dateParam).toJobParameters();
System.out.println("jobName : "+job.getName()+" at "+dateParam);
jobOperator.start(job.getName(), jobParameters.toString());
} catch (NoSuchJobException | JobInstanceAlreadyExistsException | JobParametersInvalidException ex) {
Logger.getLogger(MyRunnableJob.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
In my ScheduledProcessor class, I set a Job to myRunnable class and then pass it as a parameter of the schedule method.
public class SchedulingProcessor {
//Autowired fields :
private final JobLauncher jobLauncher;
private final Job importUserJob;
private final ThreadPoolTaskScheduler threadPoolTaskScheduler;
private final MyRunnableJob myRunnableJob;
//Other fields :
private List<ScheduledFuture> scheduledTasks;
#Autowired
public SchedulingProcessor(JobLauncher jobLauncher, Job importUserJob, ThreadPoolTaskScheduler threadPoolTaskScheduler, MyRunnableJob myRunnableJob) throws Exception {
this.jobLauncher=jobLauncher;
this.importUserJob=importUserJob;
this.threadPoolTaskScheduler=threadPoolTaskScheduler;
this.myRunnableJob=myRunnableJob;
Trigger trigger = new CronTrigger("0/6 * * * * *");
myRunnableJob.setJob(this.importUserJob);
scheduledTasks = new ArrayList();
scheduledTasks.add(this.threadPoolTaskScheduler.schedule((Runnable) myRunnableJob, trigger));
}
}
The scheduledTasks list is just to keep a control over the tasks I just scheduled.
This trick enabled me to dynamically (thanks to ThreadPoolTaskScheduler) schedule Spring Batch Jobs encapsulated in a class implementing Runnable. I wish it can help someone in the same case as mine.
Heres another way to trigger them from your spring context.
Job emailJob = (Job) applicationContext.getBean("xyzJob");
JobLauncher launcher = (JobLauncher) applicationContext
.getBean("jobLauncher");
launcher.run(emailJob, new JobParameters());

Resources