博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法
阅读量:2228 次
发布时间:2019-05-09

本文共 18263 字,大约阅读时间需要 60 分钟。

1、datasource的配置 applicationContext-datasource.xml
after_transaction
true
true
classpath:com/hoo/framework/mybatis/mybatis-common.xml
classpath:com/hoo/**/resultmap/*-resultmap.xml
classpath:com/hoo/**/mapper/*-mapper.xml
classpath:com/hoo/**/mapper/**/*-mapper.xml
上面分配配置了Oracle和MySQL数据源,MultipleDataSource为自定义的DataSource,它是继承AbstractRoutingDataSource实现抽象方法即可。

 

2、MultipleDataSource实现AbstractRoutingDataSource抽象数据源中方法,定义CustomerContextHolder来动态切换数据源。代码如下:package com.hoo.framework.spring.support; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * function: 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();    }} CustomerContextHolderpackage com.hoo.framework.spring.support; /** * function: 多数据源 * @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
contextHolder = new ThreadLocal
(); 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;  /** * function:多数据源测试服务接口测试 * @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
com.hoo.*.service.impl.*Service*\.*.*
com.hoo.*.mapper.*Mapper*\.*.*
上面的拦截器是拦截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; /** * function: 动态设置数据源拦截器 * @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;  /** * function: 多数据源测试服务接口 * @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; /** * function: 多数据源测试服务接口实现 * @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 */@Servicepublic 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; @Testpublic 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; /** * function: 多数据源测试服务接口实现 * @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 */@Servicepublic 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; /** * function: 多数据源测试服务接口实现 * @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 */@Servicepublic 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; @Testpublic void testOracleService() {    try {        oracleService.execute4MySQL();    } catch (Exception e1) {        e1.printStackTrace();    }    try {        oracleService.execute4Oracle();    } catch (Exception e) {        e.printStackTrace();    }} @Testpublic 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; /** * function: 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
> execute4MySQL() throws Exception; public List
> execute4Oracle() throws Exception;}multiple-datasource-mapper.xml
测试MyBatis的mapper查询接口,在MultipleDataSourceServiceImplTest加入以下代码@Autowiredprivate MultipleDataSourceMapper mapper; @Testpublic 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的配置的拦截器进行业务拦截,也许有些人更喜欢用annotationpackage 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; /** * function: 多数据源动态配置拦截器 * @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@Aspectpublic class MultipleDataSourceInterceptor extends ApplicationLogging {     /**     * function: 动态设置数据源     * @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(); } */ }}这种拦截器就是不需要在配置文件中加入任何配置进行拦截,算是一种扩展的方法。

 本文来源:

 

你可能感兴趣的文章
深入理解JVM虚拟机1:JVM内存的结构与消失的永久代
查看>>
深入理解JVM虚拟机3:垃圾回收器详解
查看>>
深入理解JVM虚拟机4:Java class介绍与解析实践
查看>>
深入理解JVM虚拟机5:虚拟机字节码执行引擎
查看>>
深入理解JVM虚拟机6:深入理解JVM类加载机制
查看>>
深入了解JVM虚拟机8:Java的编译期优化与运行期优化
查看>>
深入理解JVM虚拟机9:JVM监控工具与诊断实践
查看>>
深入理解JVM虚拟机10:JVM常用参数以及调优实践
查看>>
深入理解JVM虚拟机12:JVM性能管理神器VisualVM介绍与实战
查看>>
深入理解JVM虚拟机13:再谈四种引用及GC实践
查看>>
Spring源码剖析1:Spring概述
查看>>
Spring源码剖析2:初探Spring IOC核心流程
查看>>
Spring源码剖析5:JDK和cglib动态代理原理详解
查看>>
Spring源码剖析6:Spring AOP概述
查看>>
分布式系统理论基础1: 一致性、2PC和3PC
查看>>
分布式系统理论基础2 :CAP
查看>>
分布式系统理论基础3: 时间、时钟和事件顺序
查看>>
分布式系统理论基础4:Paxos
查看>>
分布式系统理论基础5:选举、多数派和租约
查看>>
分布式系统理论基础6:Raft、Zab
查看>>