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();