3

I´m creating an application with Spring boot + Quartz + Oracle and i would like to save the scheduling in the database (persistent, in case the server crashes). With RAMJobStore works fine, but when I try to use JobStoreTX it doesn't work, it always uses RAMJobStore, where colud be the problem?. I'm surely making a lot of mistakes, but it's my first application with spring boot + Quartz, can you give me an idea?

The events will be created dynamically, receiving the information in the controller.

application.yaml (In addition to Quartz, the application connects to the database to query tables, but the application and Quartz will use the same database)

hibernate:
  globally_quoted_identifiers: true
  show_sql: true
logging:
  level:
    org:
      hibernate:
        SQL: ${hibernate.logging}
  pattern:
    console: '%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n'
server:
  port: ${port}
spring:
  activemq:
    broker-url: ${activemq-url}
    password: ${activemq-password}
    user: ${activemq-user}
  datasource:
    driver-class-name: ${driverClassName}
    password: ${ddbb-password}
    jdbcUrl: ${ddbb-url}
    username: ${ddbb-user}
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.Oracle12cDialect
  quartz:
    job-store-type: jdbc
    jdbc:
      initialize-schema: never
    properties:
      org:
        quartz:
          scheduler:
            instanceId: AUTO
          jobStore:
            useProperties: true
            isClustered: false
            clusterCheckinInterval: 5000
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
            dataSource: quartzDataSource
          dataSource:
            quartzDataSource:
              driver: oracle.jdbc.driver.OracleDriver
              URL: ${ddbb-url}
              user: ${ddbb-user}
              password: ${ddbb-password}

Class SchedulerConfiguration

@Configuration
public class SchedulerConfiguration {
    
    @Bean
    public SchedulerFactoryBean schedulerFactory(ApplicationContext applicationContext) {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setJobFactory(new AutoWiringSpringBeanJobFactory());
        return schedulerFactoryBean;
    }

    @Bean
    public Scheduler scheduler(ApplicationContext applicationContext) throws SchedulerException {
        Scheduler scheduler = schedulerFactory(applicationContext).getScheduler();
        scheduler.start();
        return scheduler;
    }

   @Bean
   @QuartzDataSource
   @ConfigurationProperties(prefix = "spring.datasource")
       public DataSource quartzDataSource() {
       return DataSourceBuilder.create().build();
   }
}

class AutoWiringSpringBeanJobFactor

public class AutoWiringSpringBeanJobFactory extends SpringBeanJobFactory implements
    ApplicationContextAware{
    
    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
    

}

Job class

@Component
public class CampaignJob implements Job{
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println("Hi, the job works");
        
    }

}

The class where the scheduler it´s created

public class ManagerServiceImpl implements ManagerService {
    
    @Autowired
    private ProducerQueue producer;
    
    @Autowired
    private ManagementDatabase managementDatabase;
    
    @Autowired
    private Scheduler scheduler;
    
    @Override
    public String processCampaign(ScheduleCampaign scheduleCampaign) {
        try {
            ZonedDateTime dateTime = ZonedDateTime.of(scheduleCampaign.getDateTime(), scheduleCampaign.getTimeZone());
            JobDetail jobDetail = buildJobDetail(scheduleCampaign);
            Trigger trigger = buildJobTrigger(jobDetail, dateTime);
            scheduler.scheduleJob(jobDetail, trigger);
            } catch (SchedulerException e) {
            System.out.println("There was an error creating the scheduler: "+e);
        }
        
        return "Scheduler created";
    }
    

     private JobDetail buildJobDetail(ScheduleCampaign scheduleCampaign) {
            JobDataMap jobDataMap = new JobDataMap();
            System.out.println("Function: buildJobDetail -  campaign value: "+scheduleCampaign.getCampaign());
            jobDataMap.put("campaign", scheduleCampaign.getCampaign());
            return JobBuilder.newJob(CampaignJob.class)
                    .withIdentity(UUID.randomUUID().toString(), "campaign-jobs")
                    .requestRecovery(true)
                    .storeDurably(true)
                    .withDescription("campaign job planned")
                    .usingJobData(jobDataMap)
                    .storeDurably()
                    .build();
        }

     private Trigger buildJobTrigger(JobDetail jobDetail, ZonedDateTime startAt) {
            return TriggerBuilder.newTrigger()
                    .forJob(jobDetail)
                    .withIdentity(jobDetail.getKey().getName(), "campaign-triggers")
                    .withDescription("campaign job Trigger")
                    .startAt(Date.from(startAt.toInstant()))
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withMisfireHandlingInstructionFireNow())
                    .build();
        }

    

}

Logs

2020-12-28 16:16:08 INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...

2020-12-28 16:16:09 INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.

2020-12-28 16:16:09 INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]

2020-12-28 16:16:10 INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.4.23.Final

2020-12-28 16:16:10 INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor

2020-12-28 16:16:10 INFO o.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl

2020-12-28 16:16:10 INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.

2020-12-28 16:16:10 INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.

2020-12-28 16:16:10 INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'schedulerFactory' with instanceId 'NON_CLUSTERED'

Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.

NOT STARTED.

Currently in standby mode.

Number of jobs executed: 0

Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.

Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
FireStarter
  • 43
  • 2
  • 7

1 Answers1

0

Try to autowire your app default dataSource, if you are pointing the same DB for quartz jobs as well.

The below configuration worked for me with PostgreSQL

@Autowired
DataSource dataSource;

@Autowired
JobFactory jobFactory;

@Bean
public JobFactory jobFactory(ApplicationContext applicationContext) {
    AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
    jobFactory.setApplicationContext(applicationContext);
    return jobFactory;
}

@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
    SchedulerFactoryBean factory = new SchedulerFactoryBean();
    factory.setOverwriteExistingJobs(true);
    factory.setAutoStartup(true);
    factory.setDataSource(dataSource);
    factory.setJobFactory(jobFactory);
    factory.setQuartzProperties(quartzProperties());

    return factory;
}

@Bean
public Properties quartzProperties() throws IOException {
    PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
    propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
    propertiesFactoryBean.afterPropertiesSet();
    return propertiesFactoryBean.getObject();
}