原文地址:Spring AOP 实现读写分离

博客地址:http://www.extlight.com

一、前言

上一篇《MySQL 实现主从复制》 文章中介绍了 MySQL 主从复制的搭建,为了在项目上契合数据库的主从架构,本篇将介绍在应用层实现对数据库的读写分离。

二、原理

配置主从数据源,当接收请求时,执行具体方法之前(拦截),判断请求具体操作(读或写),最终确定从哪个数据源获取连接访问数据库。

在 JavaWeb 开发中,有 3 种方式可以对请求进行拦截:

filter:拦截所有请求
intercetor:拦截 handler/Action
aop 切面:依赖切入点

不难看出,使用 AOP 切面进行拦截最合理和灵活,因此本文将介绍使用 AOP 实现读写分离功能。

三、编码

本文只张贴关键性代码,详细代码请下载文章末尾源码进行查看。

3.1 代码

1)DynamicDataSourceHolder 确保线程安全:

/**
*
* 使用ThreadLocal技术来记录当前线程中的数据源的key
*
*/
public class DynamicDataSourceHolder { //写库对应的数据源key
private static final String MASTER = "master"; //读库对应的数据源key
private static final String SLAVE = "slave"; //使用ThreadLocal记录当前线程的数据源key
private static final ThreadLocal<String> holder = new ThreadLocal<String>(); /**
* 设置数据源key
* @param key
*/
public static void putDataSourceKey(String key) {
holder.set(key);
} /**
* 获取数据源key
* @return
*/
public static String getDataSourceKey() {
return holder.get();
} /**
* 标记写库
*/
public static void markMaster(){
putDataSourceKey(MASTER);
} /**
* 标记读库
*/
public static void markSlave(){
putDataSourceKey(SLAVE);
} }

2)定义 AOP 切面判断当前线程的读写操作

/**
* 定义数据源的AOP切面,通过该Service的方法名判断是应该走读库还是写库
*
*/
public class DataSourceAspect { /**
* 在进入Service方法之前执行
*
* @param point 切面对象
*/
public void before(JoinPoint point) {
// 获取到当前执行的方法名
String methodName = point.getSignature().getName();
if (isSlave(methodName)) {
// 标记为读库
DynamicDataSourceHolder.markSlave();
} else {
// 标记为写库
DynamicDataSourceHolder.markMaster();
}
} /**
* 判断是否为读库
*
* @param methodName
* @return
*/
private Boolean isSlave(String methodName) {
// 方法名以query、find、get开头的方法名走从库
return StringUtils.startsWithAny(methodName, "query", "find", "get");
} }

3)定义动态数据源,确定最终使用的数据源:

