Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法
Spring3.3整合Hibernate3、MyBatis3.2配置多数据源/动态切换数据源方法
一、开篇
这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能。所以在出来数据库方言的时候基本上没有什么问题,但唯一可能出现问题的就是在hibernate做添加操作生成主键策略的时候。因为我们都知道hibernate的数据库本地方言会针对不同的数据库采用不同的主键生成策略。
所以针对这一问题不得不采用自定义的主键生成策略,自己写一个主键生成器的表来维护主键生成方式或以及使用其他的方式来生成主键,从而避开利用hibernate默认提供的主键生成方式。
所以存在问题有:怎样动态的切换数据库方言?
这个问题还没有解决,没有更多时间来研究。不过想想应该可以配置两个SessionFactory来实现,那又存在怎么样动态切换SessionFactory呢?!需要解决这个问题才行,而这里则演示怎么样动态切换DataSource数据源的方法。
二、代码演示
在演示开始之前你的项目已经成功的整合完成的情况下才行,如果你还不知道怎么使用Spring整合MyBatis和Spring整合Hibernate的话。建议参考之前的文章:MyBatis3整合Spring3、SpringMVC3、Struts2、Spring、Hibernate整合ExtJS这两篇文章结合起来就可以完成整合是几大框架了。这里重点介绍动态切换DataSource数据源的方法!
1、datasource的配置applicationContext-datasource.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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数据源-->
<beanid="dataSourceOracle"class="com.mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close">
<propertyname="driverClass"value="${datasource.driver}"/>
<propertyname="jdbcUrl"value="${datasource.url}"/>
<propertyname="user"value="${datasource.username}"/>
<propertyname="password"value="${datasource.password}"/>
<propertyname="acquireIncrement"value="${c3p0.acquireIncrement}"/>
<propertyname="initialPoolSize"value="${c3p0.initialPoolSize}"/>
<propertyname="minPoolSize"value="${c3p0.minPoolSize}"/>
<propertyname="maxPoolSize"value="${c3p0.maxPoolSize}"/>
<propertyname="maxIdleTime"value="${c3p0.maxIdleTime}"/>
<propertyname="idleConnectionTestPeriod"value="${c3p0.idleConnectionTestPeriod}"/>
<propertyname="maxStatements"value="${c3p0.maxStatements}"/>
<propertyname="numHelperThreads"value="${c3p0.numHelperThreads}"/>
<propertyname="preferredTestQuery"value="${c3p0.preferredTestQuery}"/>
<propertyname="testConnectionOnCheckout"value="${c3p0.testConnectionOnCheckout}"/>
</bean>
<beanid="dataSourceMySQL"class="com.mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close">
<propertyname="driverClass"value="com.mysql.jdbc.Driver"/>
<propertyname="jdbcUrl"value="jdbc:mysql://172.31.108.178:3306/world?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull"/>
<propertyname="user"value="root"/>
<propertyname="password"value="jp2011"/>
<propertyname="acquireIncrement"value="${c3p0.acquireIncrement}"/>
<propertyname="initialPoolSize"value="${c3p0.initialPoolSize}"/>
<propertyname="minPoolSize"value="${c3p0.minPoolSize}"/>
<propertyname="maxPoolSize"value="${c3p0.maxPoolSize}"/>
<propertyname="maxIdleTime"value="${c3p0.maxIdleTime}"/>
<propertyname="idleConnectionTestPeriod"value="${c3p0.idleConnectionTestPeriod}"/>
<propertyname="maxStatements"value="${c3p0.maxStatements}"/>
<propertyname="numHelperThreads"value="${c3p0.numHelperThreads}"/>
<propertyname="preferredTestQuery"value="${c3p0.preferredTestQuery}"/>
<propertyname="testConnectionOnCheckout"value="${c3p0.testConnectionOnCheckout}"/>
</bean>
<beanid="multipleDataSource"class="com.hoo.framework.spring.support.MultipleDataSource">
<propertyname="defaultTargetDataSource"ref="dataSourceOracle"/>
<propertyname="targetDataSources">
<map>
<!--注意这里的value是和上面的DataSource的id对应,key要和下面的CustomerContextHolder中的常量对应-->
<entryvalue-ref="dataSourceOracle"key="oracleDataSource"/>
<entryvalue-ref="dataSourceMySQL"key="mySqlDataSource"/>
</map>
</property>
</bean>
<!--Annotation配置sessionFactory,配置数据库连接,注入hibernate数据库配置-->
<beanid="sessionFactory"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<propertyname="dataSource"ref="multipleDataSource"/>
<propertyname="packagesToScan"value="com.hoo.**.entity"/>
<propertyname="hibernateProperties">
<props>
<!--propkey="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop-->
<!--链接释放策略on_close|after_transaction|after_statement|auto-->
<propkey="hibernate.connection.release_mode">after_transaction</prop>
<propkey="hibernate.show_sql">true</prop>
<propkey="hibernate.format_sql">true</prop>
<!--propkey="hibernate.hbm2ddl.auto">update</prop-->
</props>
</property>
<!--propertyname="configLocation"value="classpath:hibernate.cfg.xml"/-->
<propertyname="namingStrategy">
<beanclass="com.hoo.framework.hibernate.PrefixedNamingStrategy"/>
</property>
</bean>
<!--事务管理器,注入sessionFactory-->
<beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<propertyname="sessionFactory"ref="sessionFactory"/>
</bean>
<!--配置事务的传播特性-->
<tx:adviceid="txAdvice"transaction-manager="transactionManager">
<tx:attributes>
<tx:methodname="add*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>
<tx:methodname="edit*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>
<tx:methodname="remove*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>
<tx:methodname="insert*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>
<tx:methodname="save*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>
<tx:methodname="update*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>
<tx:methodname="modify*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>
<tx:methodname="delete*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>
<tx:methodname="execute*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>
<tx:methodname="*"read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置那些类、方法纳入到事务的管理-->
<aop:config>
<aop:pointcutexpression="execution(*com.hoo.**.service.impl.*.*(..))"id="transactionManagerMethod"/>
<aop:advisoradvice-ref="txAdvice"pointcut-ref="transactionManagerMethod"/>
</aop:config>
<!--配置SqlSessionFactoryBean-->
<beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean">
<propertyname="dataSource"ref="multipleDataSource"/>
<propertyname="configLocation"value="classpath:mybatis.xml"/>
<!--mapper和resultmap配置路径-->
<propertyname="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接口的接口-->
<beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer">
<propertyname="basePackage"value="com.hoo.**.mapper"/>
<propertyname="markerInterface"value="com.hoo.framework.mybatis.SqlMapper"/>
</bean>
</beans>
上面分配配置了Oracle和MySQL数据源,MultipleDataSource为自定义的DataSource,它是继承AbstractRoutingDataSource实现抽象方法即可。
2、MultipleDataSource实现AbstractRoutingDataSource抽象数据源中方法,定义CustomerContextHolder来动态切换数据源。代码如下:
packagecom.hoo.framework.spring.support;
importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
*<b>function:</b>Spring多数据源实现
*@authorhoojo
*@createDate2013-9-27上午11:24:53
*@fileMultipleDataSource.java
*@packagecom.hoo.framework.spring.support
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
publicclassMultipleDataSourceextendsAbstractRoutingDataSource{
@Override
protectedObjectdetermineCurrentLookupKey(){
returnCustomerContextHolder.getCustomerType();
}
}
CustomerContextHolder
packagecom.hoo.framework.spring.support;
/**
*<b>function:</b>多数据源
*@authorhoojo
*@createDate2013-9-27上午11:36:57
*@fileCustomerContextHolder.java
*@packagecom.hoo.framework.spring.support
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
publicabstractclassCustomerContextHolder{
publicfinalstaticStringDATA_SOURCE_ORACLE="oracleDataSource";
publicfinalstaticStringDATA_SOURCE_MYSQL="mySqlDataSource";
privatestaticfinalThreadLocal<String>contextHolder=newThreadLocal<String>();
publicstaticvoidsetCustomerType(StringcustomerType){
contextHolder.set(customerType);
}
publicstaticStringgetCustomerType(){
returncontextHolder.get();
}
publicstaticvoidclearCustomerType(){
contextHolder.remove();
}
}
其中,常量对应的applicationContext-datasource.xml中的multipleDataSource中的targetDataSource的key,这个很关键不要搞错了。
3、测试看能否切换数据源
packagecom.hoo.framework.service.impl;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.beans.factory.annotation.Qualifier;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
importcom.hoo.framework.dao.BaseDao;
importcom.hoo.framework.log.ApplicationLogging;
importcom.hoo.framework.spring.support.CustomerContextHolder;
/**
*<b>function:</b>多数据源测试服务接口测试
*@authorhoojo
*@createDate2013-10-10上午11:18:18
*@fileMultipleDataSourceServiceImplTest.java
*@packagecom.hoo.framework.service.impl
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
@ContextConfiguration({"classpath:applicationContext-datasource.xml","classpath:applicationContext-base.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
publicclassMultipleDataSourceServiceImplTestextendsApplicationLogging{
@Autowired
privateBaseDaodao;
@Test
publicvoidtestDao(){
info(dao.toString());
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
info(dao.findBySql("select*fromdevicestate_tabwhererownum<2").toString());
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
info(dao.findBySql("select*fromcitylimit2").toString());
}
}
运行上面的测试用例后可以发现能查询到数据,如果我们注释掉其中的一项setCustomerType就会出现查询错误。在其中一个数据库没有找到对应的table。
至此,切换数据源也算成功了大半,剩下的就是如何在实际的业务中完成数据源的“动态”切换呢?!难道还是要像上面一样在每个方法上面写一个setCustomerType来手动控制数据源!答案是“当然不是”。我们用过Spring、hibernate后就会知道,先去使用hibernate的时候没有用spring,事务都是手动控制的。自从用了Spring大家都轻松了很多,事务交给了Spring来完成。所以到了这里你大概知道怎么做了,如果你还不知道~嘿嘿……(Spring那你就懂了个皮毛,最经典的部分你没有学到)
所以就是利用Spring的Aop进行切面编程,拦截器Interceptor在这里是一个很好的选择。它可以在方法之前或方法之后完成一些操作,而且控制的粒度可以细到具体的方法中的参数、返回值、方法名等。在这里控制数据源动态切换最好不过了!
4、上面是手动切换数据源,我们用Spring的Aop拦截器整个更自动化的方法来切换数据源。
Spring配置文件applicationContext-base.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:component-scanbase-package="com.hoo.**.dao.impl"/>
<context:component-scanbase-package="com.hoo.**.service.impl"/>
<context:component-scanbase-package="com.hoo.**.interceptor"/>
<!--启用AopAspectJ注解-->
<aop:aspectj-autoproxy/>
<!--注入配置文件-->
<util:propertiesid="systemConfig"location="classpath:system.properties"/>
<!--启用表达式配置xml内容-->
<beanid="placeholderConfig"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<propertyname="propertiesArray">
<util:list>
<util:propertieslocation="classpath:system.properties"/>
<util:propertieslocation="classpath:datasource.properties"/>
</util:list>
</property>
</bean>
<!--配置一个拦截器对象,处理具体的切换数据源的业务-->
<beanid="dataSourceMethodInterceptor"class="com.hoo.framework.spring.interceptor.DataSourceMethodInterceptor"/>
<!--参与动态切换数据源的切入点对象(切入点对象,确定何时何地调用拦截器)-->
<beanid="methodCachePointCut"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--配置缓存aop切面-->
<propertyname="advice"ref="dataSourceMethodInterceptor"/>
<!--配置哪些方法参与缓存策略-->
<!--
.表示符合任何单一字元
###+表示符合前一个字元一次或多次
###*表示符合前一个字元零次或多次
###\Escape任何Regularexpression使用到的符号
-->
<!--.*表示前面的前缀(包括包名)表示print方法-->
<propertyname="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拦截具体的业务,并通过业务代码中的方法和接口、实现类的规律进行动态设置数据源
packagecom.hoo.framework.spring.interceptor;
importjava.lang.reflect.Proxy;
importorg.aopalliance.intercept.MethodInterceptor;
importorg.aopalliance.intercept.MethodInvocation;
importorg.apache.commons.lang.ClassUtils;
importorg.springframework.beans.factory.InitializingBean;
importcom.hoo.framework.log.ApplicationLogging;
importcom.hoo.framework.spring.support.CustomerContextHolder;
/**
*<b>function:</b>动态设置数据源拦截器
*@authorhoojo
*@createDate2013-9-27下午02:00:13
*@fileDataSourceMethodInterceptor.java
*@packagecom.hoo.framework.spring.interceptor
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
publicclassDataSourceMethodInterceptorextendsApplicationLoggingimplementsMethodInterceptor,InitializingBean{
@Override
publicObjectinvoke(MethodInvocationinvocation)throwsThrowable{
Class<?>clazz=invocation.getThis().getClass();
StringclassName=clazz.getName();
if(ClassUtils.isAssignable(clazz,Proxy.class)){
className=invocation.getMethod().getDeclaringClass().getName();
}
StringmethodName=invocation.getMethod().getName();
Object[]arguments=invocation.getArguments();
trace("execute{}.{}({})",className,methodName,arguments);
if(className.contains("MySQL")){
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
}elseif(className.contains("Oracle")){
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
}elseif(methodName.contains("MySQL")){
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
}elseif(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);
}elseif(className.contains("Oracle")||methodName.contains("Oracle")){
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
}else{
CustomerContextHolder.clearCustomerType();
}
*/
Objectresult=invocation.proceed();
returnresult;
}
@Override
publicvoidafterPropertiesSet()throwsException{
log.trace("afterPropertiesSet……");
}
}
上面的代码是在接口或实现中如果出现MySQL就设置数据源为DATA_SOURCE_MYSQL,如果有Oracle就切换成DATA_SOURCE_ORACLE数据源。
4、编写实际的业务接口和实现来测试拦截器是否有效
MultipleDataSourceService接口
packagecom.hoo.server.datasource.service;
/**
*<b>function:</b>多数据源测试服务接口
*@authorhoojo
*@createDate2013-10-10上午11:07:31
*@fileMultipleDataSourceService.java
*@packagecom.hoo.server.datasource.service
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
publicinterfaceMultipleDataSourceService{
publicvoidexecute4MySQL()throwsException;
publicvoidexecute4Oracle()throwsException;
}
接口实现
packagecom.hoo.server.datasource.service.impl;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Service;
importcom.hoo.framework.dao.BaseDao;
importcom.hoo.framework.service.impl.AbstractService;
importcom.hoo.server.datasource.service.MultipleDataSourceService;
/**
*<b>function:</b>多数据源测试服务接口实现
*@authorhoojo
*@createDate2013-10-10上午11:09:54
*@fileMultipleDataSourceServiceImpl.java
*@packagecom.hoo.server.datasource.service.impl
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
@Service
publicclassMultipleDataSourceServiceImplextendsAbstractServiceimplementsMultipleDataSourceService{
@Autowired
privateBaseDaodao;
@Override
publicvoidexecute4MySQL()throwsException{
info(dao.findBySql("select*fromcitylimit2").toString());
}
@Override
publicvoidexecute4Oracle()throwsException{
info(dao.findBySql("select*fromdevicestate_tabwhererownum<2").toString());
}
}
测试上面的服务层代码,看看能否利用拦截器实现数据源动态切换
在上面的MultipleDataSourceServiceImplTest中加入如下代码
@Autowired
@Qualifier("multipleDataSourceServiceImpl")
privateMultipleDataSourceServiceservice;
@Test
publicvoidtestService(){
try{
service.execute4MySQL();
service.execute4Oracle();
}catch(Exceptione){
e.printStackTrace();
}
}
运行上面的代码后可以看到能够成功查询到结果
5、测试实现类带Oracle或MySQL字符串的
packagecom.hoo.server.datasource.service.impl;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Service;
importcom.hoo.framework.dao.BaseDao;
importcom.hoo.framework.service.impl.AbstractService;
importcom.hoo.server.datasource.service.MultipleDataSourceService;
/**
*<b>function:</b>多数据源测试服务接口实现
*@authorhoojo
*@createDate2013-10-10上午11:09:54
*@fileMultipleDataSourceServiceImpl.java
*@packagecom.hoo.server.datasource.service.impl
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
@Service
publicclassMySQLDataSourceServiceImplextendsAbstractServiceimplementsMultipleDataSourceService{
@Autowired
privateBaseDaodao;
@Override
publicvoidexecute4MySQL()throwsException{
info(dao.findBySql("select*fromcitylimit2").toString());
}
@Override
publicvoidexecute4Oracle()throwsException{
info(dao.findBySql("select*fromdevicestate_tabwhererownum<2").toString());
}
}
packagecom.hoo.server.datasource.service.impl;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Service;
importcom.hoo.framework.dao.BaseDao;
importcom.hoo.framework.service.impl.AbstractService;
importcom.hoo.server.datasource.service.MultipleDataSourceService;
/**
*<b>function:</b>多数据源测试服务接口实现
*@authorhoojo
*@createDate2013-10-10上午11:09:54
*@fileMultipleDataSourceServiceImpl.java
*@packagecom.hoo.server.datasource.service.impl
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
@Service
publicclassOracleDataSourceServiceImplextendsAbstractServiceimplementsMultipleDataSourceService{
@Autowired
privateBaseDaodao;
@Override
publicvoidexecute4MySQL()throwsException{
info(dao.findBySql("select*fromcitylimit2").toString());
}
@Override
publicvoidexecute4Oracle()throwsException{
info(dao.findBySql("select*fromdevicestate_tabwhererownum<2").toString());
}
}
这里的两个实现类的类名都含有不同规则的数据源标识符字符串,而且方法名也含有相关字符串,这些都匹配拦截器中的规则。
在MultipleDataSourceServiceImplTest中加入测试代码
@Autowired
@Qualifier("oracleDataSourceServiceImpl")
privateMultipleDataSourceServiceoracleService;
@Autowired
@Qualifier("mySQLDataSourceServiceImpl")
privateMultipleDataSourceServicemySQLService;
@Test
publicvoidtestOracleService(){
try{
oracleService.execute4MySQL();
}catch(Exceptione1){
e1.printStackTrace();
}
try{
oracleService.execute4Oracle();
}catch(Exceptione){
e.printStackTrace();
}
}
@Test
publicvoidtestMySQLService(){
try{
mySQLService.execute4MySQL();
}catch(Exceptione1){
e1.printStackTrace();
}
try{
mySQLService.execute4Oracle();
}catch(Exceptione){
e.printStackTrace();
}
}
执行上面的测试用例会发现有一个查询会失败,那是因为我们按照拦截器中的业务规则切换数据源就匹配到了其中一个,就是通过类名进行数据源切换,所以只定位到其中一个数据源。
6、测试MyBatis的数据源切换方法
MyBatis的查询接口
packagecom.hoo.server.datasource.mapper;
importjava.util.List;
importjava.util.Map;
importcom.hoo.framework.mybatis.SqlMapper;
/**
*<b>function:</b>MyBatis多数据源测试查询接口
*@authorhoojo
*@createDate2013-10-10下午04:18:08
*@fileMultipleDataSourceMapper.java
*@packagecom.hoo.server.datasource.mapper
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
publicinterfaceMultipleDataSourceMapperextendsSqlMapper{
publicList<Map<String,Object>>execute4MySQL()throwsException;
publicList<Map<String,Object>>execute4Oracle()throwsException;
}
multiple-datasource-mapper.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mappernamespace="com.hoo.server.datasource.mapper.MultipleDataSourceMapper">
<selectid="execute4Oracle"resultType="map">
<![CDATA[
SELECT
*
FROM
deviceInfo_tabtwhererownum<10
]]>
</select>
<selectid="execute4MySQL"resultType="map">
<![CDATA[
SELECT
*
FROM
citylimit2
]]>
</select>
</mapper>
测试MyBatis的mapper查询接口,在MultipleDataSourceServiceImplTest加入以下代码
@Autowired
privateMultipleDataSourceMappermapper;
@Test
publicvoidtestMapper(){
try{
trace(mapper.execute4MySQL());
}catch(Exceptione1){
e1.printStackTrace();
}
try{
trace(mapper.execute4Oracle());
}catch(Exceptione){
e.printStackTrace();
}
}
运行以上测试代码也能发现可以正常的查询到Oracle和MySQL数据库中的数据。MyBatis的在这里只负责查询,而增删改是hibernate完成的任务,所以这里也就不再测试modified部分。
7、上面的拦截器是需要在配置文件中进行配置的,这里利用annotation的配置的拦截器进行业务拦截,也许有些人更喜欢用annotation
packagecom.hoo.framework.spring.interceptor;
importjava.lang.reflect.Proxy;
importorg.apache.commons.lang.ClassUtils;
importorg.aspectj.lang.JoinPoint;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Before;
importorg.springframework.stereotype.Component;
importcom.hoo.framework.log.ApplicationLogging;
importcom.hoo.framework.spring.support.CustomerContextHolder;
/**
*<b>function:</b>多数据源动态配置拦截器
*@authorhoojo
*@createDate2013-10-10上午11:35:54
*@fileMultipleDataSourceInterceptor.java
*@packagecom.hoo.framework.spring.interceptor
*@projectSHMB
*@bloghttp://blog.csdn.net/IBM_hoojo
*@version1.0
*/
@Component
@Aspect
publicclassMultipleDataSourceInterceptorextendsApplicationLogging{
/**
*<b>function:</b>动态设置数据源
*@authorhoojo
*@createDate2013-10-10上午11:38:45
*@throwsException
*/
@Before("execution(*com.hoo..service.impl.*ServiceImpl.*(..))||execution(*com.hoo..mapper.*Mapper.*(..))")
publicvoiddynamicSetDataSoruce(JoinPointjoinPoint)throwsException{
Class<?>clazz=joinPoint.getTarget().getClass();
StringclassName=clazz.getName();
if(ClassUtils.isAssignable(clazz,Proxy.class)){
className=joinPoint.getSignature().getDeclaringTypeName();
}
StringmethodName=joinPoint.getSignature().getName();
Object[]arguments=joinPoint.getArgs();
trace("execute{}.{}({})",className,methodName,arguments);
if(className.contains("MySQL")){
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
}elseif(className.contains("Oracle")){
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
}elseif(methodName.contains("MySQL")){
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
}elseif(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);
}elseif(className.contains("Oracle")||methodName.contains("Oracle")){
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_ORACLE);
}else{
CustomerContextHolder.clearCustomerType();
}
*/
}
}
这种拦截器就是不需要在配置文件中加入任何配置进行拦截,算是一种扩展的方法。
三、总结
多数据源动态切换的主要地方在于我们要定义一个自己的数据源来实现AbstractRoutingDataSource中的determineCurrentLookupKey方法,然后通过CustomerContextHolder来实现数据源的切换工作。而数据源的动态切换也就在于我们利用了Spring的Aop中的拦截器Interceptor进行业务类的方法进行拦截,通过类名或方法名中的有效字符串来动态切换到我们定义好的规则对应的数据源。
http://www.cnblogs.com/hoojo/p/Spring_Hibernate_MyBatis_MultipleDataSource_switchDataSource.html