Spring3.3 整合 Hibernate3、MyBatis3.2 配置多數據源/動態切換數據源 方法

一、開篇

這裏整合分別採用了Hibernate和MyBatis兩大持久層框架,Hibernate主要完成增刪改功能和一些單一的對象查詢功能,MyBatis主要負責查詢功能。所以在出來數據庫方言的時候基本上沒有什麼問題,但唯一可能出現問題的就是在hibernate做添加操作生成主鍵策略的時候。因爲我們都知道hibernate的數據庫本地方言會針對不同的數據庫採用不同的主鍵生成策略。

所以針對這一問題不得不採用自定義的主鍵生成策略,自己寫一個主鍵生成器的表來維護主鍵生成方式或以及使用其他的方式來生成主鍵,從而避開利用hibernate默認提供的主鍵生成方式。

所以存在問題有:怎樣動態的切換數據庫方言?

這個問題還沒有解決,沒有更多時間來研究。不過想想應該可以配置兩個SessionFactory來實現,那又存在怎麼樣動態切換SessionFactory呢?!需要解決這個問題才行,而這裏則演示怎麼樣動態切換DataSource數據源的方法。

 

二、代碼演示

在演示開始之前你的項目已經成功的整合完成的情況下才行,如果你還不知道怎麼使用Spring整合MyBatis和Spring整合Hibernate的話。建議參考之前的文章:MyBatis3整合Spring3、SpringMVC3Struts2、Spring、Hibernate整合ExtJS這兩篇文章結合起來就可以完成整合是幾大框架了。這裏重點介紹動態切換DataSource數據源的方法!

1、datasource的配置 applicationContext-datasource.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
    http://www.springframework.org/schema/tx  
    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
    
    <!-- 配置c3p0數據源 -->
    <bean id="dataSourceOracle" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="${datasource.driver}"/>
        <property name="jdbcUrl" value="${datasource.url}"/>
        <property name="user" value="${datasource.username}"/>
        <property name="password" value="${datasource.password}"/>
                
        <property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>
        <property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>
        <property name="minPoolSize" value="${c3p0.minPoolSize}"/>
        <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
        <property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>
        <property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>
        <property name="maxStatements" value="${c3p0.maxStatements}"/>
        <property name="numHelperThreads" value="${c3p0.numHelperThreads}"/>
        <property name="preferredTestQuery" value="${c3p0.preferredTestQuery}"/>
        <property name="testConnectionOnCheckout" value="${c3p0.testConnectionOnCheckout}"/>
    </bean>
    
    <bean id="dataSourceMySQL" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://172.31.108.178:3306/world?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull"/>
        <property name="user" value="root"/>
        <property name="password" value="jp2011"/>
                
        <property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>
        <property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>
        <property name="minPoolSize" value="${c3p0.minPoolSize}"/>
        <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
        <property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>
        <property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>
        <property name="maxStatements" value="${c3p0.maxStatements}"/>
        <property name="numHelperThreads" value="${c3p0.numHelperThreads}"/>
        <property name="preferredTestQuery" value="${c3p0.preferredTestQuery}"/>
        <property name="testConnectionOnCheckout" value="${c3p0.testConnectionOnCheckout}"/>
    </bean>
 
    <bean id="multipleDataSource" class="com.hoo.framework.spring.support.MultipleDataSource">
        <property name="defaultTargetDataSource" ref="dataSourceOracle"/>
        <property name="targetDataSources">
            <map>     
                <!-- 注意這裏的value是和上面的DataSource的id對應,key要和下面的CustomerContextHolder中的常量對應 -->
                <entry value-ref="dataSourceOracle" key="oracleDataSource"/>
                <entry value-ref="dataSourceMySQL" key="mySqlDataSource"/>
            </map>   
        </property>
    </bean>
         
    <!-- Annotation 配置sessionFactory,配置數據庫連接,注入hibernate數據庫配置 -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="multipleDataSource"/>
        <property name="packagesToScan" value="com.hoo.**.entity"/>
        <property name="hibernateProperties">
            <props>
                <!-- prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop-->
                <!-- 鏈接釋放策略 on_close | after_transaction | after_statement | auto  -->
                <prop key="hibernate.connection.release_mode">after_transaction</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <!--prop key="hibernate.hbm2ddl.auto">update</prop-->
            </props>
        </property>
        <!-- property name="configLocation" value="classpath:hibernate.cfg.xml" /-->
        <property name="namingStrategy">
            <bean class="com.hoo.framework.hibernate.PrefixedNamingStrategy" />
        </property>
    </bean>
 
    <!-- 事務管理器,注入sessionFactory  -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <!-- 配置事務的傳播特性 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="edit*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="remove*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="modify*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="execute*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="*" read-only="true" />
        </tx:attributes>
    </tx:advice>
    
    <!-- 配置那些類、方法納入到事務的管理 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.hoo.**.service.impl.*.*(..))" id="transactionManagerMethod"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionManagerMethod" />
    </aop:config>
    
    <!-- 配置SqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="multipleDataSource"/>
        <property name="configLocation" value="classpath:mybatis.xml"/>
        <!-- mapper和resultmap配置路徑 --> 
        <property name="mapperLocations">
            <list>
                <!-- 表示在com.hoo目錄下的任意包下的resultmap包目錄中,以-resultmap.xml或-mapper.xml結尾所有文件 --> 
                <value>classpath:com/hoo/framework/mybatis/mybatis-common.xml</value>
                <value>classpath:com/hoo/**/resultmap/*-resultmap.xml</value>
                <value>classpath:com/hoo/**/mapper/*-mapper.xml</value>
                <value>classpath:com/hoo/**/mapper/**/*-mapper.xml</value>
            </list>
        </property>
    </bean>
    
    <!-- 通過掃描的模式,掃描目錄在com/hoo/任意目錄下的mapper目錄下,所有的mapper都需要繼承SqlMapper接口的接口 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.hoo.**.mapper"/>
        <property name="markerInterface" value="com.hoo.framework.mybatis.SqlMapper"/>
    </bean>
