您的位置:首页 > 数据库

从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件

2017-12-28 16:29 465 查看

从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件

在上一节 从零开发分布式数据库中间件 一、读写分离的数据库中间件 中,我们讲了如何通过ThreadLocal来指定每次访问的数据源,并通过jdbc的连接方式来切换数据源,那么这一节我们使用我们常用的数据库持久层框架MyBatis来实现数据库读写分离。

一、数据源代理:

此类与上一节相似,即可以指定当前线程访问的数据源。

[java] view plain copy print?package com.happyheng.datasource;

/**
* 数据源代理设置
* Created by happyheng on 17/1/15.
*/
public class DataSourceProxy {

private static ThreadLocal<DataSourceEnum> threadLocal = new ThreadLocal<>();

public enum DataSourceEnum {
MASTER,
SLAVE
}

/**
* 为当前线程设置数据源
*/
public static void setDataSource(DataSourceEnum sourceEnum) {
threadLocal.set(sourceEnum);
}

public static DataSourceEnum getDataSource() {
return threadLocal.get();
}

}
package com.happyheng.datasource;/** * 数据源代理设置 * Created by happyheng on 17/1/15. */public class DataSourceProxy {    private static ThreadLocal<DataSourceEnum> threadLocal = new ThreadLocal<>();    public enum DataSourceEnum {        MASTER,        SLAVE    }    /**     * 为当前线程设置数据源     */    public static void setDataSource(DataSourceEnum sourceEnum) {        threadLocal.set(sourceEnum);    }    public static DataSourceEnum getDataSource() {        return threadLocal.get();    }}


二、数据源Map:

首先我们需要将我们的读写数据源都写入到配置文件中,并设置到继承了AbstractRoutingDataSource抽象类的子类中,接下来我们会讲解AbstractRoutingDataSource的作用:

[java] view plain copy print?<bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${master.driver}" />
<property name="url" value="${master.dburl}" />
<property name="username" value="${master.user}" />
<property name="password" value="${master.password}" />
</bean>

<bean id="slaveDataSource1" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${slave1.driver}" />
<property name="url" value="${slave1.dburl}" />
<property name="username" value="${slave1.user}" />
<property name="password" value="${slave1.password}" />
</bean>

<bean id="slaveDataSource2" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${slave2.driver}" />
<property name="url" value="${slave2.dburl}" />
<property name="username" value="${slave2.user}" />
<property name="password" value="${slave2.password}" />
</bean>

<bean id="dataSource" class="com.happyheng.datasource.OptionalDataSource" >
<!-- 通过key-value的形式来关联数据源 -->
<property name="targetDataSources">
<map>
<entry key="masterDataSource" value-ref="masterDataSource" />
<entry key="slaveDataSource1" value-ref="slaveDataSource1" />
<entry key="slaveDataSource2" value-ref="slaveDataSource2" />
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:mybatis/**/*Mapper.xml"/>
</bean>
<bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"          destroy-method="close">        <property name="driverClassName" value="${master.driver}" />        <property name="url" value="${master.dburl}" />        <property name="username" value="${master.user}" />        <property name="password" value="${master.password}" />    </bean>    <bean id="slaveDataSource1" class="org.apache.commons.dbcp.BasicDataSource"          destroy-method="close">        <property name="driverClassName" value="${slave1.driver}" />        <property name="url" value="${slave1.dburl}" />        <property name="username" value="${slave1.user}" />        <property name="password" value="${slave1.password}" />    </bean>    <bean id="slaveDataSource2" class="org.apache.commons.dbcp.BasicDataSource"          destroy-method="close">        <property name="driverClassName" value="${slave2.driver}" />        <property name="url" value="${slave2.dburl}" />        <property name="username" value="${slave2.user}" />        <property name="password" value="${slave2.password}" />    </bean>    <bean id="dataSource" class="com.happyheng.datasource.OptionalDataSource" >        <!-- 通过key-value的形式来关联数据源 -->        <property name="targetDataSources">            <map>                <entry key="masterDataSource" value-ref="masterDataSource" />                <entry key="slaveDataSource1" value-ref="slaveDataSource1" />                <entry key="slaveDataSource2" value-ref="slaveDataSource2" />            </map>        </property>        <property name="defaultTargetDataSource" ref="masterDataSource" />    </bean>    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">        <property name="dataSource" ref="dataSource" />        <property name="mapperLocations" value="classpath*:mybatis/**/*Mapper.xml"/>    </bean>


二、AbstractRoutingDataSource数据源路由类:

在MyBatis中,需从SqlSessionFactory中获取dao文件,而SqlSessionFactory即需要数据源,因为我们需要根据不同的情况来选定数据源,所以不能写死一个数据源,而是应该将数据源写入到AbstractRoutingDataSource中的map中,AbstractRoutingDataSource即能够根据不同的情况指定访问的数据源。

还有需要注意的是,AbstractRoutingDataSource是一个抽象类,需要实现其determineCurrentLookupKey方法,来指定每次访问数据库的数据源。

下为继承了AbstractRoutingDataSource的OptionalDataSource类:

[java] view plain copy print?package com.happyheng.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* Created by happyheng on 17/1/10.
*/
public class OptionalDataSource extends AbstractRoutingDataSource {

// 数据源
private String masterDataSource = "masterDataSource";
private String[] slaveDataSource = {"slaveDataSource1", "slaveDataSource2"};

@Override
protected Object determineCurrentLookupKey() {

DataSourceProxy.DataSourceEnum dataSourceEnum = DataSourceProxy.getDataSource();

if (dataSourceEnum == DataSourceProxy.DataSourceEnum.SLAVE) {

double random = Math.random();
int randomIndex = (int)(random * slaveDataSource.length);

System.out.println("访问的是从数据库" + (randomIndex + 1));
return slaveDataSource[randomIndex];
} else {

System.out.println("访问的是主数据库");
return masterDataSource;
}
}
}
package com.happyheng.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/** * Created by happyheng on 17/1/10. */public class OptionalDataSource extends AbstractRoutingDataSource {    // 数据源    private String masterDataSource = "masterDataSource";    private String[] slaveDataSource = {"slaveDataSource1", "slaveDataSource2"};    @Override    protected Object determineCurrentLookupKey() {        DataSourceProxy.DataSourceEnum dataSourceEnum = DataSourceProxy.getDataSource();        if (dataSourceEnum == DataSourceProxy.DataSourceEnum.SLAVE) {            double random = Math.random();            int randomIndex = (int)(random * slaveDataSource.length);            System.out.println("访问的是从数据库" + (randomIndex + 1));            return slaveDataSource[randomIndex];        } else {            System.out.println("访问的是主数据库");            return masterDataSource;        }    }}


首先,我们将一主两从的数据源都写入到OptionalDataSource的map中,而每次MyBatis访问数据库时,都会调用此类的determineCurrentLookupKey()来获取数据源map中的key,从而得到对应的数据源。

可以看出,当我们发现是访问从数据库时,使用随机法来获取从数据库数据源,当发现是访问主数据库时,直接访问主数据库数据源。

4、此项目已在github上开源,可以完整实现MyBatis的数据库读写分离,地址为:github。如果觉得不错,那么就star一下来鼓励我吧。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