I am working on a project which uses spring + hibernate + mysql and c3p0 for connection pooling.
Currently the properties for the connection pool are loaded via properties defined outside the src. (eg: ${db_uname})
Everything starts fine when we create the spring bean.
It might so happen that the database to which we have connected is inaccessible for some reason, and we would like to switch hosts.
Need to implement a call back, where it is supposed to connect to the new host and re-initialize the pool
Any pointers on how to override the existing data source / connection pool gracefully would be of great help.
Here is how my spring config file looks like
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<!-- Component scans -->
<import resource="component-scans-1.xml" />
<import resource="component-scans-2.xml" />
<util:properties id="serviceManagerProperties"
location="classpath:servicemanagers.properties" />
<!-- Properties file -->
<context:property-placeholder location="classpath:database.config.properties,classpath:framework.properties" />
<!-- context:annotation-config / -->
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<context:mbean-server id="mbeanServer" />
<context:mbean-export server="mbeanServer" default-domain="a.b.c" />
<bean id="cacheManager" factory-method="getInstance"
class="net.sf.ehcache.CacheManager" />
<bean class="net.sf.ehcache.management.ManagementService" init-method="init">
<constructor-arg ref="cacheManager" />
<constructor-arg ref="mbeanServer" />
<constructor-arg value="false" />
<constructor-arg value="false" />
<constructor-arg value="false" />
<constructor-arg value="true" />
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaAdapter" />
<property name="persistenceXmlLocation" value="classpath*:META-INF/framework-persistence.xml" />
<property name="persistenceUnitName" value="PU-NAME" />
<property name="jpaProperties">
<props>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
<prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
<prop key="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
</prop>
<prop key="hibernate.ejb.cfgfile">hibernate.cfg.xml</prop>
<prop key="hibernate.generate_statistics">true</prop>
<!-- CONNECTION SETTINGS -->
<prop key="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</prop>
<prop key="hibernate.connection.url">
jdbc:mysql://${dbhost}/${dbschema}?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8
</prop>
<prop key="hibernate.connection.username">${dbuser}</prop>
<prop key="hibernate.connection.password">${dbpass}</prop>
<!-- CONNECTION POOLING -->
<prop key="hibernate.connection.provider_class">
org.hibernate.connection.C3P0ConnectionProvider
</prop>
<prop key="hibernate.c3p0.maxPoolSize">${hibernate.c3p0.maxSize}</prop>
<prop key="hibernate.c3p0.minPoolSize">${hibernate.c3p0.minSize}</prop>
<prop key="hibernate.c3p0.acquireIncrement">${hibernate.c3p0.acquireIncrement}</prop>
<prop key="hibernate.c3p0.idleConnectionTestPeriod">${hibernate.c3p0.idleTestPeriod}</prop>
<prop key="hibernate.c3p0.maxStatements">${hibernate.c3p0.maxStatements}</prop>
<prop key="hibernate.c3p0.timeout">${hibernate.c3p0.timeout}</prop>
</props>
</property>
</bean>
<bean id="jpaAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
Assuming the database schema is right, lets say i get the database credentials and the host information in the event, i need to 're-set' the connection pool.
To configure your own DataSource , define a @Bean of that type in your configuration. Spring Boot reuses your DataSource anywhere one is required, including database initialization. If you need to externalize some settings, you can bind your DataSource to the environment (see “Section 25.8.
Getting and Using Pooled Connections. A connection pool is a cache of database connection objects. The objects represent physical database connections that can be used by an application to connect to a database. At run time, the application requests a connection from the pool.
Spring boot datasource configuration is nothing but the factory of connection which was used in a physical data source. Spring boot datasource uses the database credential to set up connections between the database server, it is alternative to the facility of Driver Manager.
AbstractRoutingDataSource is a good choice.
Xml or annotation used like this:
<bean id="ds1" class="..c3p0.DataSource">
...
</bean>
<bean id="ds2" class="..c3p0.DataSource">
...
</bean>
<bean id="dataSource" class="..xxx.RoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="ds1" value-ref="ds1"/>
<entry key="ds2" value-ref="ds2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="ds1"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
...
</bean>
Then build a class to determine current datasoruce.
public class RoutingDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> holder = new ThreadLocal<String>();
protected Object determineCurrentLookupKey()
{
return holder.get();
}
public static void clear(){
holder.remove();
}
public static void setDataSourceKey(String key){
holder.set(key);
}
}
By the way, the 'try-finally' statement is boring!
try{
RoutingDataSource.setDataSourceKey("ds1");
myDao.doXXX();
}finally{
RoutingDataSource.clear();
}
<beans:bean id="dataSource"
class="org.springframework.aop.framework.ProxyFactoryBean">
<beans:property name="targetSource" ref="swappableDataSource" />
</beans:bean>
<beans:bean id="dummyDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close" />
<beans:bean name="swappableDataSource"
class="org.springframework.aop.target.HotSwappableTargetSource">
<beans:constructor-arg ref="dummyDataSource" />
</beans:bean>
and some where in your code you can do this.
@Autowired
HotSwappableTargetSource swapable;
public void changeDatasource() throws Exception
{
swapable.swap(createNewSource();
}
ComboPooledDataSource createNewSource() throws Exception {
ComboPooledDataSource ds2 = new ComboPooledDataSource();
ds2.setJdbcUrl(url);
ds2.setDriverClass(driver);
ds2.setUser(username);
ds2.setPassword(password);
return ds2;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With