zoukankan      html  css  js  c++  java
  • Spring+Mybatis多数据源的一种实现方式,支持事务

    最近一个项目用到了多个数据库,所以需要实现动态切换数据源来查询数据,http://www.cnblogs.com/lzrabbit/p/3750803.html这篇文章让我受益匪浅,提供了一种自动切换数据源的思路,但这种方式不支持事务,所以我进一步改进了这个方案,下面直入正题

    多数据源配置:

    #============================================================================
    # DataBaseOne
    #============================================================================
    jdbc.one.driver=com.mysql.jdbc.Driver
    jdbc.one.url=jdbc:mysql://127.0.0.1:3306/DataBaseOne?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
    jdbc.one.username=root
    jdbc.one.password=root
    =============================================================================
    
    #============================================================================
    # DataBaseTwo
    #============================================================================
    jdbc.two.driver=com.mysql.jdbc.Driver
    jdbc.two.url=jdbc:mysql://127.0.0.1:3306/DataBaseTwo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
    jdbc.two.username=root
    jdbc.two.password=root
    =============================================================================
    
    #============================================================================
    # DataBaseThree
    #============================================================================
    jdbc.three.driver=com.mysql.jdbc.Driver
    jdbc.three.url=jdbc:mysql://127.0.0.1:3306/DataBaseThree?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
    jdbc.three.username=root
    jdbc.mysql.password=root
    =============================================================================
    
    
    #============================================================================
    # 通用配置
    #============================================================================
    jdbc.initialSize=5
    jdbc.minIdle=5
    jdbc.maxIdle=20
    jdbc.maxActive=100
    jdbc.maxWait=100000
    jdbc.defaultAutoCommit=false
    jdbc.removeAbandoned=true
    jdbc.removeAbandonedTimeout=600
    jdbc.testWhileIdle=true
    jdbc.timeBetweenEvictionRunsMillis=60000
    jdbc.numTestsPerEvictionRun=20
    jdbc.minEvictableIdleTimeMillis=300000

    Spring中使用多数据源:

    <?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:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
          http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    
        <!-- 自己根据情况补全其他配置,以下只提供了数据源配置 -->
    
        <!-- 多数据源配置 -->     
        <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="location" value="classpath:jdbc.properties"/>
        </bean>
        <!-- 第一个数据源dataSourceOne -->
        <bean id="dataSourceOne" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <property name="driverClassName" value="${jdbc.one.driver}"/>
            <property name="url" value="${jdbc.one.url}"/>
            <property name="username" value="${jdbc.one.username}"/>
            <property name="password" value="${jdbc.one.password}"/>
            <property name="initialSize" value="${jdbc.initialSize}"/>
            <property name="minIdle" value="${jdbc.minIdle}"/>
            <property name="maxIdle" value="${jdbc.maxIdle}"/>
            <property name="maxActive" value="${jdbc.maxActive}"/>
            <property name="maxWait" value="${jdbc.maxWait}"/>
            <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/>
            <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
            <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
            <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
            <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
            <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/>
            <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
        </bean>
        <!-- 第二个数据源dataSourceTwo -->
        <bean id="dataSourceTwo" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <property name="driverClassName" value="${jdbc.two.driver}"/>
            <property name="url" value="${jdbc.two.url}"/>
            <property name="username" value="${jdbc.two.username}"/>
            <property name="password" value="${jdbc.two.password}"/>
            <property name="initialSize" value="${jdbc.initialSize}"/>
            <property name="minIdle" value="${jdbc.minIdle}"/>
            <property name="maxIdle" value="${jdbc.maxIdle}"/>
            <property name="maxActive" value="${jdbc.maxActive}"/>
            <property name="maxWait" value="${jdbc.maxWait}"/>
            <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/>
            <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
            <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
            <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
            <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
            <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/>
            <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
        </bean>
        <!-- 第三个数据源dataSourceThree -->
        <bean id="dataSourceThree" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <property name="driverClassName" value="${jdbc.three.driver}"/>
            <property name="url" value="${jdbc.three.url}"/>
            <property name="username" value="${jdbc.three.username}"/>
            <property name="password" value="${jdbc.three.password}"/>
            <property name="initialSize" value="${jdbc.initialSize}"/>
            <property name="minIdle" value="${jdbc.minIdle}"/>
            <property name="maxIdle" value="${jdbc.maxIdle}"/>
            <property name="maxActive" value="${jdbc.maxActive}"/>
            <property name="maxWait" value="${jdbc.maxWait}"/>
            <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/>
            <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
            <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
            <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
            <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
            <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/>
            <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
        </bean>
        <!-- 使用自己实现的数据源实现类MultipleDataSource,这个类随意放在哪个包下都行 -->
        <bean id="multipleDataSource" class="com.cnblogs.datasource.MultipleDataSource">
            <!-- 设置默认的数据源 -->
            <property name="defaultTargetDataSource" ref="dataSourceOne"/>
            <property name="targetDataSources">
                <map>
                    <!-- 这个key是对应数据源的别称,通过这个key可以找到对应的数据源,value-ref就是上面数据源的id -->
                    <entry key="dataSourceOneKey" value-ref="dataSourceOne"/>
                    <entry key="dataSourceTwoKey" value-ref="dataSourceTwo"/>
                    <entry key="dataSourceThreeKey" value-ref="dataSourceThree"/>
                </map>
            </property>
        </bean>
        <!-- 让spring使用我们配置的多数据源 -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="multipleDataSource"/>
        </bean>
    
        <!-- mybatis.spring自动映射 -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.cnblogs.mapper"/>
        </bean>
    
        <!-- 自动扫描,多个包以 逗号分隔 -->
        <context:component-scan base-package="com.cnblogs.**"/>
        
        <!-- AOP配置(事务控制) -->
        <aop:config>
            <!--pointcut元素定义一个切入点,execution中的第一个星号 用以匹配方法的返回类型, 这里星号表明匹配所有返回类型。 com.abc.service.*.*(..)表明匹配com.abc.service包下的所有类的所有方法 -->  
            <aop:pointcut id="myPointcut"
                expression="execution(* com.cnblogs.service.*.*(..))" />
            <!--将定义好的事务处理策略应用到上述的切入点 -->  
            <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" />  
        </aop:config>
    </beans>

    MultipleDataSource.java实现:

    package com.cnblogs.datasource;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /** 多数据源java实现 */
    public class MultipleDataSource extends AbstractRoutingDataSource {
        private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
    
        public static void setDataSourceKey(String dataSource) {
            dataSourceKey.set(dataSource);
        }
    
        @Override
        protected Object determineCurrentLookupKey() {
            return dataSourceKey.get();
        }
    }

    下面详解使用SpringAOP来实现数据源切换,并支持事务控制

    上面我们配置的AOP是针对service层的,所以在调用service层的任何方法时都会经过AOP,因此我们就在AOP中先于service调用时把数据源切换,看代码

    新建类MultipleDataSourceAspectAdvice,把MultipleDataSourceAspectAdvice.java放在spring能自动注入的包中,比如controller包,或者自己把这个类所在的包加入到component-scan配置中去,总之要要让spring能自动加载

    package com.cnblogs.controller;
    
    import org.apache.log4j.Logger;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import com.cnblogs.datasource.MultipleDataSource;
    import com.cnblogs.service.DataBaseOne;
    import com.cnblogs.service.DataBaseTwo;
    import com.cnblogs.service.DataBaseThree;
    
    /**
     * 数据库链接自动切换AOP处理
     * Order优先级设置到最高,因为在所有service方法调用前都必须把数据源确定
     * Order数值越小优先级越高
     */
    @Component
    @Aspect
    @Order(1)
    public class MultipleDataSourceAspectAdvice {
        private static final Logger LOGGER = Logger.getLogger(MultipleDataSourceAspectAdvice.class);
    
        public MultipleDataSourceAspectAdvice() {
            LOGGER.info("MultipleDataSourceAspectAdvice 加载成功");
        }
    
        /**
         * 定义切面
         */
        @Pointcut("execution(* com.cnblogs.service.*.*(..))")
        public void pointCut() {
        }
    
        // dataSourceOneKey
        // dataSourceTwoKey
        // dataSourceThreeKey
        @Around("pointCut()")
        public Object doAround(ProceedingJoinPoint jp) throws Throwable {
            if (jp.getTarget() instanceof DataBaseOne) {
                LOGGER.debug("使用数据库链接:dataSourceOneKey");
                MultipleDataSource.setDataSourceKey("dataSourceOneKey");
            } else if (jp.getTarget() instanceof DataBaseTwo) {
                LOGGER.debug("使用数据库链接:dataSourceTwoKey");
                MultipleDataSource.setDataSourceKey("dataSourceTwoKey");
            } else if (jp.getTarget() instanceof DataBaseThree) {
                LOGGER.debug("使用数据库链接:dataSourceThreeKey");
                MultipleDataSource.setDataSourceKey("dataSourceThreeKey");
            } else {
                // 默认是dataSourceOneKey
                LOGGER.debug("使用数据库链接:dataSourceOneKey");
                MultipleDataSource.setDataSourceKey("dataSourceOneKey");
            }
            return jp.proceed();
        }
    }

    到这里我们所以的spring多数据源配置已经完毕,那如何在执行service方法时让service切换到正确的数据库呢?上面的类中定义了有3个类DataBaseOne,DataBaseTwo,DataBaseThree,这3个类其实只是一个interface,没有任何实现方法,我们让具体业务的service都继承至这3个类以区分不同的service对应不同的数据源,因为业务的service我是知道他用的哪个数据源的,比如FooService继承至DataBaseOne,则在使用FooService任何方法时AOP就会先把数据源切换到dataSourceOneKey,以此就达到了自动切换数据源的目的,并且支持事务,下面看代码:

    package com.cnblogs.service;
    
    /**
     * DataBaseOne数据库占位类
     * 详情参见 MultipleDataSourceAspectAdvice 类
     */
    public interface DataBaseOne {
    
    }

    DataBaseTwo,DataBaseThree与这完全相同

    至此多数据源就配置完毕,可以安心写业务逻辑了

    原理部分博友http://www.cnblogs.com/lzrabbit/p/3750803.html讲的十分清楚,再次感谢

  • 相关阅读:
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车按键启动和蜂鸣器报警
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车指定花式动作
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车指定花式动作
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车指定花式动作
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车前后左右综合实验
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车前后左右综合实验
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车前后左右综合实验
    asp中设置session过期时间方法总结
    asp中设置session过期时间方法总结
    ASP.NET关于Session_End触发与否的问题
  • 原文地址:https://www.cnblogs.com/ieinstein/p/9049749.html
Copyright © 2011-2022 走看看