If you are looking for a:
Trigger and Job names
During the configuration we may select for each job and each trigger a name and a group. The last values gives us a way to cluster. The first one is in interesting.
Job names
Setting manually a job names makes the code refactoring save. By default the spring quartz integration uses the bean name. Which is good for the start, but in case that we refactor the code and rename the bean we have to keep in mind to delete the old job in the quartz tables. As so it is usually saver to define a name, which we keep stable:
@Bean public JobDetailFactoryBean retryJob() { final var jobDetailFactory = new JobDetailFactoryBean(); jobDetailFactory.setJobClass(RetryJob.class); // static name, we may of course also define a constant jobDetailFactory.setName("RETRY-JOB"); jobDetailFactory.setDurability(true); return jobDetailFactory; }
If we now change the class name or packe structure it will get updated as soon we deploy the new version instead added to the job list.
Trigger names
The combination of a trigger name and trigger group has to be unique. Which gives us a very simple way to verify if a particular job was already scheduled or not. E.g. if we want to schedule a timer to notify a user that he has to change his password in 30 days we could just:
public TriggerKey notifyUser(String user) throws SchedulerException { TriggerKey key = new TriggerKey(user, NotifyUserJob.ID); var t = TriggerBuilder.newTrigger() .forJob(JobKey.jobKey(NotifyUserJob.ID)) .startAt(Date.from(Instant.now().plusMillis(1_000 * 60 * 60 * 24 * 30))) .usingJobData("user", user) .withIdentity(key) .build(); scheduler.scheduleJob(t); return t.getKey(); }
Now we get a ObjectAlreadyExistsException
if we trigger it again and we can easily delete the trigger if the user changes the password before it would trigger. We may also update it.
Retry Triggers
A very common case is not only too trigger a task but also have a way to implement a kind of retry. With quartz this is pretty straight forward. In general we have to possibilities:
- Trigger immediately
- Queue a new Trigger to run the same job with a delay
Trigger immediately
In the most simple way we can just catch any exception and re-trigger the job again. Any change to the JobData
will be stored in an own transaction into the quartz tables. Which gives us a simple way to store intermediate results or just a retry counter; separately of our main application transaction and DB changes, which can rollback in peace.
public class RetryJob extends QuartzJobBean { @Autowired private YourService yourService; @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { Integer retryCount = (Integer)context.getMergedJobDataMap().getOrDefault("retryCount", 1); try { yourService.doStuff(); } catch (Exception e) { if (retryCount >= 3) { throw e; } else { // don't use the getMergedJobDataMap of spring, it will not be updated context.getTrigger().getJobDataMap().put("retryCount", retryCount + 1); throw new JobExecutionException("yourService.doStuff failed " + retryCount + " times. Will retry. " + e.getMessage(), true); } } } }
Trigger later
Usually we want to wait a bit before we retry, which is just updating the trigger:
@Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { Integer retryCount = (Integer)context.getMergedJobDataMap().getOrDefault("retryCount", 1); try { yourService.doStuff(); } catch (Exception e) { if (retryCount > 3) { throw e; } else { // we have to use the trgger data, as we repalce the trigger now context.getTrigger().getJobDataMap().put("retryCount", retryCount + 1); final var tb = context.getTrigger().getTriggerBuilder(); tb.startAt(Date.from(Instant.now().plusSeconds(retryCount * 10))); scheduler.rescheduleJob(context.getTrigger().getKey(), tb.build()); } } }
Delete all job triggers
If we want to cancel all triggers for a job we can just list and delete them:
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(xyzJob.getKey()); scheduler.unscheduleJobs(triggers.stream().map(t -> t.getKey()).toList());