前言

  该文是基于上篇《MySQL主从分离的实现》的代码层实现,所以本文配置的主数据库和从数据库的数据源都是在上篇博文中已经介绍了的。

动态选择数据源的配置

  由于我们在写数据的时候需要使用主库的数据源,读的时候需要从库的数据源,我们可以在Spring源码中,通过DataSource可以找到AbstractDataSource抽象类,由于我们需要动态的选择数据源,我们可以通过AbstractDataSource发现他的一个子类是AbstractRoutingDataSource的抽象类,通过类名我们可以知道该类是具有路由功能的,可以路由到不同的数据源,这个类中有一个方法determineTargetDateSource(),该方法就是决定目标数据源的,该方法会调用determineCurrentLookupKey(),就是决定数据源的名字了,该方法是一个抽象的,所以我们需要去继承AbstractRoutingDataSource这个类,并实现determineCurrentLookupKey()这个方法,来动态选择数据源,读数据的时候选择从库的数据源,写操作的时候选择主库的数据源

public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDBType();
}
}

编写DynamicDataSourceHolder

public class DynamicDataSourceHolder {
private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceHolder.class); //ThreadLocal是线程安全的
private static ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static final String DB_MASTER = "master";
public static final String DB_SLAVE = "slave"; public static String getDBType() {
String db = contextHolder.get();
if (db == null){
db = DB_MASTER; // 默认为master,因为master即支持读也支持写
}
return db;
} /**
* 设置线程的dbType
* @param str
*/
public static void setDBType(String str) {
logger.debug("所使用的数据源:"+ str);
contextHolder.set(str);
} /**
* 清理连接类型
*/
public static void clearDBType(){
contextHolder.remove();
}
}

设置mybatis的拦截器

   完成路由后,我们需要依靠拦截器对传递进来的SQL信息来选择数据源,例如传进来的是insert,update,delete语句,就使用主库的数据源,如果是select就选择从库的数据源。

@Intercepts({@Signature(type = Executor.class,method = "update",args = {MappedStatement.class,Object.class}),
@Signature(type = Executor.class,method = "query",
args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class DynamicDataSourceInterceptor implements Interceptor {
private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);
//使用正则表达式匹配增删改
private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*"; /**
* 拦截方法
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 判断当前操作是否是事务的
// 使用@Transactional来处理,则会返回true
boolean transactionActive = TransactionSynchronizationManager.isActualTransactionActive();
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
String lookupKey = DynamicDataSourceHolder.DB_MASTER;
if ( !transactionActive ) {
// 如果是查询操作
if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
//selectKey 为自增id查询主键SELECT_KEY_SUFFIX()方法,使用主库
if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
lookupKey = DynamicDataSourceHolder.DB_MASTER;
} else {
BoundSql boundSql = ms.getSqlSource().getBoundSql(args[1]);
// 对制表符,换行符,空格符就行替换
String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
//增删改使用主库,查使用从库
if (sql.matches(REGEX)) {
lookupKey = DynamicDataSourceHolder.DB_MASTER;
} else {
lookupKey = DynamicDataSourceHolder.DB_SLAVE;
}
}
} } else {
lookupKey = DynamicDataSourceHolder.DB_MASTER;
}
logger.debug("设置方法[{}] use [{}] Strategy,SqlCommandType [{}] ...",
ms.getId(), lookupKey, ms.getSqlCommandType().name());
DynamicDataSourceHolder.setDBType(lookupKey);
return invocation.proceed();
} /**
* 决定返回封装好的对象还是代理对象
* 增删改查得操作
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
//当我们拦截的对象是Executor时,就拦截,通过intercept()方法 决定所使用的数据源
//为什么要拦截Executor类型呢?因为在我们的mybatis中,Executor是用来支持一系列增删改查操作的
//只要我们检测到拦截的对象包含增删改查操作,就拦截下来,使用intercept()方法,决定所使用的数据源
if (target instanceof Executor) {
return Plugin.wrap(target,this);
} else {
return target;
}
} /**
* 在类初始化的时候,去做一些相关的设置
* @param properties
*/
@Override
public void setProperties(Properties properties) { }
}

  只是编写完这个方法是没用的,我们还需要在mybaties-config.xml配置文件中,配置上我们实现的拦截器,如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置全局属性 -->
