从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件
在上一节 从零开发分布式数据库中间件 一、读写分离的数据库中间件 中,我们讲了如何通过ThreadLocal来指定每次访问的数据源,并通过jdbc的连接方式来切换数据源,那么这一节我们使用我们常用的数据库持久层框架MyBatis来实现数据库读写分离。
一、数据源代理:
此类与上一节相似,即可以指定当前线程访问的数据源。
- 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的作用:
- <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类:
- 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一下来鼓励我吧。
从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件的更多相关文章
- MySQL中间件之ProxySQL(10):读写分离方法论
返回ProxySQL系列文章:http://www.cnblogs.com/f-ck-need-u/p/7586194.html 1.不同类型的读写分离 数据库中间件最基本的功能就是实现读写分离,Pr ...
- ASP.NET开发实战——(十二)ASP.NET MVC 与数据库之Entity Framework Migrations
在开发数据库应用程序的时候,经常会遇到某些表需要添加字段或者修改类型.新增表等需求,而对于EF Code First来说关注的只有实体类,当需求变更时只需要添加新的实体类或者在实体类中添加.删除.修改 ...
- 第十二章 LNMP架构之分离数据库
一.课程回顾 1.搭建LNMP环境 1.配置官方源2.yum安装依赖3.yum安装nginx4.配置nginx5.创建用户6.启动并加入开机自启7.上传安装包8.解压安装包9.卸载旧版本PHP10. ...
- mycat数据库集群系列之mycat读写分离安装配置
最近在梳理数据库集群的相关操作,现在花点时间整理一下关于mysql数据库集群的操作总结,恰好你又在看这一块,供一份参考.本次系列终结大概包括以下内容:多数据库安装.mycat部署安装.数据库之读写分离 ...
- mysql数据库的主从同步,实现读写分离 g
https://blog.csdn.net/qq_15092079/article/details/81672920 前言 1 分别在两台centos 7系统上安装mysql 5.7 2 master ...
- mysql数据库的主从同步,实现读写分离
大型网站为了软解大量的并发访问,除了在网站实现分布式负载均衡,远远不够.到了数据业务层.数据访问层,如果还是传统的数据结构,或者只是单单靠一台服务器来处理如此多的数据库连接操作,数据库必然会崩溃,特别 ...
- Excel开发之旅(二)----数据的读写
1.要实现数据的读写,首先,我们需要添加引用: using Excel=Microsoft.Office.Interop.Excel; 直接在项目中添加即可. 2.给3个按钮添加响应事件,工程代码截图 ...
- 【MS SQL】数据库维护计划之数据库备份(二)
原文:[MS SQL]数据库维护计划之数据库备份(二) 上篇[MS SQL]数据库维护计划之数据库备份(一) 说了数据库备份的一些概念后,这篇以HRP_KQYY数据库备份为例,进行备份计划设置. 考虑 ...
- C# 动态创建SQL数据库(二) 在.net core web项目中生成二维码 后台Post/Get 请求接口 方式 WebForm 页面ajax 请求后台页面 方法 实现输入框小数多 自动进位展示,编辑时实际值不变 快速掌握Gif动态图实现代码 C#处理和对接HTTP接口请求
C# 动态创建SQL数据库(二) 使用Entity Framework 创建数据库与表 前面文章有说到使用SQL语句动态创建数据库与数据表,这次直接使用Entriy Framwork 的ORM对象关 ...
随机推荐
- 【旧文章搬运】深入分析Win7的对象引用跟踪机制
原文发表于百度空间及看雪论坛,2010-09-12 看雪论坛地址:https://bbs.pediy.com/thread-120296.htm============================ ...
- 简单易用"里程碑"、"时间轴"<iOS小组件>
非常感谢,帮助我的朋友们,谢谢你们.上次我的好朋友指出了我编码上(jwTextFiled工具组件)存在一些不规范问题,这次注意提高. 呆毛地址:https://github.com/NIUXINGJI ...
- 2、css的存在形式及优先级
一.优先级 简单可以理解为就近原则: <html lang="en"> <head> <meta charset="UTF-8"& ...
- Flutter实战视频-移动电商-31.列表页_列表切换交互制作
31.列表页_列表切换交互制作 博客地址:https://jspang.com/post/FlutterShop.html#toc-c42 点击左侧的大类右边的小类也跟着变化 新建provide 要改 ...
- python 三元表达式 列表推导式,生成器表达式。递归,匿名函数, 内置函数
三元表达式 三元表达式仅应用于: 1.条件成立返回一个值 2.条件不成立返回一个值 res = x if x>y else y print(res) name= input("姓名&g ...
- SCUT - 114 - 作业之数学篇 - 杜教筛
https://scut.online/p/114 \(A(n)=\sum\limits_{i=1}^{n} \frac{lcm(i,n)}{gcd(i,n)}\) \(=\sum\limits_{i ...
- 洛谷 - P1891 - 疯狂LCM - 线性筛
另一道数据范围不一样的题:https://www.cnblogs.com/Yinku/p/10987912.html $F(n)=\sum\limits_{i=1}^{n} lcm(i,n) $ $\ ...
- 洛谷 - P2278 - 操作系统 - 模拟
https://www.luogu.org/problemnew/show/P2278 题目没有说同时到达的优先级最大的应该处理谁. 讲道理就是处理优先级最大的. #include<bits/s ...
- 已有项目 iPhoneX 适配
一.Assets 文件图片管理下的 LaunchImage 添加 iOS8.0 and latter 一项,并放一张 1125*2436 的LaunchPage 到对应的位置上. 二.有关 iPhon ...
- Git之常用的命令操作
set LESSCHARSET=utf-8 git设置文件名大小写敏感 git branch -rgit checkout 'TestMaster'git checkout -b 'TestMaste ...