Search code examples
springtomcatdatasourcequartz

SpringBoot with Quartz and Tomcat datasource: Driver's Blob representation is of an unsupported type: oracle.sql.BLOB


I am using SpringBoot 1.4.5 and quartz for scheduling with a DataSource configured in Tomcat's context.xml which is injected as a bean via JndiDataSource for connecting to a Oracle 10g DB.

Here are the relevant dependencies, including the Oracle driver, I am using the dependency management provided by SpringBoot:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>
            <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>oracle-ojdbc7</artifactId>
            <version>12.1.0-2</version>
        </dependency>

This is the DataSource configured in Tomcat in context.xml (the placeholder properties are defined in catalina.properties) :

<Resource name="${tomcat.dbpool.ups.quartz.resourcename}" auth="Container"
                type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver"
                url="${tomcat.dbpool.ups.quartz.connectionurl}"
                username="${tomcat.dbpool.ups.quartz.username}"
                password="${tomcat.dbpool.ups.quartz.password}"
                maxTotal="${tomcat.dbpool.ups.quartz.maxTotal}"
                maxIdle="${tomcat.dbpool.ups.quartz.maxIdle}"
                minIdle="${tomcat.dbpool.ups.quartz.minIdle}"
                maxWaitMillis="${tomcat.dbpool.ups.quartz.maxWaitMillis}"
                validationQueryTimeout="${tomcat.dbpool.ups.quartz.validationQueryTimeout}"
                testWhileIdle="true"
                removeAbandonedOnMaintenance="true"
                timeBetweenEvictionRunsMillis="${tomcat.dbpool.ups.quartz.timeBetweenEvictionRunsMillis}"
                minEvictableIdleTimeMillis="${tomcat.dbpool.ups.quartz.minEvictableIdleTimeMillis}"
                />

The datasource bean configuration and quartz necessary beans:

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

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("quartzDataSource") final DataSource quartzDS,
                                                     final JobFactory jobFactory,
                                                     @Qualifier("loansAppTrigger") final Trigger jobTrigger) throws IOException {
        final SchedulerFactoryBean factory = new SchedulerFactoryBean();
        // this allows to update triggers in DB when updating settings in config file:
        factory.setOverwriteExistingJobs(true);
        factory.setDataSource(quartzDS);
        factory.setJobFactory(jobFactory);

        factory.setQuartzProperties(quartzProperties());
        factory.setTriggers(jobTrigger);

        return factory;
    }

    @Bean(name = "quartzDataSource")
    @Profile("!local")
    public DataSource jndiDataSource() {
        JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        DataSource dataSource = dataSourceLookup.getDataSource("java:comp/env/jdbc/QUARTZ");
        return dataSource;
    }

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

    @Bean
    public JobDetailFactoryBean checkUnconfirmedApplicationsJobDetail() {
        return createJobDetail(CheckUnconfirmedApplicationsJob.class);
    }

    @Bean(name = "loansAppTrigger")
    public SimpleTriggerFactoryBean unconfirmedLoansApplicationsTrigger(
            @Qualifier("checkUnconfirmedApplicationsJobDetail") final JobDetail jobDetail,
            @Value("${ups.loan.check.pending.apps.frequency}") final long frequency) {
        return createTrigger(jobDetail, frequency);
    }

    private static JobDetailFactoryBean createJobDetail(final Class jobClass) {
        final JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
        factoryBean.setJobClass(jobClass);
        // job has to be durable to be stored in DB:
        factoryBean.setDurability(true);
        return factoryBean;
    }

    private static SimpleTriggerFactoryBean createTrigger(final JobDetail jobDetail, final long pollFrequencyMs) {
        final SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
        factoryBean.setJobDetail(jobDetail);
        factoryBean.setStartDelay(0L);
        factoryBean.setRepeatInterval(pollFrequencyMs);
        factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
        // in case of misfire, ignore all missed triggers and continue :
        factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);

        return factoryBean;
    }

And the following properties in quartz.properties file:

org.quartz.scheduler.instanceName=scheduler
org.quartz.scheduler.instanceId=AUTO
org.quartz.threadPool.threadCount=5
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.misfireThreshold=60000

org.quartz.jobStore.isClustered=true
org.quartz.jobStore.clusterCheckinInterval=20000

When I deploy this on Tomcat 8 I get the following error :

org.quartz.JobPersistenceException: Couldn't store job: Driver's Blob representation is of an usupported type: oracle.sql.BLOB

In Tomcat's lib folder I have the oracle-ojdbc7-12.1.0-2.jar driver.


Solution

  • Can you please check if you have any duplicate oracle-ojdbc JAR files present? The JAR should be in only one location: either in WEB-INF/lib or under tomcat/lib