现在大型的电子商务系统,在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库。Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询。因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较少),而读取数据通常耗时比较长,占用数据库服务器的CPU较多,从而影响用户体验。我们通常的做法就是把查询从主库中抽取出来,采用多个从库,使用负载均衡,减轻每个从库的查询压力。

  采用读写分离技术的目标:有效减轻Master库的压力,又可以把用户查询数据的请求分发到不同的Slave库,从而保证系统的健壮性。我们看下采用读写分离的背景。

  随着网站的业务不断扩展,数据不断增加,用户越来越多,数据库的压力也就越来越大,采用传统的方式,比如:数据库或者SQL的优化基本已达不到要求,这个时候可以采用读写分离的策 略来改变现状。

   具体到开发中,如何方便的实现读写分离呢?目前常用的有两种方式:

  1 第一种方式是我们最常用的方式,就是定义2个数据库连接,一个是MasterDataSource,另一个是SlaveDataSource。更新数据时我们读取MasterDataSource,查询数据时我们读取SlaveDataSource。这种方式很简单,我就不赘述了。

  2 第二种方式动态数据源切换,就是在程序运行时,把数据源动态织入到程序中,从而选择读取主库还是从库。主要使用的技术是:annotation,Spring AOP ,反射。下面会详细的介绍实现方式。

   在介绍实现方式之前,我们先准备一些必要的知识,spring 的AbstractRoutingDataSource 类

     AbstractRoutingDataSource这个类 是spring2.0以后增加的,我们先来看下AbstractRoutingDataSource的定义:

   public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean  {}

    AbstractRoutingDataSource继承了AbstractDataSource ,而AbstractDataSource 又是DataSource 的子类。DataSource   是javax.sql 的数据源接口,定义如下:

  

public interface DataSource  extends CommonDataSource,Wrapper {

  /**
* <p>Attempts to establish a connection with the data source that
* this <code>DataSource</code> object represents.
*
* @return a connection to the data source
* @exception SQLException if a database access error occurs
*/
Connection getConnection() throws SQLException; /**
* <p>Attempts to establish a connection with the data source that
* this <code>DataSource</code> object represents.
*
* @param username the database user on whose behalf the connection is
* being made
* @param password the user's password
* @return a connection to the data source
* @exception SQLException if a database access error occurs
* @since 1.4
*/
Connection getConnection(String username, String password)
throws SQLException; }

DataSource 接口定义了2个方法,都是获取数据库连接。我们在看下AbstractRoutingDataSource 如何实现了DataSource接口:

public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
} public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}

很显然就是调用自己的determineTargetDataSource()  方法获取到connection。determineTargetDataSource方法定义如下:

protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}

我们最关心的还是下面2句话:

   Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);

determineCurrentLookupKey方法返回lookupKey,resolvedDataSources方法就是根据lookupKey从Map中获得数据源。resolvedDataSources 和determineCurrentLookupKey定义如下:

  private Map<Object, DataSource> resolvedDataSources;

  protected abstract Object determineCurrentLookupKey()

  看到以上定义,我们是不是有点思路了,resolvedDataSources是Map类型,我们可以把MasterDataSource和SlaveDataSource存到Map中,如下:

    key        value

    master             MasterDataSource

    slave                  SlaveDataSource

  我们在写一个类DynamicDataSource  继承AbstractRoutingDataSource,实现其determineCurrentLookupKey() 方法,该方法返回Map的key,master或slave。

  好了,说了这么多,有点烦了,下面我们看下怎么实现。

  上面已经提到了我们要使用的技术,我们先看下annotation的定义:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}

我们还需要实现spring的抽象类AbstractRoutingDataSource,就是实现determineCurrentLookupKey方法:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSouce();
}
}
public class DynamicDataSourceHolder {
public static final ThreadLocal<String> holder = new ThreadLocal<String>(); public static void putDataSource(String name) {
holder.set(name);
} public static String getDataSouce() {
return holder.get();
}
}

