Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing datasource connection url runtime

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&amp;useUnicode=true&amp;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.

like image 636
Abhilash L L Avatar asked Jan 28 '13 04:01

Abhilash L L


People also ask

How do I change DataSource in spring boot?

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.

What is get pooled connection from DataSource?

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.

What is a DataSource spring?

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.


2 Answers

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();
}
like image 70
imxylz Avatar answered Sep 28 '22 01:09

imxylz


<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;
}
like image 37
arshid dar Avatar answered Sep 28 '22 00:09

arshid dar