在大型的應用中,爲了提高數據庫的水平伸縮性,對多個數據庫實例進行管理,需要配置多數據源。在Spring框架被廣泛運用的今天,可以很簡單的運用Spring中的特性配置動態多數據。
- 首先配置一個基於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>
接着擴展一個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();
}
}
而其中的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();
}
}
接下來就是在我們上面的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>
如何是用這個動態的多數據源呢? 其實很簡單,因爲我們的DynamicDataSource 是繼承與AbstractRoutingDataSource,而AbstractRoutingDataSource又是繼承於org.springframework.jdbc.datasource.AbstractDataSource,顯然的AbstractDataSource實現了統一的DataSource接口,所以我們的DynamicDataSource 同樣可以方便的當一個DataSource使用,下面拿Hibernate做例子:
<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>
DynamicDataSource Bean也在容器中了,現在剩下的就在程序中如何控制,選擇某一個想要的數據源該怎麼做:
//這樣就將數據源動態的設置成了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
}
}
}
總結: 我們可以看到運用AbstractRoutingDataSource可以很好的實現多數據源的,而且以後擴展更多的數據源時也非常容易,只要增加數據源和修改DynamicDataSource Bean的targetDataSources 配置就好。關於選擇某一個數據源,其實可以很好的用@Aspect在Service的入口加入一個切面@Pointcut,在@Before裏判斷JoinPoint的類容選定特定的數據源(例如更加JoinPoint的某個key來判斷在設置CustomerContextHolder.setCustomerType)。
根究實際的應用來確定。個人看法: 以前有很多應用部署了writeDataSource和readDataSource和slaveDataBase的實現,但現在的大部分應用在構架上已經不太適合了,越來越注重和用戶的交互性使得數據庫間他同步變得日益複雜和難以維護,所以在架構系統時不妨考慮使用水平切割的方法來切割數據庫,當然這種開發需要需要更多的時間分析業務領域,選取如何的配置數據源其實也是和業務息息相關的。