</beans>

上面分配配置了Oracle和MySQL數據源,MultipleDataSource爲自定義的DataSource,它是繼承AbstractRoutingDataSource實現抽象方法即可。

 

2、MultipleDataSource實現AbstractRoutingDataSource抽象數據源中方法,定義CustomerContextHolder來動態切換數據源。代碼如下:

package com.hoo.framework.spring.support;
 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
/**
 * <b>function:</b> Spring  多數據源實現
 * @author hoojo
 * @createDate 2013-9-27 上午11:24:53
 * @file MultipleDataSource.java
 * @package com.hoo.framework.spring.support
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
public class MultipleDataSource extends AbstractRoutingDataSource {
 
    @Override
    protected Object determineCurrentLookupKey() {
        return CustomerContextHolder.getCustomerType();
    }
}

 

CustomerContextHolder

package com.hoo.framework.spring.support;
 
/**
 * <b>function:</b> 多數據源
 * @author hoojo
 * @createDate 2013-9-27 上午11:36:57
 * @file CustomerContextHolder.java
 * @package com.hoo.framework.spring.support
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
public abstract class CustomerContextHolder {
 
    public final static String DATA_SOURCE_ORACLE = "oracleDataSource";
    public final static String DATA_SOURCE_MYSQL = "mySqlDataSource";
    
    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();  
    }  
}

其中,常量對應的applicationContext-datasource.xml中的multipleDataSource中的targetDataSource的key,這個很關鍵不要搞錯了。

 

3、測試看能否切換數據源

package com.hoo.framework.service.impl;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import com.hoo.framework.dao.BaseDao;
import com.hoo.framework.log.ApplicationLogging;
import com.hoo.framework.spring.support.CustomerContextHolder;
 
 
/**
 * <b>function:</b>多數據源測試服務接口測試
 * @author hoojo
 * @createDate 2013-10-10 上午11:18:18
 * @file MultipleDataSourceServiceImplTest.java
 * @package com.hoo.framework.service.impl
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
@ContextConfiguration({ "classpath:applicationContext-datasource.xml", "classpath:applicationContext-base.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class MultipleDataSourceServiceImplTest extends ApplicationLogging {
 
    @Autowired
    private BaseDao dao;
      
    @Test
    public void testDao() {
        info(dao.toString());
        
        CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
        info(dao.findBySql("select * from devicestate_tab where rownum < 2").toString());
        
        CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
        info(dao.findBySql("select * from city limit 2").toString());
    }    
}

運行上面的測試用例後可以發現能查詢到數據,如果我們註釋掉其中的一項setCustomerType就會出現查詢錯誤。在其中一個數據庫沒有找到對應的table。

至此,切換數據源也算成功了大半,剩下的就是如何在實際的業務中完成數據源的“動態”切換呢?!難道還是要像上面一樣在每個方法上面寫一個setCustomerType來手動控制數據源!答案是“當然不是”。我們用過Spring、hibernate後就會知道,先去使用hibernate的時候沒有用spring,事務都是手動控制的。自從用了Spring大家都輕鬆了很多,事務交給了Spring來完成。所以到了這裏你大概知道怎麼做了,如果你還不知道~嘿嘿……(Spring那你就懂了個皮毛,最經典的部分你沒有學到)

所以就是利用Spring的Aop進行切面編程,攔截器Interceptor在這裏是一個很好的選擇。它可以在方法之前或方法之後完成一些操作,而且控制的粒度可以細到具體的方法中的參數、返回值、方法名等。在這裏控制數據源動態切換最好不過了!

 

4、上面是手動切換數據源,我們用Spring的Aop攔截器整個更自動化的方法來切換數據源。

Spring配置文件 applicationContext-base.xml

<?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:aop="http://www.springframework.org/schema/aop" 
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
 
    <context:component-scan base-package="com.hoo.**.dao.impl"/>
    <context:component-scan base-package="com.hoo.**.service.impl"/>
    <context:component-scan base-package="com.hoo.**.interceptor"/> 
    
    <!-- 啓用Aop AspectJ註解 -->
    <aop:aspectj-autoproxy/>
    
    <!-- 注入配置文件 -->
    <util:properties id="systemConfig" location="classpath:system.properties" />
    
    <!-- 啓用表達式配置xml內容 -->
    <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="propertiesArray">
            <util:list>
                <util:properties location="classpath:system.properties"/>
                <util:properties location="classpath:datasource.properties"/>
            </util:list>
        </property>
    </bean>
    
      
    <!-- 配置一個攔截器對象,處理具體的切換數據源的業務 -->
    <bean id="dataSourceMethodInterceptor" class="com.hoo.framework.spring.interceptor.DataSourceMethodInterceptor"/>
    
    <!-- 參與動態切換數據源的切入點對象 (切入點對象,確定何時何地調用攔截器) -->
    <bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!-- 配置緩存aop切面 -->
        <property name="advice" ref="dataSourceMethodInterceptor" />
        <!-- 配置哪些方法參與緩存策略 -->
        <!--  
            .表示符合任何單一字元                  
            ###  +表示符合前一個字元一次或多次                  
            ###  *表示符合前一個字元零次或多次                  
            ###  \Escape任何Regular expression使用到的符號                  
        -->                 
        <!-- .*表示前面的前綴(包括包名) 表示print方法-->
        <property name="patterns">
            <list>
                <value>com.hoo.*.service.impl.*Service*\.*.*</value>
                <value>com.hoo.*.mapper.*Mapper*\.*.*</value>
            </list>
        </property>
    </bean>
</beans>

上面的攔截器是攔截Service和Mapper的Java對象中的執行方法,所有在service.impl包下的ServiceImpl和mapper包下的Mapper接口將會被DataSourceMethodInterceptor攔截到,並通過其中的規律動態設置數據源。

 

3、攔截器DataSourceMethodInterceptor.java攔截具體的業務,並通過業務代碼中的方法和接口、實現類的規律進行動態設置數據源

package com.hoo.framework.spring.interceptor;
 
import java.lang.reflect.Proxy;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.ClassUtils;
import org.springframework.beans.factory.InitializingBean;
import com.hoo.framework.log.ApplicationLogging;
import com.hoo.framework.spring.support.CustomerContextHolder;
 
/**
 * <b>function:</b> 動態設置數據源攔截器
 * @author hoojo
 * @createDate 2013-9-27 下午02:00:13
 * @file DataSourceMethodInterceptor.java
 * @package com.hoo.framework.spring.interceptor
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
public class DataSourceMethodInterceptor extends ApplicationLogging implements MethodInterceptor, InitializingBean {
 
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Class<?> clazz = invocation.getThis().getClass();
        String className = clazz.getName();
        if (ClassUtils.isAssignable(clazz, Proxy.class)) {
            className = invocation.getMethod().getDeclaringClass().getName();
        }
        String methodName = invocation.getMethod().getName();
        Object[] arguments = invocation.getArguments();
        trace("execute {}.{}({})", className, methodName, arguments);
        
        if (className.contains("MySQL")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
        } else if (className.contains("Oracle")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
        } else if (methodName.contains("MySQL")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
        } else if (methodName.contains("Oracle")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
        } else {
            CustomerContextHolder.clearCustomerType();
        }
        
        /*
        if (className.contains("MySQL") || methodName.contains("MySQL")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
        } else if (className.contains("Oracle") || methodName.contains("Oracle")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
        } else {
            CustomerContextHolder.clearCustomerType();
        }
        */
        Object result = invocation.proceed();
        return result;
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        log.trace("afterPropertiesSet……");
    }
}

