在大型的應用中,為了提高數據庫的水平伸縮性,對多個數據庫實例進行管理,需要配置多數據源。在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>
- package datasource;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- public class DynamicDataSource extends AbstractRoutingDataSource{
- @Override
- protected Object determineCurrentLookupKey() {
- return CustomerContextHolder.getCustomerType();
- }
- }
- 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();
- }
- }
- <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="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>
- <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);
- 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的實現,但現在的大部分應用在構架上已經不太適合了,越來越注重和用戶的交互性使得數據庫間他同步變得日益復雜和難以維護,所以在架構系統時不妨考慮使用水平切割的方法來切割數據庫,當然這種開發需要需要更多的時間分析業務領域,選取如何的配置數據源其實也是和業務息息相關的。