美文网首页
在mybatis下的多数据源配置

在mybatis下的多数据源配置

作者: 末世狂人 | 来源:发表于2019-12-03 15:48 被阅读0次
        最近遇到一个项目,接口返回数据需要在两个系统中进行获取。考虑到在另一项目中开放接口,在修改的时候需要额外的维护另一个项目的代码,所有对现有项目的数据源配置进行修改,实现多数据源配置。
    

    ps:网上关于mybatis多数据源配置的资料一大堆,质量良莠不齐,所以对自己配置的过程进行一个整理,避免下次在到处找资料
    学习转载:
    Spring MVC+Mybatis 多数据源配置
    Spring-配置文件Bean定义中parent属性详解

    1. 继承AbstractRoutingDataSource

    AbstractRoutingDataSource 是spring提供的一个多数据源抽象类。spring会在使用事务的地方来调用此类的determineCurrentLookupKey()方法来获取数据源的key值。我们继承此抽象类并实现此方法:

    package com.ctitc.collect.manage.datasource;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    /**
     * 
     * @author zongbo
     * 实现spring多路由配置,由spring调用
     */
    public class DataSourceRouter extends AbstractRoutingDataSource {
     
     // 获取数据源名称
     protected Object determineCurrentLookupKey() {
      return HandleDataSource.getDataSource();
     }
    
    }
    

    2. 线程内部数据源处理类

    DataSourceRouter 类中通过HandleDataSource.getDataSource()获取数据源的key值。此方法应该和线程绑定。

    package com.ctitc.collect.manage.datasource;
    /**
     * 线程相关的数据源处理类
     * @author zongbo
     *
     */
    public class HandleDataSource {
     // 数据源名称线程池
     private static final ThreadLocal<String> holder = new ThreadLocal<String>();
    
     /**
      * 设置数据源
      * @param datasource 数据源名称
      */
     public static void setDataSource(String datasource) {
      holder.set(datasource);
     }
     /**
      * 获取数据源
      * @return 数据源名称
      */
     public static String getDataSource() {
      return holder.get();
     }
     /**
      * 清空数据源
      */
     public static void clearDataSource() {
      holder.remove();
     }
    }
    

    3. 自定义数据源注解类

    对于spring来说,注解即简单方便且可读性也高。所以,我们也通过注解在service的方法前指定所用的数据源。我们先定义自己的注解类,其中value为数据源的key值。

    package com.ctitc.collect.manage.datasource;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 数据源注解类
     * @author zongbo
     *
     */
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataSource {
        String value();
    }
    

    4. AOP 拦截service并切换数据源

    指定注解以后,我们可以通过AOP拦截所有service方法,在方法执行之前获取方法上的注解:即数据源的key值。

    package com.ctitc.collect.manage.datasource;
    
    import java.lang.reflect.Method;
    import java.text.MessageFormat;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    
    /**
     * 切换数据源(不同方法调用不同数据源)
     */
    @Aspect
    @Component
    @Order(1) //请注意:这里order一定要小于tx:annotation-driven的order,即先执行DataSourceAspect切面,再执行事务切面,才能获取到最终的数据源
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class DataSourceAspect {
        static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
    
        /**
         * 切入点 service包及子孙包下的所有类
         */
        @Pointcut("execution(* com.ctitc.collect.service..*.*(..))")
        public void aspect() {
        }
    
        /**
         * 配置前置通知,使用在方法aspect()上注册的切入点
         */
        @Before("aspect()")
        public void before(JoinPoint point) {
            Class<?> target = point.getTarget().getClass();
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod() ;
            DataSource dataSource = null ;
            //从类初始化
            dataSource = this.getDataSource(target, method) ;
            //从接口初始化
            if(dataSource == null){
                for (Class<?> clazz : target.getInterfaces()) {
                    dataSource = getDataSource(clazz, method);
                    if(dataSource != null){
                        break ;//从某个接口中一旦发现注解,不再循环
                    }
                }
            }
            
            if(dataSource != null && !StringUtils.isEmpty(dataSource.value()) ){
                HandleDataSource.setDataSource(dataSource.value());
            }
        }
    
        @After("aspect()")
        public void after(JoinPoint point) {
            //使用完记得清空
            HandleDataSource.setDataSource(null);
        }
        
        
        /**
         * 获取方法或类的注解对象DataSource
         * @param target    类class
         * @param method    方法
         * @return DataSource
         */
        public DataSource getDataSource(Class<?> target, Method method){
            try {
                //1.优先方法注解
                Class<?>[] types = method.getParameterTypes();
                Method m = target.getMethod(method.getName(), types);
                if (m != null && m.isAnnotationPresent(DataSource.class)) {
                    return m.getAnnotation(DataSource.class);
                }
                //2.其次类注解
                if (target.isAnnotationPresent(DataSource.class)) {
                    return target.getAnnotation(DataSource.class);
                }
                
            } catch (Exception e) {
                e.printStackTrace();
                logger.error(MessageFormat.format("通过注解切换数据源时发生异常[class={0},method={1}]:"
                        , target.getName(), method.getName()),e)  ;
            }
            return null ;
        }
    }
    

    5:配置数据源

    <!-- 引用配置文件 -->
        <bean id="propertyConfigurer"  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" lazy-init="false">
            <property name="locations">
                <list>
                    <value>classpath*:props/jdbc.properties</value>
                    <value>classpath*:props/config.properties</value>
                </list>
            </property>
        </bean>
    
    <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <description>第一数据源</description>
            <property name="driverClassName" value="${jdbc.driverClassName}" />
            <property name="url" value="${jdbc.url}" />
            <property name="username" value="${jdbc.username}" />
            <property name="password" value="${jdbc.password}" />
            <!-- 配置初始化大小、最小、最大 -->
            <property name="initialSize" value="${druid.initialSize}" />
            <property name="minIdle" value="${druid.minIdle}" />
            <property name="maxActive" value="${druid.maxActive}" />
            <!-- 配置获取连接等待超时的时间 --> 
            <property name="maxWait" value="${druid.maxWait}" />
            <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> 
            <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}"></property>
            <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
            <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}"></property>
            
            <property name="validationQuery" value="${druid.validationQuery}"></property>
            <property name="testWhileIdle" value="${druid.testWhileIdle}"></property>
            <property name="testOnBorrow" value="${druid.testOnBorrow}"></property>
            <property name="testOnReturn" value="${druid.testOnReturn}"></property>
            
            <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}"></property>
            <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}"></property>
            
            <!-- 超过时间限制是否回收 -->  
            <property name="removeAbandoned" value="${druid.removeAbandoned}" />  
            <!-- 超时时间;单位为秒。1800秒=30分钟 -->  
            <property name="removeAbandonedTimeout" value="${druid.removeAbandonedTimeout}" />  
            <!-- 关闭abanded连接时输出错误日志 -->  
            <property name="logAbandoned" value="${druid.logAbandoned}" />  
            
            <property name="proxyFilters">
                <list>
                    <ref bean="stat-filter"/>
               </list>
            </property>
        </bean>
    
        <!--第二数据源 start-->
    
        <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource"  parent="dataSource1">
            <description>第二数据源</description>
            <property name="driverClassName" value="${jdbc.driverClassName}" />
            <property name="url" value="${jdbc.2.url}" />
            <property name="username" value="${jdbc.2.username}" />
            <property name="password" value="${jdbc.2.password}" />
        </bean>
    
        <!--第二数据源 end-->
    

    6.路由设置

        <bean id="multipleDataSource" class="com.stkj.framework.datasource.DataSourceRouter" lazy-init="true">
            <description>多数据源路由</description>
            <property name="targetDataSources">
                <map key-type="java.lang.String" value-type="javax.sql.DataSource">
                    <!-- write -->
                    <entry key="myDataSource1" value-ref="dataSource1"/>
                    <entry key="myDataSource2" value-ref="dataSource2"/>
                </map>
            </property>
            <!-- 默认数据源,如果未指定数据源 或者指定的数据源不存在的话 默认使用这个数据源 -->
            <!--这里的ref指向的是数据源名称,不是别名-->
            <property name="defaultTargetDataSource" ref="dataSource1"/>
        </bean>
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="multipleDataSource"/>
            <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/>
            <property name="configLocation" value="classpath:mybatis/mybatis.cfg.xml"/>
        </bean>
    
        <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" scope="prototype">
            <constructor-arg index="0" ref="sqlSessionFactory"/>
        </bean>
    
        <!-- 事务管理 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="multipleDataSource"/>
        </bean>
    
        <!-- 对标注@Transaction注解的Bean进行事务管理 -->
        <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="2"/>
    
    • dataSource 则是刚刚实现的DataSourceRouter,且需要指定此类的 targetDataSources属性和 defaultTargetDataSource属性。

    targetDataSources :数据源列表,key-value形式,即上面配置的两个数据源
    defaultTargetDataSource:默认数据源,如果未指定数据源 或者指定的数据源不存在的话 默认使用这个数据源

    6.AOP的顺序问题

    由于我使用的注解式事务,和我们的AOP数据源切面有一个顺序的关系。数据源切换必须先执行,数据库事务才能获取到正确的数据源。所以要明确指定 注解式事务和 我们AOP数据源切面的先后顺序。

    我们数据源切换的AOP是通过注解来实现的,只需要在AOP类上加上一个order(1)注解即可,其中1代表顺序号。
    注解式事务的是通过xml配置启动

    <tx:annotation-driven transaction-manager="transactionManager"  proxy-target-class="true" order="2" />
    

    7.示例Demo

    @Override
     @DataSource("busi")
     @Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
     public List<BasVersion> test1() {
      // TODO Auto-generated method stub
      return coreMapper.getVersion();
     }
     
     @Override
     @DataSource("order")
     @Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
     public List<BasVersion> test2() {
      // TODO Auto-generated method stub
      return coreMapper.getVersion();
     }
    

    相关文章

      网友评论

          本文标题:在mybatis下的多数据源配置

          本文链接:https://www.haomeiwen.com/subject/fywcgctx.html