上面的代碼是在接口或實現中如果出現MySQL就設置數據源爲DATA_SOURCE_MYSQL,如果有Oracle就切換成DATA_SOURCE_ORACLE數據源。

 

4、編寫實際的業務接口和實現來測試攔截器是否有效

MultipleDataSourceService 接口

package com.hoo.server.datasource.service;
 
 
/**
 * <b>function:</b> 多數據源測試服務接口
 * @author hoojo
 * @createDate 2013-10-10 上午11:07:31
 * @file MultipleDataSourceService.java
 * @package com.hoo.server.datasource.service
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
public interface MultipleDataSourceService {
    
    public void execute4MySQL() throws Exception;
    
    public void execute4Oracle() throws Exception;
}

 

接口實現

package com.hoo.server.datasource.service.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.hoo.framework.dao.BaseDao;
import com.hoo.framework.service.impl.AbstractService;
import com.hoo.server.datasource.service.MultipleDataSourceService;
 
/**
 * <b>function:</b> 多數據源測試服務接口實現
 * @author hoojo
 * @createDate 2013-10-10 上午11:09:54
 * @file MultipleDataSourceServiceImpl.java
 * @package com.hoo.server.datasource.service.impl
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
@Service
public class MultipleDataSourceServiceImpl extends AbstractService implements MultipleDataSourceService {
    
    @Autowired
    private BaseDao dao;
    
    @Override
    public void execute4MySQL() throws Exception {
        info(dao.findBySql("select * from city limit 2").toString());
    }
 
    @Override
    public void execute4Oracle() throws Exception {
        info(dao.findBySql("select * from devicestate_tab where rownum < 2").toString());
    }
}

測試上面的服務層代碼,看看能否利用攔截器實現數據源動態切換

在上面的MultipleDataSourceServiceImplTest中加入如下代碼

@Autowired
@Qualifier("multipleDataSourceServiceImpl")
private MultipleDataSourceService service;
 
@Test
public void testService() {
    try {
        service.execute4MySQL();
        service.execute4Oracle();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

運行上面的代碼後可以看到能夠成功查詢到結果

 

5、測試實現類帶Oracle或MySQL字符串的

package com.hoo.server.datasource.service.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hoo.framework.dao.BaseDao;
import com.hoo.framework.service.impl.AbstractService;
import com.hoo.server.datasource.service.MultipleDataSourceService;
 
/**
 * <b>function:</b> 多數據源測試服務接口實現
 * @author hoojo
 * @createDate 2013-10-10 上午11:09:54
 * @file MultipleDataSourceServiceImpl.java
 * @package com.hoo.server.datasource.service.impl
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
@Service
public class MySQLDataSourceServiceImpl extends AbstractService implements MultipleDataSourceService {
    
    @Autowired
    private BaseDao dao;
    
    @Override
    public void execute4MySQL() throws Exception {
        info(dao.findBySql("select * from city limit 2").toString());
    }
 
    @Override
    public void execute4Oracle() throws Exception {
        info(dao.findBySql("select * from devicestate_tab where rownum < 2").toString());
    }
}

 

package com.hoo.server.datasource.service.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hoo.framework.dao.BaseDao;
import com.hoo.framework.service.impl.AbstractService;
import com.hoo.server.datasource.service.MultipleDataSourceService;
 
/**
 * <b>function:</b> 多數據源測試服務接口實現
 * @author hoojo
 * @createDate 2013-10-10 上午11:09:54
 * @file MultipleDataSourceServiceImpl.java
 * @package com.hoo.server.datasource.service.impl
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
@Service
public class OracleDataSourceServiceImpl extends AbstractService implements MultipleDataSourceService {
    
    @Autowired
    private BaseDao dao;
    
    @Override
    public void execute4MySQL() throws Exception {
        info(dao.findBySql("select * from city limit 2").toString());
    }
 
    @Override
    public void execute4Oracle() throws Exception {
        info(dao.findBySql("select * from devicestate_tab where rownum < 2").toString());
    }
}

這裏的兩個實現類的類名都含有不同規則的數據源標識符字符串,而且方法名也含有相關字符串,這些都匹配攔截器中的規則。

在MultipleDataSourceServiceImplTest 中加入測試代碼

@Autowired
@Qualifier("oracleDataSourceServiceImpl")
private MultipleDataSourceService oracleService;
 
@Autowired
@Qualifier("mySQLDataSourceServiceImpl")
private MultipleDataSourceService mySQLService;
 
@Test
public void testOracleService() {
    try {
        oracleService.execute4MySQL();
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    try {
        oracleService.execute4Oracle();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
 
@Test
public void testMySQLService() {
    try {
        mySQLService.execute4MySQL();
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    try {
        mySQLService.execute4Oracle();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

執行上面的測試用例會發現有一個查詢會失敗,那是因爲我們按照攔截器中的業務規則切換數據源就匹配到了其中一個,就是通過類名進行數據源切換,所以只定位到其中一個數據源。

 

6、測試MyBatis的數據源切換方法

MyBatis的查詢接口

package com.hoo.server.datasource.mapper;
 
import java.util.List;
import java.util.Map;
 
import com.hoo.framework.mybatis.SqlMapper;
 
/**
 * <b>function:</b> MyBatis 多數據源 測試查詢接口
 * @author hoojo
 * @createDate 2013-10-10 下午04:18:08
 * @file MultipleDataSourceMapper.java
 * @package com.hoo.server.datasource.mapper
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
public interface MultipleDataSourceMapper extends SqlMapper {
 
    public List<Map<String, Object>> execute4MySQL() throws Exception;
    
    public List<Map<String, Object>> execute4Oracle() throws Exception;
}

multiple-datasource-mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hoo.server.datasource.mapper.MultipleDataSourceMapper">
    
    <select id="execute4Oracle" resultType="map">
        <![CDATA[
            SELECT
                *
            FROM
                deviceInfo_tab t where rownum < 10
        ]]>
    </select>
    
    <select id="execute4MySQL" resultType="map">
        <![CDATA[
            SELECT
                *
            FROM
                city limit 2
        ]]>
    </select>
</mapper>

測試MyBatis的mapper查詢接口,在MultipleDataSourceServiceImplTest加入以下代碼

@Autowired
private MultipleDataSourceMapper mapper;
 
@Test
public void testMapper() {
    try {
        trace(mapper.execute4MySQL());
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    try {
        trace(mapper.execute4Oracle());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

運行以上測試代碼也能發現可以正常的查詢到Oracle和MySQL數據庫中的數據。MyBatis的在這裏只負責查詢,而增刪改是hibernate完成的任務,所以這裏也就不再測試modified部分。

 

7、上面的攔截器是需要在配置文件中進行配置的,這裏利用annotation的配置的攔截器進行業務攔截,也許有些人更喜歡用annotation

package com.hoo.framework.spring.interceptor;
 
import java.lang.reflect.Proxy;
import org.apache.commons.lang.ClassUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import com.hoo.framework.log.ApplicationLogging;
import com.hoo.framework.spring.support.CustomerContextHolder;
 
/**
 * <b>function:</b> 多數據源動態配置攔截器
 * @author hoojo
 * @createDate 2013-10-10 上午11:35:54
 * @file MultipleDataSourceInterceptor.java
 * @package com.hoo.framework.spring.interceptor
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
@Component
@Aspect
public class MultipleDataSourceInterceptor extends ApplicationLogging {
 
    /**
     * <b>function:</b> 動態設置數據源
     * @author hoojo
     * @createDate 2013-10-10 上午11:38:45
     * @throws Exception
     */
    @Before("execution(* com.hoo..service.impl.*ServiceImpl.*(..)) || execution(* com.hoo..mapper.*Mapper.*(..))")
    public void dynamicSetDataSoruce(JoinPoint joinPoint) throws Exception {
        
        Class<?> clazz = joinPoint.getTarget().getClass();
        String className = clazz.getName();
        if (ClassUtils.isAssignable(clazz, Proxy.class)) {
            className = joinPoint.getSignature().getDeclaringTypeName();
        }
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        trace("execute {}.{}({})", className, methodName, arguments);
        
        if (className.contains("MySQL")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
        } else if (className.contains("Oracle")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
        } else if (methodName.contains("MySQL")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
        } else if (methodName.contains("Oracle")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
        } else {
            CustomerContextHolder.clearCustomerType();
        }
        
        /*
        if (className.contains("MySQL") || methodName.contains("MySQL")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
        } else if (className.contains("Oracle") || methodName.contains("Oracle")) {
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
        } else {
            CustomerContextHolder.clearCustomerType();
        }
        */
    }
}

這種攔截器就是不需要在配置文件中加入任何配置進行攔截,算是一種擴展的方法。

 

三、總結

多數據源動態切換的主要地方在於我們要定義一個自己的數據源來實現AbstractRoutingDataSource中的determineCurrentLookupKey方法,然後通過CustomerContextHolder來實現數據源的切換工作。而數據源的動態切換也就在於我們利用了Spring的Aop中的攔截器Interceptor進行業務類的方法進行攔截,通過類名或方法名中的有效字符串來動態切換到我們定義好的規則對應的數據源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章