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

一、数据源代理:

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

  1. package com.happyheng.datasource;
  2. /**
  3. * 数据源代理设置
  4. * Created by happyheng on 17/1/15.
  5. */
  6. public class DataSourceProxy {
  7. private static ThreadLocal<DataSourceEnum> threadLocal = new ThreadLocal<>();
  8. public enum DataSourceEnum {
  9. MASTER,
  10. SLAVE
  11. }
  12. /**
  13. * 为当前线程设置数据源
  14. */
  15. public static void setDataSource(DataSourceEnum sourceEnum) {
  16. threadLocal.set(sourceEnum);
  17. }
  18. public static DataSourceEnum getDataSource() {
  19. return threadLocal.get();
  20. }
  21. }
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的作用:

  1. <bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"
  2. destroy-method="close">
  3. <property name="driverClassName" value="${master.driver}" />
  4. <property name="url" value="${master.dburl}" />
  5. <property name="username" value="${master.user}" />
  6. <property name="password" value="${master.password}" />
  7. </bean>
  8. <bean id="slaveDataSource1" class="org.apache.commons.dbcp.BasicDataSource"
  9. destroy-method="close">
  10. <property name="driverClassName" value="${slave1.driver}" />
  11. <property name="url" value="${slave1.dburl}" />
  12. <property name="username" value="${slave1.user}" />
  13. <property name="password" value="${slave1.password}" />
  14. </bean>
  15. <bean id="slaveDataSource2" class="org.apache.commons.dbcp.BasicDataSource"
  16. destroy-method="close">
  17. <property name="driverClassName" value="${slave2.driver}" />
  18. <property name="url" value="${slave2.dburl}" />
  19. <property name="username" value="${slave2.user}" />
  20. <property name="password" value="${slave2.password}" />
  21. </bean>
  22. <bean id="dataSource" class="com.happyheng.datasource.OptionalDataSource" >
  23. <!-- 通过key-value的形式来关联数据源 -->
  24. <property name="targetDataSources">
  25. <map>
  26. <entry key="masterDataSource" value-ref="masterDataSource" />
  27. <entry key="slaveDataSource1" value-ref="slaveDataSource1" />
  28. <entry key="slaveDataSource2" value-ref="slaveDataSource2" />
  29. </map>
  30. </property>
  31. <property name="defaultTargetDataSource" ref="masterDataSource" />
  32. </bean>
  33. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  34. <property name="dataSource" ref="dataSource" />
  35. <property name="mapperLocations" value="classpath*:mybatis/**/*Mapper.xml"/>
  36. </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类:

  1. package com.happyheng.datasource;
  2. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
  3. /**
  4. * Created by happyheng on 17/1/10.
  5. */
  6. public class OptionalDataSource extends AbstractRoutingDataSource {
  7. // 数据源
  8. private String masterDataSource = "masterDataSource";
  9. private String[] slaveDataSource = {"slaveDataSource1", "slaveDataSource2"};
  10. @Override
  11. protected Object determineCurrentLookupKey() {
  12. DataSourceProxy.DataSourceEnum dataSourceEnum = DataSourceProxy.getDataSource();
  13. if (dataSourceEnum == DataSourceProxy.DataSourceEnum.SLAVE) {
  14. double random = Math.random();
  15. int randomIndex = (int)(random * slaveDataSource.length);
  16. System.out.println("访问的是从数据库" + (randomIndex + 1));
  17. return slaveDataSource[randomIndex];
  18. } else {
  19. System.out.println("访问的是主数据库");
  20. return masterDataSource;
  21. }
  22. }
  23. }
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一下来鼓励我吧。

