Configure HikariCp dataSource with HikariConfig in mybatis in xml mapper configuration - java

I am trying to configure HikariCp in MyBatis using XML configuration
I want to know how to set the hikariCongig object in object in the mapper configuration.
my config looks like this :
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="com.xyz.config.HikariCPDataSourceFactory" >
<property name="jdbcUrl" value="jdbc:postgresql://localhost:5432/beta-prod-db" />
<property name="username" value="postgres" />
<property name="password" value="${password}" />
<property name="poolName" value="test"/>
<property name="maxPoolSize" value="20" />
<property name="registerMbeans" value="true"/>
<property name="minimumIdle" value="5"/>
</dataSource>
</environment>
HikariCPDataSourceFactory.java
public class HikariCPDataSourceFactory extends PooledDataSourceFactory {
public HikariCPDataSourceFactory() {
// HikariConfig hikariConfig = new HikariConfig();
this.dataSource = new HikariDataSource();
}
}
I don't find any online article that shows how to set the hikariConfig object in the hikariDataSource object through XML configuration.
using Spring I can create a bean for hikariConfig and pass it as a parameter in the hikariDataSource object, but here I am not using spring so need to find a way with XML.
Without the hikariConfig object, if I try to get the HikariPoolMXBean object from dataSource I get the exception
org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
HikariCP 1.4.0 MBean InstanceNotFoundException
this article says it only works when I set the hikariConfig Object

You need to implement DataSourceFactory and pass the properties you specified in your MyBatis XML Configuration file to the new data source:
public class HikariCPDataSourceFactory implements DataSourceFactory {
private HikariDataSource dataSource;
#Override
public void setProperties(Properties props) {
HikariConfig config = new HikariConfig(props);
this.dataSource = new HikariDataSource(config);
}
#Override
public DataSource getDataSource() {
return dataSource;
}
}

i couldnt find a way to configure the hikariConfig in xml
Here is the workaround i used that works well for me.
HikariDataSource hikariDataSource = null;
HikariConfig hikariConfig = new HikariConfig();
dataSource.copyStateTo(hikariConfig);
hikariDataSource = new HikariDataSource(hikariConfig);
once i get the dataSource object i copy the state to a hikariConfig object and create new dataSource object using it.
Also we can make it singleton so only one instance is created.

Related

Spring 4 using class based database initialization instead of xml bean

In our Spring 4 application, we currently configure database connection in applicationContext.xml :
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="poolName" value="springHikariCP" />
<property name="dataSourceClassName" value="org.postgresql.ds.PGSimpleDataSource" />
<property name="maximumPoolSize" value="10" />
<property name="idleTimeout" value="30000" />
<property name="dataSourceProperties">
<props>
<prop key="url">jdbc:postgresql://google/mydb?cloudSqlInstance=project:region:myinstance&socketFactory=com.google.cloud.sql.postgres.SocketFactory</prop>
<prop key="user">postgres</prop>
<prop key="password">password</prop>
</props>
</property>
</bean>
Instead on defining this in applicationContext.xml, can I define the database configuration in a class such as the following:
HikariConfig config = new HikariConfig();
config.setJdbcUrl(JDBC_URL);
config.setUsername(DB_USER);
config.setPassword(DB_PASS);
....
Is it possible to do this?
You can define properties on HikariDataSource
public DataSource getDataSource(){
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(JDBC_URL);
dataSource.setUsername(DB_USER);
dataSource.setPassword("DB_PASS);
return dataSource;
}
public class HikariDataSource extends HikariConfig implements DataSource, Closeable
Then declare your method as a #Bean in #Configuration class
#Bean
public DataSource dataSource() {
return DataSourceClass.getDataSource();
}
This is a king of more general solution, not specific for your DataSource but it can be useful:
#Configuration
public class DataSourceConfig {
#Bean
public DataSource getDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:test");
dataSourceBuilder.username("SA");
dataSourceBuilder.password("");
return dataSourceBuilder.build();
}
}
What you actually need is a DataSource bean (in XML you define a bean right?) and also put it into #Configuration class so the Spring could pick it up.
What you put in that method should return pre-configured for your specific case data source.
Also, you could try something like this:
#Bean
public DataSource dataSource() throws SQLException {
HikariConfig config = new HikariConfig("/hikari.properties");
HikariDataSource dataSource = new HikariDataSource(config);
return dataSource;
}
But then you need hikari.properties file in the classpath. Example:
driverClassName=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/myDb
connectionTestQuery=SELECT 1
maximumPoolSize=20
username=...
password=...
Remember to keep #Bean in #Configuration :)
Simply annotate a class with #Configuration, making sure that this class is scanned by Spring when the application starts.
Within this class declare a datasource as #Bean in this way:
#Configuration
public class DataSourceConfig {
// More code...
#Bean(name="datasource")
public DataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(org.postgresql.Driver.class.getName());
hikariConfig.setJdbcUrl("jdbc:postgresql://....");
hikariConfig.setUsername("postgres");
hikariConfig.setPassword("password");
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setIdleTimeout(30000);
hikariConfig.setPoolName("springHikariCP");
HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
return hikariDataSource;
}
// More code...
}
And that's all.

