Spring autowire custom #ConfigurationProperties object by prefix - java

I would like to achieve not-trivial bean injection implementation.
I have a custom properties file:
#Getter
#Setter
#ConfigurationProperties
public class DatabaseProperties {
private String url;
private String username;
private String password;
}
I Here is the configuration file:
#Configuration
#EnableConfigurationProperties(DatabaseProperties.class)
public class DBConfig {
#Bean
#ConfigurationProperties(prefix = "datasource.database1")
public JdbcTemplate jdbcTemplateDatabase1(DatabaseProperties databaseProperties) {
DataSource dataSource = new DriverManagerDataSource(
databaseProperties.getUrl(),
databaseProperties.getUsername(),
databaseProperties.getPassword());
return new JdbcTemplate(dataSource);
}
#Bean
#ConfigurationProperties(prefix = "datasource.database2")
public JdbcTemplate jdbcTemplateDatabase2(DatabaseProperties databaseProperties) {
DataSource dataSource = new DriverManagerDataSource(
databaseProperties.getUrl(),
databaseProperties.getUsername(),
databaseProperties.getPassword());
return new JdbcTemplate(dataSource);
}
}
The goal I want to achieve is to instantiate a new DatabaseProperties instance based on prefix.
There are two possible solutions:
create two beans of type DatabaseProperties using corresponding prefixes and two JdbcTemplate beans where parameter is qualified DatabaseProperties bean accordingly.
in each JdbcTemplate bean provide 3 parameters (String url, String username, String password) and inject them through #Value
BUT Is it possible to get rid of creating DatabaseProperties beans for each JdbcTemplate or using #Value ?

You don't need create a DatabaseProperties. Spring already does this for us on datasources and proprierties variable
#Configuration
public class ConfigDataSource {
#Bean("datasource-1") // this name will qualify on #autowired
#ConfigurationProperties(prefix="spring.datasource.yourname-datasource-1") // this is the name for the prefix for datasource on .properties settings
public DataSource dataSourcePostgres() {
return DataSourceBuilder.create().build();
}
#Bean("datasource-2") // this name will qualify on #autowired
#ConfigurationProperties(prefix="spring.datasource.yourname-datasource-2") // this is the name for the prefix for datasource on .properties settings
public DataSource dataSourceMySql() {
return DataSourceBuilder.create().build();
}
}
.properties
# Its required use the same name declared in bean
spring.datasource.yourname-datasource-1.url=...
spring.datasource.yourname-datasource-1.jdbcUrl=${spring.datasource.yourname-datasource-1}
spring.datasource.yourname-datasource-1.username=user
spring.datasource.yourname-datasource-1.password=pass
spring.datasource.yourname-datasource-1.driver-class-name=your.driver
spring.datasource.yourname-datasource-2.url=...
spring.datasource.yourname-datasource-2.jdbcUrl=${spring.datasource.yourname-datasource-2}
spring.datasource.yourname-datasource-2.username=user
spring.datasource.yourname-datasource-2.password=pass
spring.datasource.yourname-datasource-2.driver-class-name=your.driver
using on services
#Awtowired
#Qualifier("datasource-1")
private DataSource dataSource1;
#Awtowired
#Qualifier("datasource-2")
private DataSource dataSource2;
public testJdbcTemplate(){
// You can qualifier JdbcTemplate below on bean and not necessary need instance on service
JdbcTemplate jdbcTemplateDatasource1 = new JdbcTemplate(dataSource1);
JdbcTemplate jdbcTemplateDatasource2 = new JdbcTemplate(dataSource2);
}

To my knowledge, there is no way around the fact that if you want to have access to multiple databases Spring will not be able to do the magic for you. You will need to create the two JdbcTemplate Spring-managed beans and then inject them where needed using the #Qualifier annotation.
This approach has two benefits:
It actually work;
You are explicit about what you are doing. A Spring application has already a good load of magic happening in there, so we might want to avoid some additional one when we need something a little bit more custom and that is not that complex to achieve.

Related

how to pass a datasource to a library?

