Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法

一、开篇

这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能。所以在出来数据库方言的时候基本上没有什么问题,但唯一可能出现问题的就是在hibernate做添加操作生成主键策略的时候。因为我们都知道hibernate的数据库本地方言会针对不同的数据库采用不同的主键生成策略。
所以针对这一问题不得不采用自定义的主键生成策略,自己写一个主键生成器的表来维护主键生成方式或以及使用其他的方式来生成主键,从而避开利用hibernate默认提供的主键生成方式。
所以存在问题有:怎样动态的切换数据库方言?
这个问题还没有解决,没有更多时间来研究。不过想想应该可以配置两个SessionFactory来实现,那又存在怎么样动态切换SessionFactory呢?!需要解决这个问题才行,而这里则演示怎么样动态切换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 ">


    <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&characterEncoding=UTF-8&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>     

                <entry value-ref="dataSourceOracle" key="oracleDataSource"/>
                <entry value-ref="dataSourceMySQL" key="mySqlDataSource"/>
map>
property>
bean>


    <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.connection.release_mode">after_transactionprop>
                <prop key="hibernate.show_sql">trueprop>
                <prop key="hibernate.format_sql">trueprop>

props>
property>

        <property name="namingStrategy">
            <bean class="com.hoo.framework.hibernate.PrefixedNamingStrategy" />
property>
bean>


    <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>


    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="multipleDataSource"/>
        <property name="configLocation" value="classpath:mybatis.xml"/>

        <property name="mapperLocations">
            <list>

                <value>classpath:com/hoo/framework/mybatis/mybatis-common.xmlvalue>
                <value>classpath:com/hoo/**/resultmap/*-resultmap.xmlvalue>
                <value>classpath:com/hoo/**/mapper/*-mapper.xmlvalue>
                <value>classpath:com/hoo/**/mapper/**/*-mapper.xmlvalue>
list>
property>
bean>


    <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;

/**
* 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();
}
}

CustomerContextHolder

package 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

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-autoproxy/>


    <util:properties id="systemConfig" location="classpath:system.properties" />


    <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">

        <property name="advice" ref="dataSourceMethodInterceptor" />



        <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;

/**
* 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
*/
@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;

/**
* 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
*/
@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;

/**
* 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
*/
@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;

/**
* 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

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;

/**
* 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
@Aspect
public 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();
}
*/
}
}

这种拦截器就是不需要在配置文件中加入任何配置进行拦截,算是一种扩展的方法。

三、总结

多数据源动态切换的主要地方在于我们要定义一个自己的数据源来实现AbstractRoutingDataSource中的determineCurrentLookupKey方法,然后通过CustomerContextHolder来实现数据源的切换工作。而数据源的动态切换也就在于我们利用了Spring的Aop中的拦截器Interceptor进行业务类的方法进行拦截,通过类名或方法名中的有效字符串来动态切换到我们定义好的规则对应的数据源。

Original: https://www.cnblogs.com/hoojo/p/Spring_Hibernate_MyBatis_MultipleDataSource_switchDataSource.html
Author: hoojo
Title: Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/540861/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 宕机了,Redis数据丢了怎么办?

    前言 什么是AOF? 三种写回策略 日志文件太大怎么办? AOF重写会阻塞主线程吗? AOF的缺点 总结 什么是RDB? 给哪些数据做快照? 快照时能够修改数据吗? 多久做一次快照…

    Java 2023年6月14日
    064
  • AOP面向切面

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是…

    Java 2023年6月5日
    0101
  • Vim入门

    一般模式 启动Vim后进入命令模式 此状态下敲击键盘会被Vim识别为命令,而非输入字符 以下为常用的几个命令: i :切换到 输入模式,以输入字符 x :删除当前 光标所在处的字符…

    Java 2023年6月5日
    094
  • 面向对象设计六大原则

    六大设计原则主要是指: 单一职责原则(Single Responsibility Principle); 开闭原则(Open Closed Principle); 里氏替换原则(L…

    Java 2023年6月9日
    078
  • 基于mkcert工具实现Spring Boot 项目服务通过Https方式访问

    mkcert(Windows环境) 2.选择版本 3.以管理员身份运行`命令提示符 1) cd C:/ #进入工具存放的目录下 2) mkcert-v1.4.4-windows-a…

    Java 2023年6月8日
    089
  • 部署-docker资源踩坑

    docker资源踩坑 博主在自己的电脑上,使用docker运行gitlab镜像的时候,发现docker命令失去了响应。但是根据网上的资料显示,gitlab最低配置只需要2核,4GB…

    Java 2023年6月7日
    097
  • Nginx(二)-服务模式运行nginx之WINSW

    虽然使用命令行控制ngix很简单,但是如果作为一个服务工作的话能更方便地启动、停止或者设置依赖项。 这里使用开源项目Windows Service Wrapper 来实现。 git…

    Java 2023年5月30日
    091
  • 记录eclipse中文出现空格宽度不一致的bug

    起因 不久前更新了 eclipse(2019-03) 版本;突然发现出现了,使用注释使用中出现的空格的间隔大小不一致的问题,具体可以看下图: 遇到这种问题简直逼不能忍,在网上搜一下…

    Java 2023年6月10日
    0115
  • Moriis神级遍历!

    Moriis 遍历 Morris 遍历是二叉树遍历的一种方式,传统的递归和非递归遍历的时间复杂的都是O(N),空间复杂度都是O(h)(h为树的高度),而 Morris 遍历可以做到…

    Java 2023年6月8日
    094
  • java RSA生成公钥和私钥

    1.随机生成密钥对 /** * 随机生成密钥对 * @throws NoSuchAlgorithmException */ public static void genKeyPai…

    Java 2023年6月16日
    0104
  • 第一个微信小项目

    第一个好友分析: 我们需要用到wxpy这个库,这个库用到时会弹出一个二维码,这个二维码是通过扫码的方式登录微信,以获取信息 1 #导入模块 2 from wxpy import *…

    Java 2023年6月6日
    074
  • 【转载】JAVA基础:注解

    一、认识注解 注解(Annotation)很重要,未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的…

    Java 2023年5月29日
    0113
  • 【Redis】WRONGTYPE Operation against a key holding the wrong kind of value

    此异常的出现情况之一:第一次为 key 设置 value 值是字符串类型,第二次 为 相同 key 设置 value 值的类型不同。 posted @2022-09-28 20:5…

    Java 2023年6月15日
    080
  • 谈谈对不同I/O模型的理解 (阻塞/非阻塞IO,同步/异步IO)

    一、关于I/O模型的问题 最近通过对ucore操作系统的学习,让我打开了操作系统内核这一黑盒子,与之前所学知识结合起来,解答了长久以来困扰我的关于I/O的一些问题。 1. 为什么r…

    Java 2023年6月8日
    085
  • Java函数式编程

    一、什么是函数是编程 相信大家都使用过面向对象的编程语言,面向对象编程是对数据进 行抽象,而函数式编程是对行为进行抽象。函数式编程让程序员能够写出更加容易阅读的代码。那什么时候函数…

    Java 2023年5月29日
    073
  • AnnotationConfigApplicationContext(1)之初始化Scanner和Reader

    我们以AnnotationConfigApplicationContext为起点来探究Spring的启动过程 首先映入眼帘的就是AnnotationConfigApplicatio…

    Java 2023年6月16日
    079
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球