<settings>
<!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 -->
<setting name="useGeneratedKeys" value="true" /> <!-- 使用列别名替换列名 默认:true -->
<setting name="useColumnLabel" value="true" /> <!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime} -->
<setting name="mapUnderscoreToCamelCase" value="true" />
<!-- 打印查询语句 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<plugins>
<plugin interceptor="cn.reminis.o2o.dao.split.DynamicDataSourceInterceptor" />
</plugins>
</configuration>

配置多数据源

  将原来配置dataSource的bean,改为abstractDatasource,并增加主库数据源和从库数据源的配置,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置整合mybatis过程 -->
<context:property-placeholder location="classpath:jdbc.properties"/> <!-- 2.数据库连接池 -->
<bean id="abstractDataSource" abstract="true" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- 配置连接池属性 -->
<!--
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
--> <!-- c3p0连接池的私有属性 -->
<property name="maxPoolSize" value="30" />
<property name="minPoolSize" value="10" />
<!-- 关闭连接后不自动commit -->
<property name="autoCommitOnClose" value="false" />
<!-- 获取连接超时时间 -->
<property name="checkoutTimeout" value="10000" />
<!-- 当获取连接失败重试次数 -->
<property name="acquireRetryAttempts" value="2" />
</bean> <!--主库的数据源配置-->
<bean id="master" parent="abstractDataSource">
<property name="driverClass" value="${jdbc.master.driver}" />
<property name="jdbcUrl" value="${jdbc.master.url}" />
<property name="user" value="${jdbc.master.username}" />
<property name="password" value="${jdbc.master.password}" />
</bean> <!--从库的数据源配置-->
<bean id="slave" parent="abstractDataSource">
<property name="driverClass" value="${jdbc.slave.driver}" />
<property name="jdbcUrl" value="${jdbc.slave.url}" />
<property name="user" value="${jdbc.slave.username}" />
<property name="password" value="${jdbc.slave.password}" />
</bean> <!--配置动态数据源。这里targetDataSource就是路由数据源的名称-->
<bean id="dynamicDataSource" class="cn.reminis.o2o.dao.split.DynamicDataSource">
<property name="targetDataSources">
<map>
<entry value-ref="master" key="master"></entry>
<entry value-ref="slave" key="slave"></entry>
</map>
</property>
</bean> <!--懒加载,因为数据源是程序运行时决定的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<property name="targetDataSource">
<ref bean="dynamicDataSource" />
</property>
</bean> <!-- 3.配置SqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!-- 扫描entity包 使用别名 -->
<property name="typeAliasesPackage" value="cn.reminis.o2o.entity" />
<!-- 扫描sql配置文件:mapper需要的xml文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean> <!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="cn.reminis.o2o.dao" />
</bean>
</beans>

我们在jdbc.properties配置文件中,配置主从库数据源的地址:

## 主库数据源配置
jdbc.master.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://192.168.0.188:3306/o2o?useUnicode=true&characterEncoding=utf8
jdbc.master.username=root
jdbc.master.password=123456 ## 从库数据源配置
jdbc.slave.driver=com.mysql.jdbc.Driver
jdbc.slave.url=jdbc:mysql://192.168.0.152:3306/o2o?useUnicode=true&characterEncoding=utf8
jdbc.slave.username=root
jdbc.slave.password=root

测试

  我们在执行查询操作时,就会从从库中去查询,我们可以通过查看日志的知,如下:



  当我们执行增删改操作时,就会使用从库的数据源,如下:

  通过测试可知,我们配置主从分离,代码层实现已经成功了,我们从日志也可以看到,我们的系统用户执行更多的操作都是在执行查询操作,我们也可以配置一主多从来减轻服务器的压力。

