使用Spring配置多数据源实现读写分离

随着业务不断发展, 数据量变得越来越大, 数据库的压力也与日俱增, 这时我们会考虑SQL优化, 分表分库, 读写分离这些策略来提高数据库的性能. 本篇博客这里只介绍读写分离策略, 什么是读写分离策略呢?

比如我们把数据库分为一个Master库, n个Slave库, Master库用来处理写操作和实时性高的读操作, Slave库用来处理实时性不高的读操作, 这样就有效地减轻数据库压力.

使用Spring实现读写分离

技术

  • Spring
  • Mybatis
  • MySQL

方案

以前我们都是直接配置c3p0, druid等这些第三方数据源, 现在我们需要使用自定义的数据源.

(1) 以前的配置

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>

(2) 现在的配置

<!--注册Master数据源-->
<bean id="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<!--注册Slave1数据源-->
<bean id="dataSourceSlave1" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<!--注册Slave2数据源-->
<bean id="dataSourceSlave2" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.tyshawn.muti_datasource.DynamicDataSource">
    <property name="defaultTargetDataSource" ref="dataSourceMaster"/>
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="master" value-ref="dataSourceMaster"/>
            <entry key="slave1" value-ref="dataSourceSlave1"/>
            <entry key="slave2" value-ref="dataSourceSlave2"/>
        </map>
    </property>
</bean>

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

DynamicDataSource类就是我们自己定义的数据源, 它需要继承AbstractRoutingDataSource类, 重写其中一部分的方法. 我们先来看下AbstractRoutingDataSource类的内部实现.

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    private Map<Object, Object> targetDataSources;
    private Object defaultTargetDataSource;
    private Map<Object, DataSource> resolvedDataSources;
    private DataSource resolvedDefaultDataSource;

    public AbstractRoutingDataSource() {
    }

    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        this.targetDataSources = targetDataSources;
    }

    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        this.defaultTargetDataSource = defaultTargetDataSource;
    }

    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            this.resolvedDataSources = new HashMap(this.targetDataSources.size());
            Iterator var1 = this.targetDataSources.entrySet().iterator();

            while(var1.hasNext()) {
                Entry<Object, Object> entry = (Entry)var1.next();
                Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
                DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
                this.resolvedDataSources.put(lookupKey, dataSource);
            }

            if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }

        }
    }

    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource 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 + "]");
        } else {
            return dataSource;
        }
    }

    protected abstract Object determineCurrentLookupKey();
}

上面是AbstractRoutingDataSource类的核心代码, 为了便于阅读, 我删除了一部分代码. 在配置文件中我们配置了defaultTargetDataSource和targetDataSources的初始值. AbstractRoutingDataSource的实现类在初始化时会执行afterPropertiesSet()方法, 其功能是将defaultTargetDataSource转化为resolvedDefaultDataSource, 将targetDataSources转化为resolvedDataSources. 请求每一次调用数据源时都会执行determineTargetDataSource()方法, 它决定访问的数据源是哪个.

determineTargetDataSource()方法是如何决定访问哪个数据源的呢? 注意下面这两行代码

Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);

通过调用determineCurrentLookupKey()方法来获取key值, 然后根据key值从resolvedDataSources中获取数据源. 我们要做的就是重写determineCurrent

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值