从零开发分布式数据库中间件 二、构建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对象关 ...
随机推荐
- bzoj5102
$贪心$ $按左端点排序.$ $当我们钦定了最右的左端点,那么自然希望右端点尽量靠右$ $考虑之前的区间,那么我们相当于选之前的区间中第k大的右端点$ $堆维护一下就可以了,每次把新的元素放进堆,如果 ...
- 【216】◀▶ IDL 字符串操作说明 (黑底)
参考:String Processing Routines —— 字符串处理函数 参考:IDL_String Methods 01 STRING 返回字符串. 02 STRCMP 比较字符串, ...
- 基于粒子群优化的无约束50维Rosenbrock函数求解
基于粒子群优化的无约束50维Rosenbrock函数求解 一.问题重述 无约束50维的Rosenbrock函数可以描述如下: 其中, 0 要求按PSO算法思想设计一个该问题的求解算法. Rosenbr ...
- 堆栈(栈stack)的实现和基本用法(二)
个人网站http://www.ravedonut.com/ 栈的应用: #include <iostream> #include <stack> using namespace ...
- mysql数据库备份bat脚本
@ECHO off TITLE databaseBackup E: REM : 源数据库: IP 端口 用户名 密码 SET DB_HOST=192.168.1.1 SET DB_PORT=3306 ...
- ubuntu系统下挂载新的硬盘
参考 http://zwkufo.blog.163.com/blog/static/258825120141283942244/ http://blog.csdn.net/caicai2526 ...
- shell脚本自动部署nignx反向代理及web服务器,共享存储
#!/bin/bash systemctl status nginx var=$? ] then yum install epel-release -y ] then echo "epel库 ...
- POJ 3658 Artificial Lake (单调栈)
题意: 析:利用单调栈,维护一个单调递增的栈,首先在最低的平台开始,每次向两边进行扩展,寻找两边最低的,然后不断更新宽度. 代码如下: #pragma comment(linker, "/S ...
- HDU - 4006 The kth great number multiset应用(找第k大值)
The kth great number Xiao Ming and Xiao Bao are playing a simple Numbers game. In a round Xiao Ming ...
- yzm10铺瓷砖 一只小蜜蜂 ycb与取款机
yzm10铺瓷砖 一天yzm10接到任务,要求用2×1大小的瓷砖,来铺2×4的地面,地面需要恰好被铺满.这对yzm10来说太容易了,于是他马上设计出了5种不同的铺法(旋转情况算不同种,如图示2.4). ...