随笔-157  评论-1301  文章-3 

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 hoojo_@126.com
 * @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 hoojo_@126.com
 * @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 hoojo_@126.com
 * @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 hoojo_@126.com
 * @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 hoojo_@126.com
 * @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 hoojo_@126.com
 * @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 hoojo_@126.com
 * @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 hoojo_@126.com
 * @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 hoojo_@126.com
 * @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 hoojo_@126.com
 * @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进行业务类的方法进行拦截,通过类名或方法名中的有效字符串来动态切换到我们定义好的规则对应的数据源。

posted on 2013-10-12 10:35 hoojo 阅读(...) 评论(...) 编辑 收藏