SpringMVC4+MyBatis+SQL Server2014实现数据库读写分离

发布时间 - 2026-01-11 00:48:46    点击率:

前言

      基于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方法,然后从控制台看打印结果

以上所述是小编给大家介绍的SpringMVC4+MyBatis+SQL Server2014实现读写分离,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


# springmvc4  # mybatis  # sql  # server2014  # mybatis实现读写分离  # Spring+Mybatis 实现aop数据库读写分离与多数据库源配置操作  # spring集成mybatis实现mysql数据库读写分离  # springboot配置内存数据库H2教程详解  # 详解spring整合shiro权限管理与数据库设计  # Spring MVC配置双数据源实现一个java项目同时连接两个数据库的方法  # Spring Boot集成MyBatis访问数据库的方法  # Spring jdbc中数据库操作对象化模型的实例详解  # 小编  # 都在  # 在此  # 给大家  # 所述  # 给我留言  # 配置文件  # 感谢大家  # 主键  # 疑问请  # 有任何  # 加载  # 拦截器  # 映射器  # schema  # xsi  # http  # xmlns  # www  # SQLServerDriver 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: javascript中闭包概念与用法深入理解  如何为不同团队 ID 动态生成多个“认领值班”按钮  详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)  Laravel广播系统如何实现实时通信_Laravel Reverb与WebSockets实战教程  laravel怎么为API路由添加签名中间件保护_laravel API路由签名中间件保护方法  Laravel怎么定时执行任务_Laravel任务调度器Schedule配置与Cron设置【教程】  html5的keygen标签为什么废弃_替代方案说明【解答】  JS中对数组元素进行增删改移的方法总结  如何打造高效商业网站?建站目的决定转化率  Laravel如何记录自定义日志?(Log频道配置)  JavaScript中的标签模板是什么_它如何扩展字符串功能  Laravel如何使用Socialite实现第三方登录?(微信/GitHub示例)  如何在万网主机上快速搭建网站?  JavaScript模板引擎Template.js使用详解  Laravel数据库迁移怎么用_Laravel Migration管理数据库结构的正确姿势  如何用PHP快速搭建CMS系统?  如何快速生成可下载的建站源码工具?  Laravel如何使用withoutEvents方法临时禁用模型事件  黑客如何通过漏洞一步步攻陷网站服务器?  如何用y主机助手快速搭建网站?  专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?  详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)  百度浏览器如何管理插件 百度浏览器插件管理方法  网站制作大概要多少钱一个,做一个平台网站大概多少钱?  如何快速搭建高效服务器建站系统?  Python数据仓库与ETL构建实战_Airflow调度流程详解  Laravel如何监控和管理失败的队列任务_Laravel失败任务处理与监控  php后缀怎么变mp4格式错误_修改扩展名提示格式不对怎么办【技巧】  如何在搬瓦工VPS快速搭建网站?  小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?  Laravel软删除怎么实现_Laravel Eloquent SoftDeletes功能使用教程  JavaScript如何实现类型判断_typeof和instanceof有什么区别  laravel怎么为应用开启和关闭维护模式_laravel应用维护模式开启与关闭方法  Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例  Bootstrap CSS布局之列表  如何快速生成高效建站系统源代码?  如何安全更换建站之星模板并保留数据?  php做exe能调用系统命令吗_执行cmd指令实现方式【详解】  Laravel中间件如何使用_Laravel自定义中间件实现权限控制  无锡营销型网站制作公司,无锡网选车牌流程?  打开php文件提示内存不足_怎么调整php内存限制【解决方案】  Laravel如何实现邮箱地址验证功能_Laravel邮件验证流程与配置  Laravel怎么上传文件_Laravel图片上传及存储配置  ,交易猫的商品怎么发布到网站上去?  Laravel如何实现URL美化Slug功能_Laravel使用eloquent-sluggable生成别名【方法】  Laravel Artisan命令怎么自定义_创建自己的Laravel命令行工具完全指南  微信h5制作网站有哪些,免费微信H5页面制作工具?  如何实现建站之星域名转发设置?  如何在Ubuntu系统下快速搭建WordPress个人网站?  如何在 Pandas 中基于一列条件计算另一列的分组均值