I am writing a library which retrieves data from a specific data schema. This library holds a Datasource object which can be anything. Right now I have defined the name of the datasource within the library which I would like to avoid.
import javax.sql.DataSource
public class MyLibraryDao.java {
private static final DS_NAME = "MY_DS_NAME";
#Resource(name = "default", lookup = DS_NAME , type = DataSource.class)
protected DataSource dataSource;
}
The DAO class is not directly exposed to the client. There is a service layer inbetween:
import javax.inject.Inject;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Model;
#ApplicationScoped
#Model
public class MyLibraryService {
#Inject
MyLibraryDao dao;
}
Now, how would I pass the datasource object to the library?
I assume I need to create a constructor in the DAO with takes a DataSource but what about the service?
The library will be used in a CDI environment.
First things first your library needs a datasource, let's declare the dependency:
public class MyLibraryDao {
#Inject
protected DataSource dataSource;
}
Now the rest of the application that is using the library is responsible to provide a datasource to CDI; a simple way is:
// Example; your implementation may vary
public class AppDatasourceProducer {
private static final DS_NAME = "MY_APP_DS_NAME";
#Resource(name = "default", lookup = DS_NAME , type = DataSource.class)
protected DataSource dataSource;
#Produces
#ApplicationScoped
public DataSource getDatasource() {
return dataSource;
}
}
What's changed? Now your application is responsible for knowing the datasource name AND providing the datasource itself. The example above can work in JEE environments that honor the #Resource annotation. Using a different implementation for the provider would work in e.g. a desktop environment (standalone application), making your library reusable.
The exact datasource name may be fixed, just like in the example, or read from configuration, e.g. from system properties (like mentioned in a comment); e.g.:
// Example 2, DS name from system properties
#ApplicationScoped
public class AppDatasourceProducer {
protected DataSource dataSource;
#PostConstruct
void init() throws Exception {
String dsName = System.getProperty("XXXXX");
InitialContext ic = new InitialContext();
dataSource = (DataSource) ic.lookup(dsName);
}
#Produces
#ApplicationScoped
public DataSource getDatasource() {
return dataSource;
}
}
Going further:
An application that uses your library may be using several datasources, for whatever reason. You may want to provide a qualifier to specify the datasource to be used by your app.
I used field injection in MyLibraryDao for simplicity. If you change to constructor injection then, at least MyLibraryDao, will be usable in non-CDI environments as well, i.e. if you have obtained a DataSource somehow, you can now do new MyLibraryDao(datasource). Even more reusability.

How can I inject and use multiple datasources dynamically in Spring boot application?

