SpringMVC4+MyBatis+SQL Server2014实现读写分离
前言
基于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实现读写分离的更多相关文章
- SpringMVC4+MyBatis+SQL Server2014 基于SqlSession实现读写分离(也可以实现主从分离)
前言 上篇文章我觉的使用拦截器虽然方便快捷,但是在使用读串还是写串上你无法控制,我更希望我们像jdbc那样可以手动控制我使用读写串,那么这篇则在sqlsession的基础上实现读写分离, 这种方式则需 ...
- SpringMVC4+MyBatis+SQL Server2014+druid 监控SQL运行情况
前言 在基于SpringMVC+MyBatis的开发过程中,我们希望能看到自己手写SQL的执行情况,在开发阶段我们可以配置log4j在控制台里基于debug模式查看,那么上线后,在生产声我们想查看SQ ...
- spring+mybatis+mysql5.7实现读写分离,主从复制
申明:请尽量与我本博文所有的软件版本保持一致,避免不必要的错误. 所用软件版本列表:MySQL 5.7spring5mybaties3.4.6 首先搭建一个完整的spring5+springMVC5+ ...
- MyBatis多数据源配置(读写分离)
原文:http://blog.csdn.net/isea533/article/details/46815385 MyBatis多数据源配置(读写分离) 首先说明,本文的配置使用的最直接的方式,实际用 ...
- SQL Server 2012 读写分离设置
SQL Server 2012 读写分离设置 - AlsoIn 时间 2014-07-21 17:38:00 博客园-所有随笔区 原文 http://www.cnblogs.com/also/p/ ...
- springboot+mybatis实现数据库的读写分离
介绍 随着业务的发展,除了拆分业务模块外,数据库的读写分离也是常见的优化手段.方案使用了AbstractRoutingDataSource和mybatis plugin来动态的选择数据源选择这个方案的 ...
- Spring+mybatis 实现aop数据库读写分离,多数据库源配置
在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库.Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询.因为在实际的应用中,数据库都是读多写少 ...
- SQL Server数据库读写分离提高并发性
在一些大型的网站或者应用中,单台的SQL Server 服务器可能难以支撑非常大的访问压力.很多人在这时候,第一个想到的就是一个解决性能问题的利器——负载均衡.遗憾的是,SQL Server 的所有版 ...
- SQL Server实现读写分离提高系统并发
转自:http://www.canway.net/Lists/CanwayOriginalArticels/DispForm.aspx?ID=666 在一些大型的网站或者应用中,单台的SQL Serv ...
随机推荐
- Ural 1039 Anniversary Party
题目链接:http://acm.timus.ru/problem.aspx?space=1&num=1039 Dynamic Programming. 建立树形结构,每个employee有两个 ...
- Maven学习 一 概念介绍
一 Maven是什么 Maven是一个Apache公司的开源项目,主要有两个作用:(1)是项目构建工具.(2)是用来管理java程序中jar包的依赖. 它包含了一个项目对象模型(Project Obj ...
- 主引导扇区MBR的解析
http://blog.chinaunix.net/uid-24774106-id-3340397.html 最近排查一个USB相关的故障,由于信息安全就不多说工作上的事情了,顺路学习了MBR的相关知 ...
- C++标准库之右值引用与交付语义
C++标准委员会不应该制定一条阻止程序员拿起枪朝自己的脚丫子开火的规则. 右值引用(rvalue).交付语义(move) 最近阅读<C++标准库第二版>,看到第二章介绍C++11新特性3. ...
- 微服务架构day01
1.微服务架构的基本概念 分布式:将一个项目模块化 区分为多个子项目(自己理解:将业务逻辑层和数据库访问层独立化 通过rpc远程调用(rpc框架 springCould httpCliend ...
- springmvc接收数组方式总结
1.接受正常的数组 如param1=aaa¶m1=bbb¶m1=3 对于这种,在实体参数中,使用String param1[] 这种参数既可以获取数组的值 2.接受数组 ...
- java 日志框架
1.java常用日志框架介绍: https://www.cnblogs.com/chenhongliang/p/5312517.html 2.java各类日志组件汇总: https://blog.cs ...
- git 依据SHA值提取文件patch
git format-patch -M master //当前分支所有超前master的提交 git format-patch -s SHA值 //此SHA值提交以后的所有PATCH git form ...
- 关于JavaScript的操作
一:js基础. 1. var是定义js变量的关键字. 如: var leng=5;定义一个变量为5 var length = 16; // Number 通过数字字面量赋值 var points = ...
- 手动封装on,emit,off
on 绑定 emit 触发 off 解绑 //存放事件eventList = {key:valhandle:[]} 1对多on(eventName,callback);handle:-------N多 ...