/**
* 定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可
*
* 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource{ @Override
protected Object determineCurrentLookupKey() {
// 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
String dataSourceKey = DynamicDataSourceHolder.getDataSourceKey();
System.out.println("dataSourceKey ======> "+dataSourceKey);
return dataSourceKey;
} }

3.2 配置文件

1)jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver

jdbc.master.url=jdbc:mysql://192.168.2.21/mysql_test?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=UTC
jdbc.master.username=root
jdbc.master.password=tiger jdbc.slave01.url=jdbc:mysql://192.168.2.22/mysql_test?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=UTC
jdbc.slave01.username=root
jdbc.slave01.password=tiger

2)applicationContext.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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <context:component-scan base-package="com.light.*">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan> <context:property-placeholder location="classpath:*.properties"/> <!-- 数据源 -->
<bean id="dataSource" class="com.light.dynamicdatasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="masterDataSource"></entry>
<entry key="slave" value-ref="slave01DataSource"></entry>
</map>
</property>
<!-- 默认数据源 -->
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean> <!-- 主库数据源 -->
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${jdbc.master.url}"/>
<property name="username" value="${jdbc.master.username}"/>
<property name="password" value="${jdbc.master.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="initialSize" value="5"/>
<property name="minIdle" value="5"/>
<property name="maxActive" value="50"/>
</bean> <!-- 从库数据源 -->
<bean id="slave01DataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${jdbc.slave01.url}"/>
<property name="username" value="${jdbc.slave01.username}"/>
<property name="password" value="${jdbc.slave01.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="initialSize" value="5"/>
<property name="minIdle" value="5"/>
<property name="maxActive" value="50"/>
</bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- 引入 mybatis 配置文件 -->
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml"></property>
<property name="typeAliasesPackage" value="com.light.domain"></property>
<!-- sql配置文件 -->
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
</bean> <!-- 扫描Mapper -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.light.mapper"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean> <!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean> <!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice> <!-- 切面 -->
<bean id="dataSourceAspect" class="com.light.dynamicdatasource.DataSourceAspect"></bean> <aop:config proxy-target-class="true">
<aop:pointcut id="myPointcut" expression="execution(* com.light.service.*.*(..))" />
<!-- 事务切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
<!-- 自定义切面 -->
<aop:aspect ref="dataSourceAspect" order="-9999">
<aop:before method="before" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>

四、测试

笔者在项目的 web 层写了 UserController 类,里边包含 get 和 delete 两个方法。

正常情况,当访问 get 方法(读操作)时,使用从库数据源,那么控制台应该打印 slave 。

正常情况,当访问 delete 方法(写操作)时,使用主库数据源,那么控制台应该打印 master 。

以下是 2 次测试结果:

get 方法:

delete 方法:

五、源码

源码下载

Spring AOP 实现读写分离的更多相关文章

  1. 使用Spring AOP实现读写分离(MySql实现主从复制)

    1.  背景 我们一般应用对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较大,有一个思路就是说采用数据库集群的方案,其中一个是主库,负责写入数据,我们称之为:写库: 其它都是从库,负责读 ...

  2. spring+mybatis实现读写分离

    springmore-core spring+ibatis实现读写分离 特点 无缝结合spring+ibatis,对于程序员来说,是透明的 除了修改配置信息之外,程序的代码不需要修改任何东西 支持sp ...

  3. 使用Spring实现MySQL读写分离

    1. 为什么要进行读写分离 大量的JavaWeb应用做的是IO密集型任务, 数据库的压力较大, 需要分流 大量的应用场景, 是读多写少, 数据库读取的压力更大 一个很自然的思路是使用一主多从的数据库集 ...

  4. 使用Spring实现MySQL读写分离(转)

    使用Spring实现MySQL读写分离 为什么要进行读写分离 大量的JavaWeb应用做的是IO密集型任务, 数据库的压力较大, 需要分流 大量的应用场景, 是读多写少, 数据库读取的压力更大 一个很 ...

  5. 基于spring的aop实现读写分离与事务配置

    项目开发中经常会遇到读写分离等多数据源配置的需求,在Java项目中可以通过Spring AOP来实现多数据源的切换. 一.Spring事务开启流程 Spring中通常通过@Transactional来 ...

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

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

  7. Spring 实现数据库读写分离

    随着互联网的大型网站系统访问量的增高,数据库访问压力方面不断的显现而出,所以许多公司在数据库层面采用读写分离技术,也就是一个master,多个slave.master负责数据的实时更新或实时查询,而s ...

  8. spring实现数据库读写分离

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

  9. 使用Spring+MySql实现读写分离(二)spring整合多数据库

    紧接着上一章,因为现在做的项目还是以spring为主要的容器管理框架,所以写以下spring如何整合多个数据源. 1. 背景 我们一般应用对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较 ...

随机推荐

  1. Ubuntu下常用强化学习实验环境搭建(MuJoCo, OpenAI Gym, rllab, DeepMind Lab, TORCS, PySC2)

    http://lib.csdn.net/article/aimachinelearning/68113 原文地址:http://blog.csdn.net/jinzhuojun/article/det ...

  2. Vue学习笔记之vue-cli脚手架项目中组件的使用

    在webpack-simple模板中,包括webpck模板.一个.vue文件就是一个组件. 为什么会这样呢?因为webpack干活了!webpack的将我们所有的资源文件进行打包.同时webpack还 ...

  3. Django使用本地css/js文件

    Django使用本地css/js文件 在manager.py同层级下创建static文件夹, 里面放上css , js, images等文件或者文件夹 我的文件夹层级 然后只需在settings.py ...

  4. 20145301赵嘉鑫《网络对抗》逆向及Bof基础

      20145301赵嘉鑫<网络对抗>逆向及Bof基础 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回 ...

  5. 初识PHP(一)基础语法

    一直准备学习PHP,结果前一段时间总是有事情,耽误了一阵子.现在赶快迎头赶上! 这个系列只是谈谈我对于PHP的一些看法,不是教程性质的.另外我是小白,只是写写随笔,大神求轻拍.本人学习过c .java ...

  6. HashMap 遍历的两种方式及性能比较

    HashMap 是Java开发中经常使用的数据结构.相信HashMap 的基本用法你已经很熟悉了.那么我们该如何遍历HashMap 呢?哪种遍历方式的性能更好呢?本篇文章来为你解决这个疑惑. 一.Ha ...

  7. Using SQLXML Bulk Load in the .NET Environment

    http://msdn.microsoft.com/en-us/library/ms171878.aspx 1.首先创建一张表 CREATE TABLE Ord ( OrderID ,) PRIMAR ...

  8. BZOJ 1853: [Scoi2010]幸运数字(容斥原理)

    http://www.lydsy.com/JudgeOnline/problem.php?id=1853 题意: 在中国,很多人都把6和8视为是幸运数字!lxhgww也这样认为,于是他定义自己的“幸运 ...

  9. Rails 5 Test Prescriptions 第9章 Testing-JavaScript: Integration Testing,❌挂一个问题webpacker::helper

    使用Capybara进行JS的集成测试 谈论驱动 让测试通过 Webpack in Development Mode Js设计 是用户在网页上有好的体验的重要因素. 尽管如此,许多网页不测试JS. 部 ...

  10. Oracle Sourcing Implementation and Administration Guide(转)

    原文地址 Oracle Sourcing Implementation and Administration Guide