I have these profiles, uat-nyc, uat-ldn.
uat-nyc datasource is oracle and uat-ldn is mysql server
This configuration is setup in application-uat-nyc.yml and application-uat-ldn.yml
I have below configuration class
#Profile({"uat-nyc", "uat-ldn"})
#Configuration
#EnableConfigurationPropeties(DatSourceProperties.class)
public class DataSourceConfig{
private DataSourceProperties properties; // server, username, password are set here
DataSource getDataSource(){// gets datasource based on profiles}
}
if my application is run with spring.profiles.active: uat-nyc,uat-ldn will it create two datasources, ?
one with configuration from uat-nyc and another from uat-ldn
I have a function below, in this function, I am getting data from third-party service, and depending on ldn or nyc , I need to persist into ldn or nyc database. How can I make the below if else section dynamic? How can I get respective datasources i.e ldn and nyc in the if else section in below getProducts method?
class Product{
String name;
int price;
int region;
}
#Component
Class ProductLoader{
JdbcTemplate jdbcTemplate;
public ProductLoader(DataSource ds){
jdbcTemplate = new JdbcTemplate(ds);
}
public void getProducts(){
List<Product> products = // rest service to get products
if(Product product : product){
if(product.getRegion().equals("LONDON"){
//write to LONDON datbase
// How can I get ldn datasource here?
}
if else(product.getRegion().equals("NewYork"){
//write to NewYork datbase
How can I get NewYork datasource here?
}
else{
// Unknown location
}
}
}
}
Question -
if my application is run with spring.profiles.active: uat-nyc,uat-ldn will it create two datasources, ?
How can I inject the datasources dynamically into ProductLoader and use specific datasource for ldn and nyc
At the first time you need to tell spring you gonna use two datasource will be managed by context spring. use #Bean on #Configuration class then use #Autowired to declareing vars managed by spring.
you could use #Qualifier to choose and qualify your beans
#Configuration
public class ConfigDataSource {
// example for dataSource
#Bean("dataSourceWithPropOnCode") // this name will qualify on #autowired
public DataSource dataSourceWithPropOnCode() {
return DataSourceBuilder.create().url("").password("").username("").driverClassName("").build();
}
#Bean("dataSourceWithPropFromProperties") // this name will qualify on #autowired
#ConfigurationProperties(prefix="spring.datasource.yourname-datasource") // this is the name for the prefix for datasource on .properties settings
public DataSource dataSourcePostgres() {
return DataSourceBuilder.create().build();
}
// example for jdbctemplate
#Bean("jdbcTemaplateWithPropFromProperties") // this name will qualify on #autowired
public JdbcTemplate jdbcTemplatePostgres(#Qualifier("dataSourceWithPropFromProperties") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#Bean("jdbcTemaplateWithPropOnCode") // this name will qualify on #autowired
public JdbcTemplate jdbcTemplatePostgres(#Qualifier("dataSourceWithPropOnCode") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
settings on properties
spring.datasource.yourname-datasource.url=...
spring.datasource.yourname-datasource.jdbcUrl=${spring.datasource.yourname-datasource}
spring.datasource.yourname-datasource.username=user
spring.datasource.yourname-datasource.password=pass
spring.datasource.yourname-datasource.driver-class-name=your.driver
using on services
#Qualifier("jdbcTemaplateWithPropFromProperties")
#Autowired
private JdbcTemplate jdbcTemplate1;
#Qualifier("jdbcTemaplateWithPropOnCode")
#Autowired
private JdbcTemplate jdbcTemplate2;
#Qualifier("dataSourceWithPropOnCode")
#Autowired
private DataSource dataSource1;
private DataSource dataSource2;
public someContructorIfYouPrefer(#Qualifier("dataSourceWithPropFromProperties") #Autowired private DataSource dataSource2){
this.dataSource2 = dataSource2;
}

Issue with Declarative Transactions and TransactionAwareDataSourceProxy in combination with JOOQ

I have a data source configuration class that looks as follows, with separate DataSource beans for testing and non-testing environments using JOOQ. In my code, I do not use DSLContext.transaction(ctx -> {...} but rather mark the method as transactional, so that JOOQ defers to Spring's declarative transactions for transactionality. I am using Spring 4.3.7.RELEASE.
I have the following issue:
During testing (JUnit), #Transactional works as expected. A single method is transactional no matter how many times I use the DSLContext's store() method, and a RuntimeException triggers a rollback of the entire transaction.
During actual production runtime, #Transactional is completely ignored. A method is no longer transactional, and TransactionSynchronizationManager.getResourceMap() holds two separate values: one showing to my connection pool (which is not transactional), and one showing the TransactionAwareDataSourceProxy).
In this case, I would have expected only a single resource of type TransactionAwareDataSourceProxy which wraps my DB CP.
After much trial and error using the second set of configuration changes I made (noted below with "AFTER"), #Transactional works correctly as expected even during runtime, though TransactionSynchronizationManager.getResourceMap() holds the following value:
In this case, my DataSourceTransactionManager seems to not even know the TransactionAwareDataSourceProxy (most likely due to my passing it the simple DataSource, and not the proxy object), which seems to completely 'skip' the proxy anyway.
My question is: the initial configuration that I had seemed correct, but did not work. The proposed 'fix' works, but IMO should not work at all (since the transaction manager does not seem to be aware of the TransactionAwareDataSourceProxy).
What is going on here? Is there a cleaner way to fix this issue?
BEFORE (not transactional during runtime)
#Configuration
#EnableTransactionManagement
#RefreshScope
#Slf4j
public class DataSourceConfig {
#Bean
#Primary
public DSLContext dslContext(org.jooq.Configuration configuration) throws SQLException {
return new DefaultDSLContext(configuration);
}
#Bean
#Primary
public org.jooq.Configuration defaultConfiguration(DataSourceConnectionProvider dataSourceConnectionProvider) {
org.jooq.Configuration configuration = new DefaultConfiguration()
.derive(dataSourceConnectionProvider)
.derive(SQLDialect.POSTGRES_9_5);
configuration.set(new DeleteOrUpdateWithoutWhereListener());
return configuration;
}
#Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
#Bean
public DataSourceConnectionProvider dataSourceConnectionProvider(DataSource dataSource) {
return new DataSourceConnectionProvider(dataSource);
}
#Configuration
#ConditionalOnClass(EmbeddedPostgres.class)
static class EmbeddedDataSourceConfig {
#Value("${spring.jdbc.port}")
private int dbPort;
#Bean(destroyMethod = "close")
public EmbeddedPostgres embeddedPostgres() throws Exception {
EmbeddedPostgres embeddedPostgres = EmbeddedPostgresHelper.startDatabase(dbPort);
return embeddedPostgres;
}
#Bean
#Primary
public DataSource dataSource(EmbeddedPostgres embeddedPostgres) throws Exception {
DataSource dataSource = embeddedPostgres.getPostgresDatabase();
return new TransactionAwareDataSourceProxy(dataSource);
}
}
#Configuration
#ConditionalOnMissingClass("com.opentable.db.postgres.embedded.EmbeddedPostgres")
#RefreshScope
static class DefaultDataSourceConfig {
#Value("${spring.jdbc.url}")
private String url;
#Value("${spring.jdbc.username}")
private String username;
#Value("${spring.jdbc.password}")
private String password;
#Value("${spring.jdbc.driverClass}")
private String driverClass;
#Value("${spring.jdbc.MaximumPoolSize}")
private Integer maxPoolSize;
#Bean
#Primary
#RefreshScope
public DataSource dataSource() {
log.debug("Connecting to datasource: {}", url);
HikariConfig hikariConfig = buildPool();
DataSource dataSource = new HikariDataSource(hikariConfig);
return new TransactionAwareDataSourceProxy(dataSource);
}
private HikariConfig buildPool() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setDriverClassName(driverClass);
config.setConnectionTestQuery("SELECT 1");
config.setMaximumPoolSize(maxPoolSize);
return config;
}
}
AFTER (transactional during runtime, as expected, all non-listed beans identical to above)
#Configuration
#EnableTransactionManagement
#RefreshScope
#Slf4j
public class DataSourceConfig {
#Bean
public DataSourceConnectionProvider dataSourceConnectionProvider(TransactionAwareDataSourceProxy dataSourceProxy) {
return new DataSourceConnectionProvider(dataSourceProxy);
}
#Bean
public TransactionAwareDataSourceProxy transactionAwareDataSourceProxy(DataSource dataSource) {
return new TransactionAwareDataSourceProxy(dataSource);
}
#Configuration
#ConditionalOnMissingClass("com.opentable.db.postgres.embedded.EmbeddedPostgres")
#RefreshScope
static class DefaultDataSourceConfig {
#Value("${spring.jdbc.url}")
private String url;
#Value("${spring.jdbc.username}")
private String username;
#Value("${spring.jdbc.password}")
private String password;
#Value("${spring.jdbc.driverClass}")
private String driverClass;
#Value("${spring.jdbc.MaximumPoolSize}")
private Integer maxPoolSize;
#Bean
#Primary
#RefreshScope
public DataSource dataSource() {
log.debug("Connecting to datasource: {}", url);
HikariConfig hikariConfig = buildPoolConfig();
DataSource dataSource = new HikariDataSource(hikariConfig);
return dataSource; // not returning the proxy here
}
}
}
I'll turn my comments into an answer.
The transaction manager should NOT be aware of the proxy. From the documentation:
Note that the transaction manager, for example
DataSourceTransactionManager, still needs to work with the underlying
DataSource, not with this proxy.
The class TransactionAwareDataSourceProxy is a special purpose class that is not needed in most cases. Anything that is interfacing with your data source through the Spring framework infrastructure should NOT have the proxy in their chain of access. The proxy is intended for code that cannot interface with the Spring infrastructure. For example, a third party library that was already setup to work with JDBC and did not accept any of Spring's JDBC templates. This is stated in the same docs as above:
This proxy allows data access code to work with the plain JDBC API and
still participate in Spring-managed transactions, similar to JDBC code
in a J2EE/JTA environment. However, if possible, use Spring's
DataSourceUtils, JdbcTemplate or JDBC operation objects to get
transaction participation even without a proxy for the target
DataSource, avoiding the need to define such a proxy in the first
place.
If you do not have any code that needs to bypass the Spring framework then do not use the TransactionAwareDataSourceProxy at all. If you do have legacy code like this then you will need to do what you already configured in your second setup. You will need to create two beans, one which is the data source, and one which is the proxy. You should then give the data source to all of the Spring managed types and the proxy to the legacy types.

Spring Java Config. Use of PropertiesFactoryBean in the configuration file

I've the following configuration file
#Configuration
#ComponentScan(basePackages = "com.foo")
#EnableTransactionManagement
public class AppSpringConfiguration {
#Autowired
private Environment env;
#Autowired
private ApplicationContext appContext;
#Value("#{cvlExternalProperties['dbDriverClassName']}")
private String dbDriverName;
#Bean
public PropertiesFactoryBean cvlExternalProperties() {
PropertiesFactoryBean res = new PropertiesFactoryBean();
res.setFileEncoding("UTF-8");
res.setLocation(new FileSystemResource(env.resolvePlaceholders("${MY_ENV_VAR}") + "external.properties"));
return res;
}
#Bean
public BasicDataSource datasource() {
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName("myDriverClassName");
basicDataSource.setUrl("MyDbUrl");
basicDataSource.setUsername("myUser");
basicDataSource.setPassword("myPass");
return basicDataSource;
}
}
And in the external properties file I've
dbUrl=jdbc:mysql://localhost:3306/someDb
dbUser=someUser
dbPassword=somePass
dbDriverClassName=com.mysql.jdbc.Driver
In which way I can use the cvlProperties inside the datasource() method?
I've tried
env.getProperty("dbDriverClassName")
env.getProperty("#cvlProperties['dbDriverClassName']")
But I'm not able to retrieve the properties.
The field dbDriverName is correctly filled, so that means the bean declaration is ok.
I want to use the PropertyFactoryBean class because in this way I can specify the encoding to use.
If I use the the following annotation on the top of the configuration class
#PropertySource("file:${MY_ENV_VAR}/external.properties")
I'm able to retrieve the properties with this piece of code
env.getProperty("dbDriverClassName")
But the encoding used by the PropertySource annotation is the windows default, and for me is not correct.
Can you help me?
At the moment the solution(that I don't love so much) is to declare the properties by using the annotation #Value
#Value("#{cvlExternalProperties['dbDriverClassName']}")
private String dbDriverClassName;
and then using it inside the java class

Creating #Autowire like annotation for injecting properties

I am using Spring 3 and also heavily utilize the well known #Autowire annotation. I would like to create a new annotation, let's call it #Property that autowires Java properties from set by .property files or vm arguments.
Considering the following class
class A {
#Property("my.a")
private int a;
}
if the property my.a is present, the property A.a is set.
Is such an annotation maybe already existing? If not I am aiming to create one, as mentioned above. Are the utilities given by spring to achieve my goal? I think about creating a BeanPostProcessor ...
Thanks for your hints!
There's already such an annotation - #Value
You should just define a PropertyPlaceHolderConfigurer, and configure it to resolve system properties.
Refer http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/
you can use #ImportResource to import XML configuration files. Then use context:property-placeholder to load properties
#Configuration
#ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
private #Value("${jdbc.url}") String url;
private #Value("${jdbc.username}") String username;
private #Value("${jdbc.password}") String password;
public #Bean DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}

Categories