从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件的更多相关文章

  1. MySQL中间件之ProxySQL(10):读写分离方法论

    返回ProxySQL系列文章:http://www.cnblogs.com/f-ck-need-u/p/7586194.html 1.不同类型的读写分离 数据库中间件最基本的功能就是实现读写分离,Pr ...

  2. ASP.NET开发实战——(十二)ASP.NET MVC 与数据库之Entity Framework Migrations

    在开发数据库应用程序的时候,经常会遇到某些表需要添加字段或者修改类型.新增表等需求,而对于EF Code First来说关注的只有实体类,当需求变更时只需要添加新的实体类或者在实体类中添加.删除.修改 ...

  3. 第十二章 LNMP架构之分离数据库

    一.课程回顾 1.搭建LNMP环境 1.配置官方源2.yum安装依赖3.yum安装nginx4.配置nginx5.创建用户6.启动并加入开机自启​7.上传安装包8.解压安装包9.卸载旧版本PHP10. ...

  4. mycat数据库集群系列之mycat读写分离安装配置

    最近在梳理数据库集群的相关操作,现在花点时间整理一下关于mysql数据库集群的操作总结,恰好你又在看这一块,供一份参考.本次系列终结大概包括以下内容:多数据库安装.mycat部署安装.数据库之读写分离 ...

  5. mysql数据库的主从同步,实现读写分离 g

    https://blog.csdn.net/qq_15092079/article/details/81672920 前言 1 分别在两台centos 7系统上安装mysql 5.7 2 master ...

  6. mysql数据库的主从同步,实现读写分离

    大型网站为了软解大量的并发访问,除了在网站实现分布式负载均衡,远远不够.到了数据业务层.数据访问层,如果还是传统的数据结构,或者只是单单靠一台服务器来处理如此多的数据库连接操作,数据库必然会崩溃,特别 ...

  7. Excel开发之旅(二)----数据的读写

    1.要实现数据的读写,首先,我们需要添加引用: using Excel=Microsoft.Office.Interop.Excel; 直接在项目中添加即可. 2.给3个按钮添加响应事件,工程代码截图 ...

  8. 【MS SQL】数据库维护计划之数据库备份(二)

    原文:[MS SQL]数据库维护计划之数据库备份(二) 上篇[MS SQL]数据库维护计划之数据库备份(一) 说了数据库备份的一些概念后,这篇以HRP_KQYY数据库备份为例,进行备份计划设置. 考虑 ...

  9. C# 动态创建SQL数据库(二) 在.net core web项目中生成二维码 后台Post/Get 请求接口 方式 WebForm 页面ajax 请求后台页面 方法 实现输入框小数多 自动进位展示,编辑时实际值不变 快速掌握Gif动态图实现代码 C#处理和对接HTTP接口请求

    C# 动态创建SQL数据库(二) 使用Entity Framework  创建数据库与表 前面文章有说到使用SQL语句动态创建数据库与数据表,这次直接使用Entriy Framwork 的ORM对象关 ...

随机推荐

  1. Httpclient爬取优酷网

    参考:http://www.cnblogs.com/lchzls/p/6277210.html /httpClient/src/main/java/com/louis/youku/Page.java ...

  2. spring使用过程中遇到的问题

    1.出现这样的错误:The type org.springframework.core.NestedRuntimeException cannot be resolved. It is indirec ...

  3. 3-C++程序的结构1.1

    数据的共享和保护机制是C++的重要特性之一. 1.标识符的作用域与可见性 作用域讨论的是标识符的有效范围,可见性是讨论标识符是否可以被引用. a.作用域 作用域是一个标识符在程序正文中有效的区域.C+ ...

  4. 如何将linux主机接入到网络中

    前言: 这篇博客的主旨就想文章题目说的那样,当我们拿到一台新的系统时,我们怎么实现让你的主机连接到网络中.配置网络就是给你的系统配置IP地址,子网掩码,网关以及DNS.也就是说配置好这几项计算机上网的 ...

  5. MySql用户配置

    数据库:MySQL5.7 注意事项: MySQL5.7 mysql.user 表没有 password字段 改 authentication_string: 一.前言 我们在创建数据库和权限的时候才用 ...

  6. 洛谷 - UVA11346 - 概率 Probability - 积分

    要是没学过高等数学的积分怎么办呢?可以求助于自适应辛普森法. 容易发现其实这个图形是对称的,我们只要求第一象限就可以了,第一象限如上图. 由于取点是在面积内等概率的,由高中的几何概型可知,所求概率为: ...

  7. now code——处女座的期末复习

    题目描述 快要期末考试了,处女座现在有n门课程需要考试,每一门课程需要花ai小时进行复习,考试的起始时间为bi,处女座为了考试可以不吃饭不睡觉,处女座想知道他能否复习完所有的科目(即在每一门考试之前复 ...

  8. Qt解析CSV文件

    最近需要解析Excel文件,于是顺带写了解析CSV的代码 定义数据类型LX::Sheet #ifndef LX_H #define LX_H #include <QString> #inc ...

  9. TRANSFORM_TEX是做什么的

    简单来说,TRANSFORM_TEX主要作用是拿顶点的uv去和材质球的tiling和offset作运算,确保材质球里的缩放和偏移设置是正确的. (v.texcoord就是顶点的uv) 而_MainTe ...

  10. css 三种引用方式

    内联式 代码 <!doctype html> <html lang="en"> <head> <meta charset="UT ...