在上一节 从零开发分布式数据库中间件 一、读写分离的数据库中间件 中,我们讲了如何通过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. office2007下载地址

    thunder://QUFodHRwOi8vNDYuZHVvdGUub3JnOjgwODAvb2ZmaWNlMjAwN3Byby56aXBaWg==thunder://QUFodHRwOi8vNTEu ...

  2. SQL 系统表

    http://www.cnblogs.com/asdcer/archive/2007/05/14/746377.aspx

  3. Locked Treasure

    题意: 有一宝箱,宝箱上有一些锁,有n个人,每个人分管一些钥匙(每个锁对应无数个钥匙), 现给出n,m 求问至少需要多少锁才能存在一种钥匙分配方案使得: 1.至少m个人才可以打开锁. 2.任选m个人一 ...

  4. web.xml报错Cannot resolve class 'StrutsPrepareAndExecuteFilter' (idea创建SSH项目)

    原因: xwork-core.jar包已经合并到struts2-core.jar下,并且点开jar包,发现没有 org.apache.struts2.dispatcher.ng.filter.Stru ...

  5. HDU-2616

    Kill the monster Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  6. 【Data Structure & Algorithm】字符串全排列

    字符串全排列 题目:输入一个字符串,打印出该字符串的所有排列.例如输入字符串abc,则输出由字符a.b.c所能排列出来的所有字符串abc.acb.bac.bca.cab.cba. 分析:考察对递归的理 ...

  7. C# 选择文件路径,选择文件

    // 选择文件: private string SelectPath() { string path = string.Empty; var openFileDialog = new Microsof ...

  8. hdoj5289【RMQ+二分】【未完待续】

    思路: 对当前值查找最近满足位置: 利用RMQ求出区间最大最小值,再枚举右端点,二分区间找到满足要求的最大区间累加

  9. Some JPR highlights (JPR 2019 March)

    Journal Name:Journal of Proteome Research Issue:2019 March Shared by: Weining Zhao 1.     Acetylome: ...

  10. sed 删除指定行

    参考:http://blog.sina.com.cn/s/blog_4ba5b45e0102e7l2.html