前言

基于mybatis的AbstractRoutingDataSource和Interceptor用拦截器的方式实现读写分离,根据MappedStatement的boundsql,查询sql的select、insert、update、delete,根据起判断使用读写连接串。

开发环境

SpringMVC4、mybatis3

项目结构

读写分离实现

1、pom.xml

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>sqljdbc4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency> <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>

2、jdbc.properties

sqlserver.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
sqlserver.url=jdbc:sqlserver://127.0.0.1:1433;databaseName=test sqlserver.read.username=sa
sqlserver.read.password=000000 sqlserver.writer.username=sa
sqlserver.writer.password=000000

3、springmvc-serlvet.xml,主要配置都在这里

<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
"> <!--从配置文件加载数据库信息-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:config/jdbc.properties"/>
<property name="fileEncoding" value="UTF-8"/>
</bean> <!--配置数据源,这里使用Spring默认-->
<bean id="abstractDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${sqlserver.driver}"/>
<property name="url" value="${sqlserver.url}"/>
</bean> <!--读-->
<bean id="shawnTimeDataSourceRead" parent="abstractDataSource">
<property name="username" value="${sqlserver.read.username}"/>
<property name="password" value="${sqlserver.read.password}"/>
</bean> <!--写-->
<bean id="shawnTimeDataSourceWiter" parent="abstractDataSource">
<property name="username" value="${sqlserver.writer.username}"/>
<property name="password" value="${sqlserver.writer.password}"/>
</bean> <bean id="shawnTimeDataSource" class="com.autohome.rwdb.DynamicDataSource">
<property name="readDataSource" ref="shawnTimeDataSourceRead"/>
<property name="writeDataSource" ref="shawnTimeDataSourceRead"/>
</bean> <bean id="shawnTimeTransactionManager" class="com.autohome.rwdb.DynamicDataSourceTransactionManager">
<property name="dataSource" ref="shawnTimeDataSource"/>
</bean> <!--配置sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:springmvc-mybatis.xml"/>
<property name="dataSource" ref="shawnTimeDataSource"/>
<property name="plugins">
<array>
<bean class="com.autohome.rwdb.DynamicPlugin"/>
</array>
</property>
</bean> <!--扫描Mapper-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.autohome.dao"/>
</bean> <!--启用最新的注解器、映射器-->
<mvc:annotation-driven/> <context:component-scan base-package="com.autohome.*"/> <!--jsp视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean> </beans>

4、DynamicDataSource。实现AbstractRoutingDataSource

package com.autohome.rwdb;

import java.util.HashMap;
import java.util.Map; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { private Object writeDataSource; //写数据源 private Object readDataSource; //读数据源 @Override
public void afterPropertiesSet() {
if (this.writeDataSource == null) {
throw new IllegalArgumentException("Property 'writeDataSource' is required");
}
setDefaultTargetDataSource(writeDataSource);
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put(DynamicDataSourceGlobal.WRITE.name(), writeDataSource);
if(readDataSource != null) {
targetDataSources.put(DynamicDataSourceGlobal.READ.name(), readDataSource);
}
setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
} @Override
protected Object determineCurrentLookupKey() { DynamicDataSourceGlobal dynamicDataSourceGlobal = DynamicDataSourceHolder.getDataSource(); if(dynamicDataSourceGlobal == null
|| dynamicDataSourceGlobal == DynamicDataSourceGlobal.WRITE) {
return DynamicDataSourceGlobal.WRITE.name();
} return DynamicDataSourceGlobal.READ.name();
} public void setWriteDataSource(Object writeDataSource) {
this.writeDataSource = writeDataSource;
} public Object getWriteDataSource() {
return writeDataSource;
} public Object getReadDataSource() {
return readDataSource;
} public void setReadDataSource(Object readDataSource) {
this.readDataSource = readDataSource;
}
}

5、DynamicDataSourceGlobal

package com.autohome.rwdb;

public enum DynamicDataSourceGlobal {
READ, WRITE;
}