基于MySql主从分离的代码层实现的更多相关文章

  1. Mysql主从分离介绍及实现

    参考: http://www.cnblogs.com/panxuejun/p/5887118.html https://www.cnblogs.com/alvin_xp/p/4162249.html ...

  2. 基于mysql主从同步的proxy读写分离

    mysql-proxy 简介 MySQL Proxy是一个处于你的client端和MySQL server端之间的简单程序,它可以监测.分析或改变它们的通信.它使用灵活,没有限制,常见的用途包括:负载 ...

  3. Atlas实现mysql主从分离

     可以接受失败,无法接受放弃!加油! 一.介绍Atlas及架构图 Atlas源代码用C语言编写,它对于Web Server相当于是DB,相对于DB相当于是Client,如果把Atlas的逻辑放到Web ...

  4. MySQL主从分离实现

    前言   大型网站为了减轻服务器处理海量的并发访问,所产生的性能问题,采用了很多解决方案,其中最主流的解决方案就是读写分离,即将读操作和写操作分别导流到不同的服务器集群执行,到了数据业务层,数据访问层 ...

  5. Mysql主从分离与双机热备超详细配置

    一.概述 本例是在Windows环境,基于一台已经安装好的Mysql57,在本机安装第二台Mysql57服务. 读完本篇内容,你可以了解到Mysql的主从分离与双机热备的知识,以及配置期间问题的解决方 ...

  6. MySQL主从分离读写复制

    参考教程:http://www.yii-china.com/post/detail/283.html MySQL是开源的关系型数据库系统.复制(Replication)是从一台MySQL数据库服务器( ...

  7. mysql主从分离

    1.工具: 两台机器 master:192.168.0.1 slave:192.168.0.2 2.master的配置 找到mysql的配置文件,一般centos的是/etc/my.cnf,ubunt ...

  8. mysql主从之基于mycat实现读写分离

    一 环境 1.1 结构 192.168.132.125 mycat 192.168.132.121  master 192.168.132.122  slave 主从已经配置完成 1.2 安装myca ...

  9. mysql 主从同步-读写分离

    主从同步与读写分离测试 一.  实验环境(主从同步) Master                   centos 7.3              192.168.138.13 Slave     ...

随机推荐

  1. 使用 Nginx 部署静态页面

    Nginx 介绍 Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器, Nginx,它的发音为「engine X」,是一个高性能的 HTTP 和反向代理服务器,同时也是一个 IMAP/ POP ...

  2. 03 . Shell数组和函数

    Shell数组 简介 数组中可以存放多个值.Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似). 与大部分编程语言类似,数组元素的下标由0开始. S ...

  3. 安装pymysql模块及使用

    安装pymysql模块: https://www.cnblogs.com/Eva-J/articles/9772614.html file--settings for New Projects---P ...

  4. 【FastDFS】如何打造一款高可用的分布式文件系统?这次我明白了!!

    写在前面 前面我们学习了如何基于两台服务器搭建FastDFS环境,而往往在生产环境中,需要FastDFS做到高可用,那如何基于FastDFS打造一款高可用的分布式文件系统呢?别急,今天,我们就一起来基 ...

  5. 「树形DP」洛谷P2607 [ZJOI2008]骑士

    P2607 [ZJOI2008]骑士 题面: 题目描述 Z 国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬. 最近发生了一件可怕的事情,邪恶的 ...

  6. Subset POJ - 3977(折半枚举+二分查找)

    题目描述 Given a list of N integers with absolute values no larger than 10 15, find a non empty subset o ...

  7. locust接口压测

    前言: locust是完全基于python,是一个简单易用的分布式负载测试工具 Locust特性 使用Python编写模拟用户行为的代码,无需繁琐的配置 分布式可扩展,能够支持上百万用户 自带Web界 ...

  8. Spring 5.2.x 源码环境搭建(Windows 系统环境下)

    前期准备 1.确保本机已经安装好了 Git 2.Jdk 版本至少为 1.8 3.安装好 IntelliJ IDEA (其他开发工具,如 eclipse.Spring Tool Suite 等也是可以的 ...

  9. day24 常用模块(下)

    目录 一.logging模块 1 日志级别 2 默认级别为warning,默认打印到终端 3 为logging模块指定全局配置,针对所有的logger有效,控制打印到文件中 4.logging配置文件 ...

  10. C#数据类型及其转换详解

    前言 在 C# 中,数据类型可以分为以下几种类型: 值类型(Value types) 引用类型(Reference types) 指针类型(Pointer types) 其中指针类型只在不安全代码下使 ...