`
danwind
  • 浏览: 233905 次
  • 性别: Icon_minigender_1
  • 来自: 广东
社区版块
存档分类
最新评论

Spring 动态切换数据源

 
阅读更多

一、开篇

这里整合分别采用了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进行业务类的方法进行拦截,通过类名或方法名中的有效字符串来动态切换到我们定义好的规则对应的数据源。

 

 

本文转载自:http://blog.csdn.net/ibm_hoojo/article/details/12650399

分享到:
评论

相关推荐

    spring 动态切换数据源

    在Spring框架中,动态切换数据源是一项重要的功能,它允许应用程序根据业务需求在多个数据库之间灵活切换。这一特性对于多租户系统、读写分离、分布式数据库等场景尤其有用。以下将详细介绍如何实现Spring的动态数据...

    spring动态切换数据源

    在Java的Spring框架中,动态切换数据源是一项重要的功能,尤其在多租户、微服务或者需要根据业务逻辑切换数据库的场景下。本知识点主要围绕如何在Spring中实现数据源的动态切换进行深入探讨。 首先,我们需要理解...

    真正意义的spring动态切换数据源源码

    本文将深入探讨“真正意义的Spring动态切换数据源”这一主题,并结合源码进行分析。 动态数据源切换是在多数据库环境下的常见需求,例如在测试和生产环境中使用不同的数据库,或者在微服务架构中,每个服务可能需要...

    mybatis+spring实现动态切换数据源

    在本项目中,我们将探讨如何利用MyBatis与Spring框架实现动态切换数据源的功能。首先,我们需要理解MyBatis和Spring的基本概念以及它们如何协同工作。 MyBatis是一个优秀的持久层框架,它简化了Java与数据库之间的...

    Spring动态切换多数据源Demo

    此外,为了在代码中切换数据源,通常会有一个数据源路由的策略,例如在Service层根据业务逻辑决定使用哪个数据源。这可能涉及到AOP(面向切面编程)的使用,通过注解或配置来切换数据源。 在实际开发中,多数据源的...

    Spring+SpringMvc+MybatisPlus+Aop(自定义注解)动态切换数据源

    本项目“Spring+SpringMvc+MybatisPlus+Aop(自定义注解)动态切换数据源”正是针对这一需求提供的一种解决方案。下面将详细介绍这个项目中的关键技术点和实现原理。 首先,Spring框架是Java企业级应用开发的核心...

    SpringBoot配置多数据源实现动态切换数据源

    接下来是动态切换数据源的关键,这通常通过AOP(面向切面编程)和ThreadLocal来实现。创建一个自定义的`Aspect`,并在方法执行前根据业务逻辑选择合适的数据源: ```java @Aspect @Component public class ...

    springboot-AOP实现多数据源动态切换(Druid连接池)

    在Spring Boot项目中实现多数据源动态切换是一项高级特性,能够使应用根据不同业务需求访问不同的数据库,从而实现服务的解耦和数据库操作的优化。该技术的关键在于如何在同一个应用中配置和使用多个数据源,以及...

    Springcloud 多数库 多数据源整合,查询动态切换数据库

    2. **动态切换数据源**:在Spring框架中,我们可以利用AOP(面向切面编程)和ThreadLocal来实现数据源的动态切换。创建一个自定义的数据源切换注解,比如`@SwitchDataSource`,并在需要切换数据源的方法上使用。通过...

    spring boot动态切换多数据源

    动态切换数据源意味着程序可以根据运行时的需求,灵活地选择要使用的数据源。这对于处理复杂业务逻辑和优化系统性能至关重要。例如,读操作可能指向一个只读的数据源,而写操作则连接到主数据库。 4. **实现方式**...

    Spring Boot多数据源(支持Spring声明式事务切换和回滚).pdf

    5. **事务内切换数据源**: - 引入了一个创新的特性,即在同一个事务内支持数据源的切换,并且兼容Spring的声明式事务管理。这意味着在事务中,可以按需在不同的数据源之间平滑切换,而无需担心事务一致性问题。 6...

    spring boot多数据源(AOP注解动态切换)

    通过以上步骤,我们成功地在Spring Boot中实现了基于AOP注解的多数据源动态切换。这使得在运行时可以根据业务需求选择合适的数据源,提高了代码的灵活性和可维护性。在实际项目中,你可能还需要考虑事务管理、数据源...

    SpringBoot+Atomikos+动态多数据源+事务+2种切换数据源的方式

    本主题将深入探讨如何利用SpringBoot结合Atomikos实现动态多数据源以及事务管理,并介绍两种切换数据源的方法。 首先,SpringBoot简化了传统Spring应用的初始化过程,它通过自动配置和starter包让开发者快速搭建...

    Spring(AbstractRoutingDataSource)实现动态数据源切换示例

    【Spring 动态数据源切换】使用 `AbstractRoutingDataSource` 的详细实现在处理多数据库环境时,Spring 提供了一个强大的工具 `AbstractRoutingDataSource`,它允许我们根据特定条件动态地切换数据源。本文将深入...

    ssm动态切换数据源

    本文将深入探讨如何在基于SSM(Spring、Spring MVC、MyBatis)的项目中实现“动态切换数据源”,这是一个在多数据库环境下非常重要的功能。 首先,我们来理解“动态切换数据源”的概念。在大型应用中,通常会根据...

    spring mybatis 多数据源动态切换

    Spring 和 MyBatis 结合使用时,实现多数据源动态切换是一项重要的技术。本文将深入探讨如何在 Spring 中配置和管理多个数据源,并实现动态切换。 首先,我们需要理解“多数据源”是什么。它是指在一个应用中同时...

    Spring MVC 切换数据源

    本文将深入探讨如何在Spring MVC中实现数据源的动态切换,以及如何与MySQL数据库协同工作,同时利用MyEclipse作为开发环境。 首先,我们需要理解Spring MVC中的数据源(DataSource)是应用程序连接到数据库的桥梁。...

    spring动态选择数据源

    本篇文章将探讨“Spring动态选择数据源”这一主题,它允许我们的应用程序根据业务需求在多个数据源之间灵活切换,这在多租户、读写分离等场景下尤其重要。 首先,理解“数据源”在Spring中的概念。数据源...

    mybatis+spring实现动态切换数据源 maven项目

    mybatis+spring实现动态切换数据源,修改数据源配置信息之后,直接运行test可进行测试 maven项目 导入即可 jar包都不用 适合学习参考 出自Java自学网 (Java自学网专供 就是免积分下载)

Global site tag (gtag.js) - Google Analytics