6、DynamicDataSourceHolder

package com.autohome.rwdb;

public final class DynamicDataSourceHolder {

    private static final ThreadLocal<DynamicDataSourceGlobal> holder = new ThreadLocal<DynamicDataSourceGlobal>();

    private DynamicDataSourceHolder() {
//
} public static void putDataSource(DynamicDataSourceGlobal dataSource){
holder.set(dataSource);
} public static DynamicDataSourceGlobal getDataSource(){
return holder.get();
} public static void clearDataSource() {
holder.remove();
} }

7、DynamicDataSourceTransactionManager

package com.autohome.rwdb;

import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition; public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager { /**
* 只读事务到读库,读写事务到写库
* @param transaction
* @param definition
*/
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) { //设置数据源
boolean readOnly = definition.isReadOnly();
if(readOnly) {
DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.READ);
} else {
DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.WRITE);
}
super.doBegin(transaction, definition);
} /**
* 清理本地线程的数据源
* @param transaction
*/
@Override
protected void doCleanupAfterCompletion(Object transaction) {
super.doCleanupAfterCompletion(transaction);
DynamicDataSourceHolder.clearDataSource();
}
}

8、DynamicPlugin

package com.autohome.rwdb;

import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager; @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 DynamicPlugin implements Interceptor { protected static final Logger logger = LoggerFactory.getLogger(DynamicPlugin.class); private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*"; private static final Map<String, DynamicDataSourceGlobal> cacheMap = new ConcurrentHashMap<String, DynamicDataSourceGlobal>(); @Override
public Object intercept(Invocation invocation) throws Throwable { boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
if(!synchronizationActive) {
Object[] objects = invocation.getArgs();
MappedStatement ms = (MappedStatement) objects[0]; DynamicDataSourceGlobal dynamicDataSourceGlobal = null; if((dynamicDataSourceGlobal = cacheMap.get(ms.getId())) == null) {
//读方法
if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
//!selectKey 为自增id查询主键(SELECT LAST_INSERT_ID() )方法,使用主库
if(ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
} else {
BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
//获取MappedStatement 的sql语句,select update delete insert
String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " "); if(sql.matches(REGEX)) {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
} else {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.READ;
}
}
}else{
dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
}
System.out.println("设置方法["+ms.getId()+"] use ["+ dynamicDataSourceGlobal.name()+"] Strategy, SqlCommandType ["+ms.getSqlCommandType().name()+"]..");
cacheMap.put(ms.getId(), dynamicDataSourceGlobal);
}
DynamicDataSourceHolder.putDataSource(dynamicDataSourceGlobal);
} return invocation.proceed();
} @Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
} @Override
public void setProperties(Properties properties) { }
}

  

测试分离是否实现

运行UserController.index方法,然后从控制台看打印结果

参考

https://github.com/shawntime/shawn-rwdb

http://www.jianshu.com/p/2222257f96d3

  