Spring utilizing Mongo implementation over JPA

I am fairly new to the Spring Framework and have been having some trouble setting up the project I am currently working on. I need to be able to connect to two different databases one being MongoDB and the other being MSSQL. I am using JPA to connect to the MSSQL.
The problem that I am encountering is that it appears to be trying to make calls to the Mongo database when I want it to make calls to the MSSQL and I'm not really sure how to tell it what to read from. I have seen the posts advising to use the #Qualifier annotation to direct it to the correct implementation, but I don't think that that will work for my case.
#RestController
#RequestMapping("/software")
public class SoftwareEndpoint {
#Autowired
SoftwareRepository repo;
/**********************************************************************************
********************************MSSQL calls****************************************
***********************************************************************************/
#RequestMapping(value="/all",method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON)
String getAllSoftware(){
System.out.println("Here1");
List<Software> allSoftware = (List<Software>) repo.findAll();
System.out.println("Here2");
//rest of method and class
Above shows a snippet of my controller class that has an instance of my SoftwareRepository. I also print to the out stream before and after the db call.
The out stream only shows "Here1", goes on to print out this line:
2016-10-04 07:35:39.810 INFO 4236 --- [nio-8080-exec-2] org.mongodb.driver.cluster : No server chosen by ReadPreferenceServerSelector{readPreference=primary} from cluster description ClusterDescription{type=UNKNOWN, connectionMode=SINGLE, all=[ServerDescription{address=localhost:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused: connect}}]}. Waiting for 30000 ms before timing out
and then throws an exception on timeout.
I do not have a mongo instance running locally, however there will be on where the application is being deployed, but I don't believe that this is the problem because on hitting that endpoint, it shouldn't be making a call to the Mongo database, it should be trying to reach out to MSSQL.
TLDR: How do I specify which database implementation for Spring to use for a specific repository or database call?
You can connect to different databases in spring based on the configuration in context.
The below code is for connecting to MySql and Mongo DB. You can substitute MySql with MSSQL provided you have the JDBC for it. Check http://jdbforms.sourceforge.net/UsersGuide/html/ch20s02.html for what the properties for JDBC connection mean.
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="mySqldataSource" /> <!-- Change the datasource to MSSQL-->
</bean>
<bean id="mySqldataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="removeAbandoned">
<value>true</value>
</property>
<property name="removeAbandonedTimeout">
<value>30</value>
</property>
<property name="driverClassName">
<value>MSSQL_DRIVER_CLASS_NAME</value>
</property>
<property name="url">
<value>MSSQL_DATABASE_URL</value>
</property>
<property name="username">
<value>MSSQL_DB_USER_NAME</value>
</property>
<property name="password">
<value>MSSQL_DB_PASSWORD</value>
</property>
<property name="maxIdle">
<value>10</value>
</property>
<property name="maxActive">
<value>10</value>
</property>
<property name="maxWait">
<value>100000</value>
</property>
<property name="testOnBorrow">
<value>false</value>
</property>
<property name="testWhileIdle">
<value>false</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>60000</value>
</property>
<property name="minEvictableIdleTimeMillis">
<value>60000</value>
</property>
<property name="numTestsPerEvictionRun">
<value>1</value>
</property>
<property name="defaultTransactionIsolation" value="1" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="1" />
</bean>
<bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"></bean>
Below is for connecting to mongodb
<mongo:db-factory dbname="mongoDbName" host="mongoServer" port="mongoPort"/>
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</bean>
<mongo:repositories base-package="com.test.repoPackage"/> <!-- Package containing the mongo repository interfaces -->
Now you can use the repositories provided by spring.
EDIT 1: Suppose name of config is springConfig.properties. In the above example for the properties dbname, host and port in mongo:db-factory, you would want the values to be configured in springConfig.properties. So lets name them below:
mongoServer = xxx.xx.xxx.xxx
mongoPort = 27017
mongoDb = testDb
Now the context file needs to be modified to import the springConfig.properties. this is done as below in the context file:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
<property name="locations" >
<list>
<value>classpath:/log4j.properties</value>
<value>classpath:/springConfig.properties</value>
</list>
</property>
</bean>
The bean mongo:db-factory would now look like:
<mongo:db-factory dbname="${mongoDb}" host="${mongoServer}" port="${mongoPort}"/>
Notice that the "keys" from config (dbname, host and port) are represented insde ${}. This will replace with values in config for the keys.
You need to have two separated config for JPA. Don't forget to disable JPA auto configuration.
#SpringBootApplication(
exclude={
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
}
)
Below is example for two different sql database. Could be easily adapted for your needs (when second datasource is mongo).
First one:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "sellitEntityManagerFactory",
transactionManagerRef = "sellitTransactionManager",
basePackages = { "co.sellit.core.api.repository.sellit" }
)
public class JpaSellitConfig {
#Bean
#ConfigurationProperties(prefix="spring.datasource.sellit")
public DataSourceProperties sellitDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties(prefix="spring.hikaricp.sellit")
public HikariConfig sellitHikariConfig() {
HikariConfig hikariConfig = new HikariConfig();
return hikariConfig;
}
#Bean
public DataSource sellitDataSource(
#Qualifier("sellitHikariConfig") HikariConfig sellitHikariConfig,
#Qualifier("sellitDataSourceProperties") DataSourceProperties sellitDataSourceProperties) {
sellitHikariConfig.setDriverClassName(sellitDataSourceProperties.getDriverClassName());
sellitHikariConfig.setJdbcUrl(sellitDataSourceProperties.getUrl());
sellitHikariConfig.setUsername(sellitDataSourceProperties.getUsername());
sellitHikariConfig.setPassword(sellitDataSourceProperties.getPassword());
sellitHikariConfig.setConnectionTestQuery("SELECT 1");
HikariDataSource hikariDataSource = new HikariDataSource(sellitHikariConfig);
return hikariDataSource;
}
#Bean
#ConfigurationProperties(prefix="spring.jpa.sellit")
public JpaVendorAdapter sellitJpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
return jpaVendorAdapter;
}
#Bean
#Autowired
public EntityManagerFactory sellitEntityManagerFactory(
#Qualifier("sellitDataSource") DataSource sellitDataSource,
#Qualifier("sellitJpaVendorAdapter") JpaVendorAdapter sellitJpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean lemf = new LocalContainerEntityManagerFactoryBean();
lemf.setDataSource(sellitDataSource);
lemf.setJpaVendorAdapter(sellitJpaVendorAdapter);
lemf.setPackagesToScan("co.sellit.core.api.entity.sellit");
lemf.setPersistenceUnitName("sellitPersistenceUnit");
lemf.afterPropertiesSet();
return lemf.getObject();
}
#Bean
#Autowired
public EntityManager sellitDataSourceEntityManager(
#Qualifier("sellitEntityManagerFactory") EntityManagerFactory sellitEntityManagerFactory) {
return sellitEntityManagerFactory.createEntityManager();
}
#Bean
#Autowired
#Qualifier("sellitTransactionManager")
public PlatformTransactionManager sellitTransactionManager(
#Qualifier("sellitEntityManagerFactory") EntityManagerFactory sellitEntityManagerFactory) {
return new JpaTransactionManager(sellitEntityManagerFactory);
}
}
Second one:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "ofEntityManagerFactory",
transactionManagerRef = "ofTransactionManager",
basePackages = { "co.sellit.core.api.repository.openfire" }
)
public class JpaOpenfireConfig {
#Bean
#ConfigurationProperties(prefix="spring.datasource.openfire")
public DataSourceProperties ofDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties(prefix="spring.hikaricp.openfire")
public HikariConfig ofHikariConfig() {
HikariConfig hikariConfig = new HikariConfig();
return hikariConfig;
}
#Bean
public DataSource ofDataSource(
#Qualifier("ofHikariConfig") HikariConfig ofHikariConfig,
#Qualifier("ofDataSourceProperties") DataSourceProperties ofDataSourceProperties) {
ofHikariConfig.setDriverClassName(ofDataSourceProperties.getDriverClassName());
ofHikariConfig.setJdbcUrl(ofDataSourceProperties.getUrl());
ofHikariConfig.setUsername(ofDataSourceProperties.getUsername());
ofHikariConfig.setPassword(ofDataSourceProperties.getPassword());
ofHikariConfig.setConnectionTestQuery("SELECT 1");
HikariDataSource hikariDataSource = new HikariDataSource(ofHikariConfig);
return hikariDataSource;
}
#Bean
#ConfigurationProperties(prefix="spring.jpa.openfire")
public JpaVendorAdapter ofJpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
return jpaVendorAdapter;
}
#Bean
#Autowired
public EntityManagerFactory ofEntityManagerFactory(
#Qualifier("ofDataSource") DataSource ofDataSource,
#Qualifier("ofJpaVendorAdapter") JpaVendorAdapter ofJpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean lemf = new LocalContainerEntityManagerFactoryBean();
lemf.setDataSource(ofDataSource);
lemf.setJpaVendorAdapter(ofJpaVendorAdapter);
lemf.setPackagesToScan("co.sellit.core.api.entity.openfire");
lemf.setPersistenceUnitName("ofPersistenceUnit");
lemf.afterPropertiesSet();
return lemf.getObject();
}
#Bean
#Autowired
public EntityManager ofDataSourceEntityManager(
#Qualifier("ofEntityManagerFactory") EntityManagerFactory ofEntityManagerFactory) {
return ofEntityManagerFactory.createEntityManager();
}
#Bean
#Autowired
#Qualifier("ofTransactionManager")
public PlatformTransactionManager ofTransactionManager(
#Qualifier("ofEntityManagerFactory") EntityManagerFactory ofEntityManagerFactory) {
return new JpaTransactionManager(ofEntityManagerFactory);
}
}
So repositories from package co.sellit.core.api.repository.sellit will use sellit db
But repositories from package co.sellit.core.api.repository.openfire will use openfire database.
UPDATE (xml config version)
<bean id="openfireDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/openfire?characterEncoding=UTF-8" />
</bean>
<bean id="sellitDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/sellit?characterEncoding=UTF-8" />
</bean>
<bean id="openfireSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="openfireDataSource" />
<property name="packagesToScan" value="co.sellit.core.api.repository.openfire" />
<property name="hibernateProperties">
<props>
...
</props>
</property>
</bean>
<bean id="sellitSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="sellitDataSource" />
<property name="packagesToScan" value="co.sellit.core.api.repository.sellit" />
<property name="hibernateProperties">
<props>
...
</props>
</property>
</bean>
<bean id="openfireTxnManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="openfireSessionFactory" />
</bean>
<bean id="sellitTxnManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sellitSessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="openfireTxnManager" />
<tx:annotation-driven transaction-manager="sellitTxnManager" />

