Home > Net >  IllegalArgumentException: jdbcUrl is required with driverClassName
IllegalArgumentException: jdbcUrl is required with driverClassName

Time:09-29

Running maven build on my Spring Boot app fails with IllegalArgumentException: jdbcUrl is required with driverClassName. Here's how the setup looks like:

@Bean
private DataSource getDataSource() {
    return DataSourceBuilder.create()
          .driverClassName("org.postgresql.Driver")
          .url(environment.getProperty(VAR_URL)) // Error thrown here
          .username(environment.getProperty(VAR_NAME))
          .password(environment.getProperty(VAR_PASSWORD))
          .build();

The reason for the error is that the environment does not have value for the key VAR_URL during build time as it is pulled from a secret source during app startup. If I hardcode the URL, user & password details, it works. So is there a workaround I can follow to resolve this problem?

Point to note: The branch I am working on and has this issue has a quartz scheduler configured. And the same code(another branch) without quartz scheduler builds and runs fine. This makes me curious about whether if the quartz configuration with spring Datasource is causing the problem. This is my first time with quartz.

Here is the YAML with the configuration to examine:

server:
  port: 8080
spring:
  application:
    name: my-app
  profiles:
    active: dev
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
    show-sql: true
    open-in-view: true
  datasource:
    url:
    username:
    password:
    driver-class-name: org.postgresql.Driver

  quartz:
    properties:
      org.quartz.jobStore.isClustered: true
      org.quartz.scheduler.instanceId: AUTO
      org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
      org.quartz.scheduler.instanceName: my-scheduler
      org.quartz.threadPool.threadCount: 3
      org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
      org.quartz.jobStore.useProperties: true
      org.quartz.jobStore.misfireThreshold: 60000
      org.quartz.jobStore.tablePrefix: qrtz_
      org.quartz.plugin.shutdownHook.class: org.quartz.plugins.management.ShutdownHookPlugin
      org.quartz.plugin.shutdownHook.cleanShutdown: TRUE
    jdbc:
      initialize-schema: always
    job-store-type: jdbc

Am I missing anything? Please suggest if there's a better way of doing this. Thank you.

UPDATE

 @Bean
  public SchedulerFactoryBean schedulerFactoryBean() {

    SchedulerJobFactory jobFactory = new SchedulerJobFactory();
    jobFactory.setApplicationContext(applicationContext);

    Properties properties = new Properties();
    properties.putAll(quartzProperties.getProperties());

    SchedulerFactoryBean factory = new SchedulerFactoryBean();
    factory.setOverwriteExistingJobs(true);
    factory.setDataSource(dataSource);
    factory.setQuartzProperties(properties);
    factory.setJobFactory(jobFactory);
    factory.setTriggers(reportTriggerConfig().getObject());
    return factory;
  }

DataSourceConfig:


import java.util.Optional;
import javax.sql.DataSource;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
/**
 * DataSourceConfig.
 * 
 */

@Configuration
@RequiredArgsConstructor
public class DataSourceConfig {

  public final Environment environment;

  @Value("${dburl:#{null}}") Optional<String> databaseUrl;
  @Value("${dbusername:#{null}}") Optional<String> username;
  @Value("${dbpassword:#{null}}") Optional<String> password;

  /**
   * Gets the dev datasource.
   *
   * @return the dev datasource
   */
  @Bean
  @Profile("dev")
  @Primary
    public DataSource getDevDatasource() {
     return DataSourceBuilder.create()
          .driverClassName("org.postgresql.Driver")
          .username(username.orElse(null))
          .url(databaseUrl.orElse(null))
          .password(password.orElse(null))
          .build();
  }
}

SchedulerConfig class:

import java.util.Properties;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.quartz.QuartzProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**
 * SchedulerConfig.
 */
@Configuration
public class SchedulerConfig {

  @Autowired
  private DataSource dataSource;

  @Autowired
  private ApplicationContext applicationContext;

  @Autowired
  private QuartzProperties quartzProperties;

  /**
   * SchedulerFactoryBean.
   * @return SchedulerFactoryBean
   */
  @Bean
  public SchedulerFactoryBean schedulerFactoryBean() {

    SchedulerJobFactory jobFactory = new SchedulerJobFactory();
    jobFactory.setApplicationContext(applicationContext);

    Properties properties = new Properties();
    properties.putAll(quartzProperties.getProperties());

    SchedulerFactoryBean factory = new SchedulerFactoryBean();
    factory.setOverwriteExistingJobs(true);
    factory.setDataSource(dataSource);
    factory.setQuartzProperties(properties);
    factory.setJobFactory(jobFactory);
    factory.setTriggers(reportTriggerConfig().getObject());
    return factory;
  }

  /**
   * JobDetailFactoryBean.
   * @return JobDetailFactoryBean
   */
  @Bean
  public JobDetailFactoryBean reportJobConfig() {
    JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
    jobDetailFactory.setJobClass(ReportJob.class);
    jobDetailFactory.setDurability(true);
    jobDetailFactory.setGroup("pit-scheduler-group");
    return jobDetailFactory;
  }

  /**
   * CronTriggerFactoryBean.
   * @return CronTriggerFactoryBean
   */
  @Bean
  public CronTriggerFactoryBean reportTriggerConfig() {
    CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
    cronTriggerFactoryBean.setJobDetail(reportJobConfig().getObject());
    cronTriggerFactoryBean.setCronExpression("0 0/1 0 ? * * *");
    cronTriggerFactoryBean.setGroup("pit-scheduler-group");
    return cronTriggerFactoryBean;
  }
}

Main SpringBootTest class:

@SpringBootTest
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
    DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
@EnableConfigurationProperties(value = appConfig.class)
class PitApplicationTest {

  @Autowired
  private HistoryController historyController;

  @MockBean
  private HistoryRepository historyRepository;

  @Test
  void contextLoads() {
    assertNotNull(historyController);
  }
}

Stacktrace:

[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 3.078 s <<< FAILURE! - in com.pit.PitApplicationTest
[ERROR] contextLoads  Time elapsed: 0.011 s  <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schedulerFactoryBean' defined in class path resource [com/pit/job/SchedulerConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.

CodePudding user response:

You are basically overriding spring-boot's autoconfiguration in order to configure the datasource of this scheduler. That's why it works on the branch without the quartz scheduler (I guess you do not have the @Bean private DataSource getDataSource() in that branch).

You have to use @Value in order to retrieve the database properties from application.yml

e.g.

@Value("${spring.datasource.url}")
private String databaseUrl;

@Value("${spring.datasource.username}")
private String username;

@Value("${spring.datasource.password}")
private String password;

//and then use these strings in your getDataSource()

@Bean
private DataSource getDataSource() {
    return DataSourceBuilder.create()
          .driverClassName("org.postgresql.Driver")
          .url(environment.getProperty(databaseUrl)) // Error thrown here
          .username(environment.getProperty(username))
          .password(environment.getProperty(password))
          .build();

CodePudding user response:

@Value("${spring.datasource.url}")
private String databaseUrl;

@Value("${spring.datasource.username}")
private String username;

@Value("${spring.datasource.password}")
private String password;

@Value("${spring.datasource.driver_class_name}")
private String driverClassName;


@Bean
private DataSource getDataSource() {
    return DataSourceBuilder.create()
          .driverClassName(driverClassName)
          .url(databaseUrl)
          .username(username)
          .password(password)
          .build();
  • Related