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讲的十分清楚,再次感谢

  • 相关阅读:
    section_4.python操作mysqlsql注入导入导出数据库
    section_3.单表多表查询
    section_2.约束索引存储引擎
    Section_1.Mysql基础
    day7.线程-线程队列进程池和线程池回调函数协程
    Mysql小技巧总汇
    常用对照表的参考_chapter-one(Content-Type)
    ORACLE 数据库配置
    Shiro入门(用户权限控制)
    Quartz定时任务调度机制解析(CronTirgger、SimpleTrigger )
  • 原文地址:https://www.cnblogs.com/ieinstein/p/9049749.html
Copyright © 2011-2022 走看看