`

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

分享到:
评论

相关推荐

    spring boot 2多数据源,里面有hibernate和mybatis的多数据源代码

    这些代码可以直接集成到你的项目中,只需要调整为匹配你的数据库配置,就可以实现Spring Boot 2下的多数据源支持,同时利用Hibernate和MyBatis的优势。 总之,多数据源的实现是Spring Boot 2应用中的高级特性,它...

    Spring4.0+SpringMVC4.0+Mybatis3.2框架整合例子(SSM) 自动生成代码

    2. **Spring配置**:创建Spring的核心配置文件(如`applicationContext.xml`),配置Bean的定义,包括数据源、事务管理器以及Mybatis的SqlSessionFactory。 3. **Spring MVC配置**:创建Spring MVC的配置文件(如`...

    spring+hibernate和spring+myBatis实现连接多个数据库,同时操作的项目

    "spring+hibernate和spring+myBatis实现连接多个数据库,同时操作的项目"是针对这种需求的一个解决方案,旨在提供一种灵活且动态的数据源切换机制。 首先,Spring框架作为Java领域中最受欢迎的应用框架之一,其强大...

    spring、struts、hibernate+mybatis 整合在在一起

    在Spring中,我们需要配置数据源、SessionFactory或SqlSessionFactory、Service、DAO等bean,并设置相应的事务管理器。Struts2的配置包括Action配置、结果映射以及拦截器链。在MyBatis中,配置主要包括映射文件...

    springboot(4) 整合mybatis和hibernate

    注意,由于同时使用了MyBatis和Hibernate,可能需要调整Spring Boot的数据源配置,避免两者冲突。在实际项目中,可能需要考虑事务管理,确保在一次操作中使用同一种ORM框架。 在启动项目之前,按照描述中的提示,...

    springMVC+hibernate+mybatis整合

    在配置文件中,我们需要定义数据源、SessionFactory、SqlSessionFactoryBean以及MyBatis的Mapper接口扫描路径。此外,每个Mapper接口对应一个XML文件,用于编写具体的SQL语句。 至于"marketing"这个文件名,可能是...

    springmvc4.1+spring4.1+mybatis3.2+spring-security3.2 jar包

    Spring 4.1 引入了对Java 8的支持,包括日期时间API,改进了Groovy bean定义,增强了数据源配置,以及提升了整体性能和稳定性。 3. **MyBatis** MyBatis 是一个持久层框架,它允许开发者编写SQL语句并与Java对象...

    SPRING ,HIBERNATE,MYBATIS重构系统

    标题中的"SPRING, HIBERNATE, MYBATIS重构系统"涉及到三个核心的Java开发框架,它们在企业级应用开发中占据着重要的地位。Spring是全面的后端应用程序框架,提供依赖注入(DI)和面向切面编程(AOP)等功能;...

    springmvc整合hibernate,mybatis,struts

    整合这三个框架时,需要注意配置文件的正确设置,包括数据库连接信息、数据源配置、事务管理器配置、DAO和Service的bean定义等。同时,为了保持代码整洁,建议遵循一定的编码规范和设计原则,如单一职责原则、开闭...

    Spring+hibernate整合源代码

    3. **数据源配置**:配置数据源是整合的起点。你需要在 Spring 配置文件中定义数据源 Bean,如使用 DriverManagerDataSource 或者 DataSourceProxy,提供连接数据库所需的 JDBC 配置信息。 4. **SessionFactory ...

    spring,struts,hibernate,mybatis的约束文件

    例如,你可以通过XML配置来创建一个数据访问对象(DAO),并将其与数据源连接起来。 2. **Struts**:Struts是一个基于MVC设计模式的Java Web框架,它负责处理用户请求并呈现视图。Struts的配置文件主要是`struts-...

    Spring-SpringMVC-Mybatis整合所有jar包

    3. **配置Mybatis**:创建Mybatis的全局配置文件,指定数据源、事务管理器,以及Mapper配置文件的位置。 4. **编写Mapper接口和Mapper XML文件**:在Java中定义Mapper接口,对应数据库操作;在XML文件中编写SQL语句...

    Spring boot+Atomikos+JTA+Hibernate+mybatis+MySQL实现分布式事务+多数据源

    本案例主要探讨如何利用Spring Boot、Atomikos、JTA(Java Transaction API)、Hibernate以及MyBatis,结合MySQL数据库,实现一个跨数据源的分布式事务解决方案。 首先,Spring Boot是一个基于Spring框架的快速开发...

    spring+hibernate+struts2 +mybatis整合笔记

    1. **新建MyBatis配置文件**:在资源目录下创建`mybatis-config.xml`文件,配置数据源和Mapper映射文件的位置。 ```xml &lt;!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" ...

    springboot-多数据源配置 -mybatis-jpa

    在Spring Boot应用中,多数据源配置是一项关键的技术,它允许我们连接并操作多个数据库,这对于大型企业级应用尤其有用,比如分离读写操作、实现数据冗余或满足不同业务需求。Spring Boot提供了灵活的方式来配置这些...

    spring 3.0.4 +hibernate3.6+mybatis3.0.4+struts 2.1.8+freemark整合

    标题 "spring 3.0.4 + hibernate3.6 + mybatis3.0.4 + struts 2.1.8 + freemarker 整合" 描述了一个经典的Java Web开发集成框架,用于构建高效、可扩展的Web应用程序。这个组合在过去的几年里非常流行,因为它将多个...

    使用maven构建项目,spring mvc,spring,与jdbctemplate,hibernate,mybatis整合

    使用maven构建项目,spring mvc,spring,分别与jdbctemplate,hibernate,mybatis全注解整合,其中包括包含有spring动态代理,数据库的事务处理。以及动态数据源的切换! 最新新增了ehcache缓存的应用

    Spring3+Struts2+Hibernate4+Mybatis整合的一个maven例子

    在多数据源或复杂SQL需求的场景下,Mybatis可以作为一个有效的补充。 5. Maven: Maven 是一个项目管理和综合工具,它通过POM(Project Object Model)文件管理项目的依赖关系,构建过程,以及发布工件。在本示例...

    Spring+struts2+mybatis3环境配置

    综上所述,"Spring+Struts2+Mybatis3环境配置"是一个典型的Java Web开发环境搭建过程,涉及到多个层面的配置和集成,包括各个框架的核心功能、事务管理和日志记录。这个环境为开发者提供了便捷的开发体验,使得业务...

Global site tag (gtag.js) - Google Analytics