SpringMVC4+MyBatis+SQL Server2014实现读写分离的更多相关文章

  1. SpringMVC4+MyBatis+SQL Server2014 基于SqlSession实现读写分离(也可以实现主从分离)

    前言 上篇文章我觉的使用拦截器虽然方便快捷,但是在使用读串还是写串上你无法控制,我更希望我们像jdbc那样可以手动控制我使用读写串,那么这篇则在sqlsession的基础上实现读写分离, 这种方式则需 ...

  2. SpringMVC4+MyBatis+SQL Server2014+druid 监控SQL运行情况

    前言 在基于SpringMVC+MyBatis的开发过程中,我们希望能看到自己手写SQL的执行情况,在开发阶段我们可以配置log4j在控制台里基于debug模式查看,那么上线后,在生产声我们想查看SQ ...

  3. spring+mybatis+mysql5.7实现读写分离,主从复制

    申明:请尽量与我本博文所有的软件版本保持一致,避免不必要的错误. 所用软件版本列表:MySQL 5.7spring5mybaties3.4.6 首先搭建一个完整的spring5+springMVC5+ ...

  4. MyBatis多数据源配置(读写分离)

    原文:http://blog.csdn.net/isea533/article/details/46815385 MyBatis多数据源配置(读写分离) 首先说明,本文的配置使用的最直接的方式,实际用 ...

  5. SQL Server 2012 读写分离设置

    SQL Server 2012 读写分离设置 - AlsoIn 时间 2014-07-21 17:38:00  博客园-所有随笔区 原文  http://www.cnblogs.com/also/p/ ...

  6. springboot+mybatis实现数据库的读写分离

    介绍 随着业务的发展,除了拆分业务模块外,数据库的读写分离也是常见的优化手段.方案使用了AbstractRoutingDataSource和mybatis plugin来动态的选择数据源选择这个方案的 ...

  7. Spring+mybatis 实现aop数据库读写分离,多数据库源配置

    在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库.Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询.因为在实际的应用中,数据库都是读多写少 ...

  8. SQL Server数据库读写分离提高并发性

    在一些大型的网站或者应用中,单台的SQL Server 服务器可能难以支撑非常大的访问压力.很多人在这时候,第一个想到的就是一个解决性能问题的利器——负载均衡.遗憾的是,SQL Server 的所有版 ...

  9. SQL Server实现读写分离提高系统并发

    转自:http://www.canway.net/Lists/CanwayOriginalArticels/DispForm.aspx?ID=666 在一些大型的网站或者应用中,单台的SQL Serv ...

随机推荐

  1. .NET winform播放音频文件

    前提:最近要求做一个在winform端做一个音频文件播放的功能,至此,总结最近搜寻的相关资料. 一.微软提供了三种方式来播放音频文件 1.通过System.Media.SoundPlayer来播放 2 ...

  2. javascript常见内存泄露

    一.全局变量引起的内存泄漏 function func(){ lmw = 123456 //lmw是全局变量,不会被释放 } 二.闭包引起的内存泄漏 function func(){ var lmw ...

  3. 学习newton raphson and back eluer

    % % time step https://ww2.mathworks.cn/matlabcentral/answers/184200-newton-raphson-loop-for-backward ...

  4. 【pycharm 密钥】pycharm 2017 密钥

    server选项里边输入:  http://idea.liyang.io   亲测可用!!!

  5. MFC开发中添加自定义消息和消息响应函数

    (1)在.h或.cpp文件定义一个消息 #define CLICK_MESSAGE_BOX WM_USER+1001 //add by 20180612 给主窗口ctrl.cpp发送消息 //自定义消 ...

  6. RunTime之类与对象

    我们知道,Objective-C是一门动态语言,它将很多静态语言在编译时期做的事放到了运行时来处理.用C++编写的程序通过编译器直接把函数地址硬编码进入可执行文件:而Objective-C无法通过编译 ...

  7. 【洛谷4770】 [NOI2018]你的名字(SAM,线段树合并)

    传送门 洛谷 Solution 做过的比较玄学的后缀自动机. 果然就像\(Tham\)所讲,后缀自动机这种东西考场考了不可能做的出来的... 考虑如果\(l=1,r=|S|\)的怎么做? 直接建后缀自 ...

  8. 利用树莓派3和RTL-SDR V3搭建一个低成本的QRP小功率监测点

    TUTORIAL: SETTING UP A LOW COST QRP (FT8, JT9, WSPR ETC) MONITORING STATION WITH AN RTL-SDR V3 AND R ...

  9. Linux pwn入门教程——格式化字符串漏洞

    本文作者:Tangerine@SAINTSEC 原文来自:https://bbs.ichunqiu.com/thread-42943-1-1.html 0×00 printf函数中的漏洞printf函 ...

  10. hadoop安装hbase

    1.安装hadoop hadoop安装请参考我的centoos 安装hadoop集群 在安装hadoop的基础上新增了两台slave机器,新增后的配置为 H30(192.168.3.238) mast ...