http://blog.youkuaiyun.com/kindazrael/article/details/7285336
在大型的应用中,为了提高数据库的水平伸缩性,对多个数据库实例进行管理,需要配置多数据源。在Spring框架被广泛运用的今天,可以很简单的运用Spring中的特性配置动态多数据。
1. 首先配置一个基于c3p0.ComboPooledDataSource的数据源A,数据源B.
daoContext.xml
- <bean id="dataSourceA" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
- <property name="driverClass" value="${jdbc.driver}"></property>
- <property name="jdbcUrl" value="${jdbc.ur.al}?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8"></property>
- <property name="user" value="${jdbc.user}"></property>
- <property name="password" value="${jdbc.password}"></property>
- <property name="minPoolSize" value="${jdbc.miniPoolSize}" />
- <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
- <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
- <property name="maxIdleTime" value="${jdbc.maxIdleTime}"/>
- <property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
- <property name="acquireRetryAttempts" value="${jdbc.acquireRetryAttempts}"/>
- <property name="acquireRetryDelay" value="${jdbc.acquireRetryDelay}"/>
- <property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
- <property name="checkoutTimeout" value="${jdbc.checkoutTimeout}"/>
- </bean>
- <bean id="dataSourceB" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
- <property name="driverClass" value="${jdbc.driver}"></property>
- <property name="jdbcUrl" value="${jdbc.url.b}?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8"></property>
- <property name="user" value="${jdbc.user}"></property>
- <property name="password" value="${jdbc.password}"></property>
- <property name="minPoolSize" value="${jdbc.miniPoolSize}" />
- <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
- <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
- <property name="maxIdleTime" value="${jdbc.maxIdleTime}"/>
- <property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
- <property name="acquireRetryAttempts" value="${jdbc.acquireRetryAttempts}"/>
- <property name="acquireRetryDelay" value="${jdbc.acquireRetryDelay}"/>
- <property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
- <property name="checkoutTimeout" value="${jdbc.checkoutTimeout}"/>
- </bean>
<bean id="dataSourceA" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.ur.al}?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="minPoolSize" value="${jdbc.miniPoolSize}" />
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
<property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
<property name="maxIdleTime" value="${jdbc.maxIdleTime}"/>
<property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
<property name="acquireRetryAttempts" value="${jdbc.acquireRetryAttempts}"/>
<property name="acquireRetryDelay" value="${jdbc.acquireRetryDelay}"/>
<property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
<property name="checkoutTimeout" value="${jdbc.checkoutTimeout}"/>
</bean>
<bean id="dataSourceB" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url.b}?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="minPoolSize" value="${jdbc.miniPoolSize}" />
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
<property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
<property name="maxIdleTime" value="${jdbc.maxIdleTime}"/>
<property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
<property name="acquireRetryAttempts" value="${jdbc.acquireRetryAttempts}"/>
<property name="acquireRetryDelay" value="${jdbc.acquireRetryDelay}"/>
<property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
<property name="checkoutTimeout" value="${jdbc.checkoutTimeout}"/>
</bean>
3. 接着扩展一个Spring提供的AbstractRoutingDataSource,Override 其中的 determineCurrentLookupKey方法实现数据源的route.
- package datasource;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- public class DynamicDataSource extends AbstractRoutingDataSource{
- @Override
- protected Object determineCurrentLookupKey() {
- return CustomerContextHolder.getCustomerType();
- }
- }
package datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
}
而其中的CustomerContextHolder这是开发人员自己实现的一个封装了ThreadLocal类型的ContextHolder。
- package datasource;
- public class CustomerContextHolder {
- public static final String DATA_SOURCE_A = "dataSourceA";
- public static final String DATA_SOURCE_B = "dataSourceB";
- private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
- public static void setCustomerType(String customerType) {
- contextHolder.set(customerType);
- }
- public static String getCustomerType() {
- return contextHolder.get();
- }
- public static void clearCustomerType() {
- contextHolder.remove();
- }
- }
package datasource;
public class CustomerContextHolder {
public static final String DATA_SOURCE_A = "dataSourceA";
public static final String DATA_SOURCE_B = "dataSourceB";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
return contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
4.接下来就是在我们上面的daoContext.xml将这个DynamicDataSource Bean加入进去,同时配置targetDataSources的 Map映射。
- <bean id="dynamicDataSource" class="datasource.DynamicDataSource" >
- <!-- 通过key-value的形式来关联数据源 -->
- <property name="targetDataSources">
- <map key-type="java.lang.String">
- <entry value-ref="dataSourceA" key="dataSourceA"></entry>
- <entry value-ref="dataSourceB" key="dataSourceB"></entry>
- </map>
- </property>
- <property name="defaultTargetDataSource" ref="dataSourceA" >
- </property>
- </bean>
<bean id="dynamicDataSource" class="datasource.DynamicDataSource" >
<!-- 通过key-value的形式来关联数据源 -->
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataSourceA" key="dataSourceA"></entry>
<entry value-ref="dataSourceB" key="dataSourceB"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceA" >
</property>
</bean>
5. 如何是用这个动态的多数据源呢? 其实很简单,因为我们的DynamicDataSource 是继承与AbstractRoutingDataSource,而AbstractRoutingDataSource又是继承于org.springframework.jdbc.datasource.AbstractDataSource,显然的AbstractDataSource实现了统一的DataSource接口,所以我们的DynamicDataSource 同样可以方便的当一个DataSource使用,下面拿Hibernate做例子:
</pre><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><strong>[java]</strong> <a target=_blank title="view plain" class="ViewSource" href="http://blog.youkuaiyun.com/kindazrael/article/details/7285336#">view plain</a><a target=_blank title="copy" class="CopyToClipboard" href="http://blog.youkuaiyun.com/kindazrael/article/details/7285336#">copy</a><a target=_blank title="print" class="PrintSource" href="http://blog.youkuaiyun.com/kindazrael/article/details/7285336#">print</a><a target=_blank title="?" class="About" href="http://blog.youkuaiyun.com/kindazrael/article/details/7285336#">?</a></div></div><ol class="dp-j"><li class="alt"><span><span><bean id=</span><span class="string">"sessionFactory"</span><span> </span><span class="keyword">class</span><span>=</span><span class="string">"org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"</span><span>> </span></span></li><li><span> <!-- 可以看到和 普通的dataSource用法一样 --> </span></li><li class="alt"><span> <property name=<span class="string">"dataSource"</span><span> ref=</span><span class="string">"dynamicDataSource"</span><span> /> </span></span></li><li><span> <property name=<span class="string">"configLocations"</span><span> value=</span><span class="string">"classpath:hibernate.cfg.xml"</span><span> /> </span></span></li><li class="alt"><span> <property name=<span class="string">"hibernateProperties"</span><span>> </span></span></li><li><span> <props> </span></li><li class="alt"><span> <prop key=<span class="string">"hibernate.dialect"</span><span>>${hibernate.dialect}</prop> </span></span></li><li><span> </props> </span></li><li class="alt"><span> </property> </span></li><li><span></bean> </span></li></ol></div><pre class="java" style="display: none;" name="code"><bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<!-- 可以看到和 普通的dataSource用法一样 -->
<property name="dataSource" ref="dynamicDataSource" />
<property name="configLocations" value="classpath:hibernate.cfg.xml" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
</props>
</property>
</bean>
可以看到我们用的仍然是一个sessionFactory,这样看来事务管理的配置也和以前一样。
- <tx:annotation-driven transaction-manager="transactionManager"/>
- <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
- <property name="sessionFactory" ref="sessionFactory" />
- </bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
DynamicDataSource Bean也在容器中了,现在剩下的就在程序中如何控制,选择某一个想要的数据源该怎么做:
- //这样就将数据源动态的设置成了dataSourceB.
- CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_B);
//这样就将数据源动态的设置成了dataSourceB.
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_B);
或者使用AOP实现
- package datasource;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- @Aspect
- public class DynamicDataSourceAspect {
- @Pointcut("execution (public service.impl..*.*(..))")
- public void serviceExecution(){}
- @Before("serviceExecution()")
- public void setDynamicDataSource(JoinPoint jp) {
- for(Object o : jp.getArgs()) {
- //处理具体的逻辑 ,根据具体的境况CustomerContextHolder.setCustomerType()选取DataSource
- }
- }
- }
package datasource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class DynamicDataSourceAspect {
@Pointcut("execution (public service.impl..*.*(..))")
public void serviceExecution(){}
@Before("serviceExecution()")
public void setDynamicDataSource(JoinPoint jp) {
for(Object o : jp.getArgs()) {
//处理具体的逻辑 ,根据具体的境况CustomerContextHolder.setCustomerType()选取DataSource
}
}
}
- 6. 总结: 我们可以看到运用AbstractRoutingDataSource可以很好的实现多数据源的,而且以后扩展更多的数据源时也非常容易,只要增加数据源和修改DynamicDataSource Bean的targetDataSources 配置就好。关于选择某一个数据源,其实可以很好的用@Aspect在Service的入口加入一个切面@Pointcut,在@Before里判断JoinPoint的类容选定特定的数据源(例如更加JoinPoint的某个key来判断在设置CustomerContextHolder.setCustomerType)。
- 根究实际的应用来确定。个人看法: 以前有很多应用部署了writeDataSource和readDataSource和slaveDataBase的实现,但现在的大部分应用在构架上已经不太适合了,越来越注重和用户的交互性使得数据库间他同步变得日益复杂和难以维护,所以在架构系统时不妨考虑使用水平切割的方法来切割数据库,当然这种开发需要需要更多的时间分析业务领域,选取如何的配置数据源其实也是和业务息息相关的。