Skip to content

Instantly share code, notes, and snippets.

@mychalvlcek
Last active June 12, 2024 09:39
Show Gist options
  • Save mychalvlcek/f06864b3767e98b70fec904b7f5e6e3e to your computer and use it in GitHub Desktop.
Save mychalvlcek/f06864b3767e98b70fec904b7f5e6e3e to your computer and use it in GitHub Desktop.
Spring Quartz - multitenancy

To achieve multitenant behavior for quartz scheduler, we need to setup:

  • set tenantId for every job we created.. we use quartz JobDataMap (handy key/value store) in Job class
  • JobListener - to inject tenantId from JobDataMap into current thread-bounded tenant context

This will work with shared quartz database.. if we want to have separated quartz databases/schemas for every tenant, we can instantiate multiple SchedulerFactoryBean with its custom dataSource

@Configuration
public class QuartzMultitenantConfig {
@Bean
public JobDetailFactoryBean jobDetail() {
JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
jobDetailFactory.setJobClass(TestJob.class);
jobDetailFactory.setDescription("Invoke Sample Job service...");
jobDetailFactory.setName("test");
jobDetailFactory.setGroup("group");
jobDetailFactory.setJobDataAsMap(Map.of(TenantContext.TENANT_HEADER, "tenant1"));
jobDetailFactory.setDurability(true);
return jobDetailFactory;
}
@Bean
public SimpleTriggerFactoryBean trigger(JobDetail job) {
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
trigger.setJobDetail(job);
trigger.setRepeatInterval(3000);
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
return trigger;
}
@Bean
public SchedulerFactoryBean schedulerFactory(ObjectProvider<Trigger> triggers) {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(jobFactory());
schedulerFactoryBean.setTriggers(triggers.orderedStream().toArray(Trigger[]::new));
schedulerFactoryBean.setGlobalJobListeners(new TenantInjectorJobListener());
return schedulerFactoryBean;
}
@Bean
public SpringBeanJobFactory jobFactory() {
return new SpringBeanJobFactory();
}
}
@Slf4j
public class TenantContext {
public static final String TENANT_HEADER = "X-TenantId";
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public static void setTenantId(String tenantId) {
if (log.isDebugEnabled()) {
log.debug("Setting tenantId to {} ", tenantId);
}
CONTEXT.set(tenantId);
}
public static String getTenantId() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
@Slf4j
public class TenantInjectorJobListener implements JobListener {
private static final String NAME = "tenantContext";
@Override
public String getName() {
return NAME;
}
@Override
public void jobToBeExecuted(JobExecutionContext context) {
TenantContext.setTenantId(context.getJobDetail().getJobDataMap().getString(TenantContext.TENANT_HEADER));
}
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
TenantContext.clear();
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
TenantContext.clear();
}
}
@Slf4j
@Component
public class TestJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) {
log.info("TestJob {}", TenantContext.getTenantId());
}
}
@CodeNinjai
Copy link

How would multi Instantiation for SchedulerFactoryBean with custom dataSource look like?

@mychalvlcek
Copy link
Author

To be honest I dont know, thats why I have choosen different solution

@CodeNinjai
Copy link

Ok, thanks anyway. I needed to implement quartz in a multi-tenant environment, where each tenant has its own dataSource 🙁

@CodeNinjai
Copy link

CodeNinjai commented Oct 11, 2022 via email

@mychalvlcek
Copy link
Author

Ok, thanks anyway. I needed to implement quartz in a multi-tenant environment, where each tenant has its own dataSource 🙁

OK, with any luck?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment