Reputation: 4875
I want to use an external properties file to load database and REST endpoint information. I'm trying to avoid the XML configuration and favor the annotation based configuration.
I have created 2 classes both of which are annotated with @Configuration
and use the @Value
annotation in their constructors to load the properties:
RestConfiguration.java
@Configuration
public class RestConfiguration {
private final String grantType;
private final AuthenticationScheme authenticationScheme;
private final String clientId;
private final String clientSecret;
private final String accessTokenUri;
private final boolean useProxy;
private final String proxyHost;
private final int proxyPort;
@Autowired
public RestConfiguration(
@Value("${api.oauth2.grant-type}") String grantType,
@Value("${api.oauth2.authentication-scheme}") AuthenticationScheme authenticationScheme,
@Value("${api.oauth2.client-id}") String clientId,
@Value("${api.oauth2.client-secret}") String clientSecret,
@Value("${api.oauth2.url}") String accessTokenUri,
@Value("${net.proxy}") boolean useProxy,
@Value("${net.proxy.host}") String proxyHost,
@Value("${net.proxy.port}") int proxyPort) {
this.grantType = grantType;
this.authenticationScheme = authenticationScheme;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessTokenUri = accessTokenUri;
this.useProxy = useProxy;
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
}
}
PersistenceConfiguration.java
@Configuration
public class PersistenceConfiguration {
private final String host;
private final String port;
private final String database;
private final String schema;
private final String user;
private final String password;
@Autowired
public PersistenceConfiguration(
@Value("${db.host}") String host,
@Value("${db.port}") String port,
@Value("${db.database}") String database,
@Value("${db.schema}") String schema,
@Value("${db.user}") String user,
@Value("${db.password}") String password) {
this.host = host;
this.port = port;
this.database = database;
this.schema = schema;
this.user = user;
this.password = password;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("ch.example.rest.entities");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://" + host + ":" + port + "/" + database);
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setSchema(schema);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "none");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQL95Dialect");
properties.setProperty(
"hibernate.physical_naming_strategy",
"ch.example.rest.configurations.SnakeCaseNamingStrategy");
properties.setProperty(
"spring.datasource.hikari.data-source-properties", "stringtype=unspecified");
return properties;
}
}
Both configuration files reside in the same package (sub-package configurations
).
The class that initializes the Spring context looks like this:
@Configuration
@ComponentScan(basePackages = "ch.example.rest")
@EnableJpaRepositories("ch.example.rest.repositories")
@PropertySource("classpath:application.properties")
public class RestClient {
private CommandLineController commandLineController;
@Autowired
public RestClient(CommandLineController commandLineController) {
this.commandLineController = commandLineController;
}
private static void main(String[] args) {
// ... some parsing of command line arguments
// Initialize context
ApplicationContext ctx = new AnnotationConfigApplicationContext(RestClient.class);
RestClient restClient = ctx.getBean(RestClient.class, uploadCommand);
restClient.runCommand(parsedCommand, uploadCommand);
}
public void runCommand(String command, UploadBillsCommand uploadCommand) {
// Some calls to a controller
commandLineController....;
}
}
Interestingly the RestConfiguration
class receives the properties, but the PersistenceConfiguration
does not. During debugging I noticed that the PersistenceConfiguration
class is constructed almost immediately, whereas the RestConfiguration
is loaded some time later, when the first call to the RestTemplate is made.
I suspect that this might have something to do with the fact that Spring JPA tries to wire up the repositories and therefore requires the SQL connection to be made at startup.
I found this question that seems to suggest that it is not possible to supply the database configuration externally without additional boilerplate code. As that question is already 5 years old, I was wondering if maybe there is another elegant solution to fix this problem without having to create a second context.
Upvotes: 0
Views: 1143
Reputation: 4875
Alright, so the answer seems pretty simple. Instead of letting Spring load the properties using the expression language with the @Value
annotation, I just had to inject an instance of Environment
and then get the properties directly from it:
@Configuration
public class PersistenceConfiguration {
private String host;
private String port;
private String database;
private String schema;
private String user;
private String password;
@Autowired
public PersistenceConfiguration(Environment environment) {
this.host = environment.getProperty("db.host");
this.port = environment.getProperty("db.port");
this.database = environment.getProperty("db.database");
this.schema = environment.getProperty("db.schema");
this.user = environment.getProperty("db.user");
this.password = environment.getProperty("db.password");
}
}
Upvotes: 1