How to inject values from Java Class to Datasource Bean in spring

This might sound like a novice question. I want to inject datasource properties (which I am getting at runtime) and inject it to the bean..
I have a method in my javaclass...
public <String,String>map myMethod(Map<String, String> model) {
Map mapA = new HashMap();
mapA.put("username", "element 1");
mapA.put("password", "element 2");
mapA.put("host", "element 3");
return map;
}
I want to inject these values to my datasource bean in application-context.xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value=""/> // inject values here
<property name="url" />
<property name="username" />
<property name="password" />
</bean>
I have seen numerous example on injecting values to beans using properties file but I could not figure out on how to inject a value from java class to the bean properties.
Thanks
You need to create a #Configuration class with a method annotated with #Bean returning an instance of org.apache.commons.dbcp.BasicDataSource.
#Configuration
public class DatasourceConfiguration {
#Bean
public BasicDataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName(""); // you can call your code here
ds.setUrl(""); // to get these configuration values
ds.setUsername("");
ds.setPassword("");
return ds;
}
}
It can be a not so elegant solution, but what about this approach?
You can try to return a String from your method.
#Configuration
public class DatasourceConfiguration2 {
#Bean
public String getDataSourceSetting() {
Map<String, String> map = myMethod(model); //assuming that you are not able to edit the original method
StringBuilder sb = new StringBuilder();
for (Entry<String, String> e : map.entrySet()) {
sb.append(e.getKey()).append('=').append(e.getValue()).append(';');
}
}
}
In your xml you can define the property like:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="connectionProperties" value="dataSourceSetting"/>
</bean>
Based on dbcp api:
The "user" and "password" properties will be added explicitly, so they do not need to be included here.
Checking the source code you can see if user and password are null a message like log("DBCP DataSource configured without a 'username'"); will be printed. But the property will be available there.
Finally, in case of url property, there is no option, you need to set it up explicitly.