从DynamicDataSource 的定义看出,他返回的是DynamicDataSourceHolder.getDataSouce()值,我们需要在程序运行时调用DynamicDataSourceHolder.putDataSource()方法,对其赋值。下面是我们实现的核心部分,也就是AOP部分,DataSourceAspect定义如下:

public class DataSourceAspect {

    public void before(JoinPoint point)
{
Object target = point.getTarget();
String method = point.getSignature().getName(); Class<?>[] classz = target.getClass().getInterfaces(); Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = classz[0].getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m
.getAnnotation(DataSource.class);
DynamicDataSourceHolder.putDataSource(data.value());
System.out.println(data.value());
} } catch (Exception e) {
// TODO: handle exception
}
}
}
 
public class DataSourceType {
/** master **/
public static final String MASTER = "master";
/** slave **/
public static final String SLAVE = "slave";
}

为了方便测试,我定义了2个数据库,表结构一致,但数据不同,数据库配置如下:

<!-- 配置数据源 第一数据源(写库) -->
<bean name="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="initialSize" value="${jdbc.initialSize}" />
<property name="minIdle" value="${jdbc.minIdle}" />
<property name="maxActive" value="${jdbc.maxActive}" />
<property name="maxWait" value="${jdbc.maxWait}" />
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${jdbc.validationQuery}" />
<property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
<property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
<property name="testOnReturn" value="${jdbc.testOnReturn}" />
<property name="removeAbandoned" value="${jdbc.removeAbandoned}" />
<property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}" />
<property name="filters" value="${jdbc.filters}" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<property name="proxyFilters">
<list>
<ref bean="log-filter"/>
</list>
</property>
</bean> <!-- 配置数据源 第二数据源(读库) -->
<bean name="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${slave.jdbc.url}" />
<property name="username" value="${slave.jdbc.username}" />
<property name="password" value="${slave.jdbc.password}" />
</bean> <bean id="dataSource" class="com.abc.cc.web.common.core.source.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- write -->
<entry key="master" value-ref="masterDataSource"/>
<!-- read -->
<entry key="slave" value-ref="slaveDataSource"/>
</map> </property>
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations">
<list>
<value>classpath:com/abc/cc/web/*/*/mapper/*.xml</value>
</list>
</property>
</bean> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<bean id="baseMybatisDao" class="com.qdingnet.pcloud.resource.common.dao.BaseMybatisDao" >
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.abc.cc.web.*.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean> <!-- 配置数据库注解aop -->
<aop:aspectj-autoproxy />
<bean id="manyDataSourceAspect" class="com.qdingnet.pcloud.resource.common.core.source.DataSourceAspect"/>
<aop:config>
<aop:aspect id="c" ref="manyDataSourceAspect">
<aop:pointcut id="tx" expression="execution(* com.abc.cc.web.*.dao.*.*(..))"/>
<aop:before pointcut-ref="tx" method="before"/>
</aop:aspect>
</aop:config>

下面是MyBatis的mapper的定义,读的都是slave:

    @DataSource(DataSourceType.SLAVE)
List<RemindInfo> getRemindListByUserId(@Param("userId")String userId,@Param("pageIndex")Integer pageIndex,@Param("pageSize")Integer pageSize); @DataSource(DataSourceType.SLAVE)
Integer getRemindCountByUserId(@Param("userId")String userId);

转载,原文地址:http://www.cnblogs.com/surge/p/3582248.html

SpringMVC+ Mybatis 配置多数据源 + 自动数据源切换 + 实现数据库读写分离的更多相关文章

  1. Spring + Mybatis项目实现数据库读写分离

    主要思路:通过实现AbstractRoutingDataSource类来动态管理数据源,利用面向切面思维,每一次进入service方法前,选择数据源. 1.首先pom.xml中添加aspect依赖 & ...

  2. spring+mybatis利用interceptor(plugin)兑现数据库读写分离

    使用spring的动态路由实现数据库负载均衡 系统中存在的多台服务器是"地位相当"的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这 ...

  3. Spring+MyBatis实现数据库读写分离方案

    推荐第四种:https://github.com/shawntime/shawn-rwdb 方案1 通过MyBatis配置文件创建读写分离两个DataSource,每个SqlSessionFactor ...

  4. 使用Adivisor配置增强处理,来实现数据库读写分离

    一.先写一个demo来概述Adivisor的简单使用步骤 实现步骤: 1.通过MethodBeforeAdivice接口实现前置增强处理 public class ServiceBeforeAdvis ...

  5. Linux安装Mycat1.6.7.4并实现Mysql数据库读写分离简单配置

    1. Mycat简介 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务.ACID.可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群 一 ...

  6. Orchestrator+Proxysql 实现自动导换+应用透明读写分离

    前面一篇博客我介绍了如何通过Orachestrator+脚本实现自动导换. 这一章,我来演示一下如何通过Orchestrator+Proxysql 实现自动导换+应用透明读写分离 总体架构 可选架构1 ...

  7. yii2的数据库读写分离配置

    简介 数据库读写分离是在网站遇到性能瓶颈的时候最先考虑优化的步骤,那么yii2是如何做数据库读写分离的呢?本节教程来给大家普及一下yii2的数据库读写分离配置. 两个服务器的数据同步是读写分离的前提条 ...

  8. centos MySQL主从配置 ntsysv chkconfig setup命令 配置MySQL 主从 子shell MySQL备份 kill命令 pid文件 discuz!论坛数据库读写分离 双主搭建 mysql.history 第二十九节课

    centos  MySQL主从配置 ntsysv   chkconfig  setup命令  配置MySQL 主从 子shell  MySQL备份  kill命令  pid文件  discuz!论坛数 ...

  9. EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~终结~配置的优化和事务里读写的统一

    回到目录 本讲是通过DbCommand拦截器来实现读写分离的最后一讲,对之前几篇文章做了一个优化,无论是程序可读性还是实用性上都有一个提升,在配置信息这块,去除了字符串方式的拼接,取而代之的是sect ...

随机推荐

  1. NineveGL引擎学习笔记

  2. Hass.io: add-on Samba

    { "workgroup": "WORKGROUP", "name": "hassio", "guest&qu ...

  3. 软件开发 [CJOJ 1101] [NOIP 模拟]

    Description 一个软件开发公司同时要开发两个软件,并且要同时交付给用户,现在公司为了尽快完成这一任务,将每个软件划分成m个模块,由公司里的技术人员分工完成,每个技术人员完成同一软件的不同模块 ...

  4. js间隔一段时间打印数据库中的值

    https://blog.csdn.net/qq_33599109/article/details/78417474

  5. webpack常用loader和plugin及打包速度优化

    优化 或 也可以用: 备用: 慎用的配置,用的不好会增加打包时间: 代码丑化插件:

  6. 使用pycharm以及用pycharm句子切分调试

    c在D盘建立了个文件夹,然后把segment1.py文件放进去. 然后双击segment1,就自动用pycharm打开了.然后就可以运行和debug.之前最开始的时候我segment1.py文件放桌面 ...

  7. 关于 extern "C"的说明

    在用C++的项目源码中,经常会不可避免的会看到下面的代码 #ifdef __cplusplus extern "C" { #endif /*...*/ #ifdef __cplus ...

  8. Linux命令第四篇

    作业四: 1)  新建目录/test/dir,属主为tom,数组为group1,/test目录的权限为777 # useradd tom [root@localhost /]# groupadd gr ...

  9. Node_初步了解(3)回调,作用域,上下文

    1. //回调:回调是异步编程最基本的方法,node.js需要按顺序执行异步逻辑的时候,一般采用后续传递的方式,将后续逻辑封装在回调函数中,作为起始函数的参数. //具名函数 function lea ...

  10. JSP解决:Attempt to clear a buffer that&#39;s already been flushed错误(jsp:forward标签跳转空白)

    [摘要:本日正在开辟过程当中发明一个题目:正在页里中应用了jsp:forward扔错Attempt to clear a buffer that's already been flushed!! 百思 ...