mybatis配置多数据源

背景

最近在项目中遇到在同一个工程中需要用到两个数据库的情况,在查询了众多资料后做个总结。参考http://blog.51cto.com/linhongyu/1615895

首先,回顾一下单数据库的配置情况

先配置数据源

<bean id="adminDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="${mifi_admin_master_db_url}"/>
        <property name="username" value="${mifi_admin_master_db_username}"/>
        <property name="password" value="${mifi_admin_master_db_password}"/>
        <property name="validationQuery" value="SELECT 1"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="testWhileIdle" value="true"/>
        <property name="timeBetweenEvictionRunsMillis" value="300000"/>
        <property name="minEvictableIdleTimeMillis" value="1800000"/>
        <property name="numTestsPerEvictionRun" value="-1"/>
        <property name="initialSize" value="20"/>
        <property name="maxActive" value="150"/>
        <property name="maxIdle" value="20"/>
        <property name="minIdle" value="10"/>
        <property name="maxWait" value="1000"/>
    </bean>

然后配置sqlSessionFactory

<bean id="adminSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="adminDataSource"/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>

再配置mapperScannerConfigurer

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.xiaomi.mifi.ins.neo.lucky.dao"/>
        <property name="sqlSessionFactoryBeanName" value="adminSqlSessionFactory"/>
</bean>

现在由于SqlSessionFactoryBean是单例的,所以不能配置两个SqlSessionFactoryBean。所以思路是配置两个数据源,然后配置一个DynamicDataSource动态决定用哪个数据源

1.配置两个数据源

<!-- mifi_insurance -->
<bean id="insuranceDataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="${mifi_insurance_master_url}"/>
        <property name="username" value="${mifi_insurance_master_username}"/>
        <property name="password" value="${mifi_insurance_master_password}"/>
        <property name="validationQuery" value="SELECT 1"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="testWhileIdle" value="true"/>
        <property name="timeBetweenEvictionRunsMillis" value="300000"/>
        <property name="minEvictableIdleTimeMillis" value="1800000"/>
        <property name="numTestsPerEvictionRun" value="-1"/>
        <property name="initialSize" value="20"/>
        <property name="maxActive" value="150"/>
        <property name="maxIdle" value="20"/>
        <property name="minIdle" value="10"/>
        <property name="maxWait" value="1000"/>
    </bean>
    <!-- mifi_insurance_activity -->
    <bean id="activityDataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="${mifi_insurance_activity_master_url}"/>
        <property name="username" value="${mifi_insurance_activity_master_username}"/>
        <property name="password" value="${mifi_insurance_activity_master_password}"/>
        <property name="validationQuery" value="SELECT 1"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="testWhileIdle" value="true"/>
        <property name="timeBetweenEvictionRunsMillis" value="300000"/>
        <property name="minEvictableIdleTimeMillis" value="1800000"/>
        <property name="numTestsPerEvictionRun" value="-1"/>
        <property name="initialSize" value="20"/>
        <property name="maxActive" value="150"/>
        <property name="maxIdle" value="20"/>
        <property name="minIdle" value="10"/>
        <property name="maxWait" value="1000"/>
    </bean>

2.配置DynamicDataSource

先来看看DynamicDataSource的原理,DynamicDataSource继承自AbstractRoutingDataSource。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

而AbstractRoutingDataSource继承自AbstractDataSource,我们可以重点看他的getConnection方法:

public Connection getConnection() throws SQLException {  
    return determineTargetDataSource().getConnection();  
}  
  
public Connection getConnection(String username, String password) throws SQLException {  
     return determineTargetDataSource().getConnection(username, password);  
}

获取连接的方法中,重点是determineTargetDataSource()方法,看源码:

/** 
     * Retrieve the current target DataSource. Determines the 
     * {@link #determineCurrentLookupKey() current lookup key}, performs 
     * a lookup in the {@link #setTargetDataSources targetDataSources} map, 
     * falls back to the specified 
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary. 
     * @see #determineCurrentLookupKey() 
     */  
    protected DataSource determineTargetDataSource() {  
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");  
        Object lookupKey = determineCurrentLookupKey();  
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);  
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {  
            dataSource = this.resolvedDefaultDataSource;  
        }  
        if (dataSource == null) {  
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");  
        }  
        return dataSource;  
    }

上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。

看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

<bean id="dynamicDataSource" class="com.xiaomi.mifi.ins.neo.ins3c.util.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!--通过不同的key决定用哪个dataSource-->
                <entry value-ref="insuranceDataSource" key="insuranceDataSource"></entry>
                <entry value-ref="activityDataSource" key="activityDataSource"></entry>
            </map>
        </property>
        <!--设置默认的dataSource-->
        <property name="defaultTargetDataSource" ref="insuranceDataSource">
        </property>
</bean>
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContext.getDataSource();
    }
}

我们来看一下DataSourceContext的代码

public class DataSourceContext {
    private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        THREAD_LOCAL.set(dataSource);
    }

    public static String getDataSource() {
        return THREAD_LOCAL.get();
    }

    public static void clearDataSource() {
        THREAD_LOCAL.remove();
    }
}

主要关注其中的set方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

现在我们只要在恰当是时间把数据源的key放入其中就可以了,那么在什么时候放入呢,当然是service层的方法中放入比较好。如果每次在代码中手动放入的会比较麻烦,可以写个自定义注解

package com.xiaomi.mifi.ins.neo.ins3c.util.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by zhangjian on 2019/1/4.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "insuranceDataSource";

    public static String insurance = "insuranceDataSource";

    public static String activity = "activityDataSource";
}

这样,每次在service层的方法上加@DataSource(name=“......”)就可以了。

关于自定义spring自定义注解,可以参见这篇文章

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值