How to get Jndi name in java class in Spring

I want to get a JNDI value in my java conf Spring.
the context.xml file:
<Environment name="foo" type="java.lang.String" value="bar" />
the xml spring config:
<jee:jndi-lookup id="foobar" jndi-name="java:comp/env/foo" default-value="nothing"/>
the java spring config:
#Bean
public String foobar() {
???
}
If someone could you give me an example it'll be very useful. Thanks
Edit:
Try autowiring JndiObjectFactoryBean it will have a getJndiName() method, which is inherited from JndiObjectLocator will expose your jndi name
Try this
#Bean
public DataSource dataSource() {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
dsLookup.setResourceRef(true);
DataSource dataSource = dsLookup.getDataSource("jdbc/yourJdbcGoesHere");
return dataSource;
}

Dynamically create spring beans and alter properties on existing beans

I sucessfully managed to implement dynamic changing of database connections by following http://blog.springsource.com/2007/01/23/dynamic-datasource-routing/ article.
But now the problem is, I have a list of database urls in a configuration file that is managed by a legacy application.
Is there a way to create beans in that Spring context from a list of values (i.e. Year2011DataSource, Year2012DataSource,...) and populate map of the dataSource bean with those beans that were just created?
<!-- Property file located in the legacy application's folder -->
<context:property-placeholder location="file:///D:/config.properties" />
<!-- Shared data source properties are read from the config.properties file -->
<bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" abstract="true">
<property name="driverClassName" value="${db.driver}" />
<property name="username" value="${db.user}" />
<property name="password" value="${db.password}" />
</bean>
<!-- Database urls by year -->
<bean id="Year2012DataSource" parent="parentDataSource">
<property name="url" value="jdbc:sqlserver://localhost;databaseName=DbName_v570_2012" />
</bean>
<bean id="Year2011DataSource" parent="parentDataSource">
<property name="url" value="jdbc:sqlserver://localhost;databaseName=DbName_v570_2011" />
</bean>
<bean id="Year2010DataSource" parent="parentDataSource">
<property name="url" value="jdbc:sqlserver://localhost;databaseName=DbName_v570_2010" />
</bean>
<!-- ... and so on, these should instead be populated dynamically ... -->
<!-- DbConnectionRoutingDataSource extends AbstractRoutingDataSource -->
<bean id="dataSource" class="someProject.DbConnectionRoutingDataSource">
<property name="targetDataSources">
<map key-type="int">
<entry key="2011" value-ref="Year2011DataSource" />
<entry key="2010" value-ref="Year2010DataSource" />
<!-- ... and so on, these also should instead be populated dynamically ... -->
</map>
</property>
<property name="defaultTargetDataSource" ref="Year2012DataSource" />
</bean>
A good fit for this requirement I think is a custom BeanFactoryPostProcessor - read in the legacy configuration and generate the datasources in the custom bean factory post processor:
class MyDatasourceRegisteringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//Read in details from legacy properties.. build custom bean definitions and register with bean factory
//for each legacy property...
BeanDefinitionBuilder datasourceDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(BasicDataSource.class).addPropertyValue("url", "jdbc..");
beanFactory.registerBeanDefinition(datasourceDefinitionBuilder.getBeanDefinition());
}
}
As far as I know, there is no out-of-the-box solution using XML configuration. However, a simple solution to achieve this is described in this answer using FactoryBean abstraction in Spring.
I can tell you annotation approach. I would add urls and configuration in properties file and do something like following :
#Bean(name="dataSourceMap")
public Map<String, DataSource> dataSourceMap(DataSource dataSource2011, DataSource dataSource2012) {
// read properties from properties file and create map of datasource
Map<DataSource> map = new HashMap<>();
map.put("dataSource2011",dataSource2011);
map.put("dataSource2012",dataSource2012);
//map.put("dataSource2013",dataSource2013);
return map;
}
#Bean(name="dataSource2011",destroyMethod="close")
public DataSource dataSource() {
// read properties from properties file and create map of
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url2011);
dataSource.setUsername(username2011);
dataSource.setPassword(password2011);
return dataSource;
}
#Bean(name="dataSource2012",destroyMethod="close")
public DataSource dataSource() {
// read properties from properties file and create map of
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url2012);
dataSource.setUsername(username2012);
dataSource.setPassword(password2012);
return dataSource;
}
============================================
By following Biju's tip I've got everything working like this:
============================================
"Database urls by year" section in the spring configuration is no more, beans are created in the BeanFactoryPostProcessor.
"dataSource" bean has it's properties set to dummy data that is replaced in the BeanFactoryPostProcessor:
<bean id="dataSource" class="someProject.DbConnectionRoutingDataSource">
<property name="targetDataSources">
<map key-type="String">
<!-- Will be filled from the DatasourceRegisteringBeanFactoryPostProcessor -->
</map>
</property>
<property name="defaultTargetDataSource" value="java:jboss/datasources/ExampleDS" />
</bean>
And this is the BeanFactoryPostProcessor implementation:
#Component
class DatasourceRegisteringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
InitialContext ic = new InitialContext();
// read the list of available JNDI connections
NamingEnumeration<?> list = ic.listBindings(getJndiDSRoot());
HashSet<String> jndiDataSources = new HashSet<String>();
while (list.hasMore()) {
/*... (ommitted for simplicity) ...*/
connectionsList.put(key, value);
}
BeanDefinitionRegistry factory = (BeanDefinitionRegistry) beanFactory;
BeanDefinitionBuilder datasourceDefinitionBuilder;
// Create new beans
for (Entry<Integer, String> e : connectionsList.entrySet()) {
datasourceDefinitionBuilder = BeanDefinitionBuilder
.childBeanDefinition("parentDataSource")
.addPropertyValue("url", e.getValue());
factory.registerBeanDefinition("Year" + e.getKey() + "DataSource",
datasourceDefinitionBuilder.getBeanDefinition());
}
// Configure the dataSource bean properties
MutablePropertyValues mpv = factory.getBeanDefinition("dataSource").getPropertyValues();
// Here you can set the default dataSource
mpv.removePropertyValue("defaultTargetDataSource");
mpv.addPropertyValue("defaultTargetDataSource",
new RuntimeBeanReference("Year" + defaultYear + "DataSource"));
// Set the targetDataSource properties map with the list of connections
ManagedMap<Integer, RuntimeBeanReference> mm = (ManagedMap<Integer, RuntimeBeanReference>) mpv.getPropertyValue("targetDataSources").getValue();
mm.clear();
// Fill the map with bean references to the newly created beans
for (Entry<Integer, String> e : connectionsList.entrySet()) {
mm.put(e.getKey(), new RuntimeBeanReference("Year" + e.getKey() + "DataSource")));
}
}
}

Categories

Resources