Spring AOP 实现读写分离
原文地址: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 实现读写分离的更多相关文章
- 使用Spring AOP实现读写分离(MySql实现主从复制)
1. 背景 我们一般应用对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较大,有一个思路就是说采用数据库集群的方案,其中一个是主库,负责写入数据,我们称之为:写库: 其它都是从库,负责读 ...
- spring+mybatis实现读写分离
springmore-core spring+ibatis实现读写分离 特点 无缝结合spring+ibatis,对于程序员来说,是透明的 除了修改配置信息之外,程序的代码不需要修改任何东西 支持sp ...
- 使用Spring实现MySQL读写分离
1. 为什么要进行读写分离 大量的JavaWeb应用做的是IO密集型任务, 数据库的压力较大, 需要分流 大量的应用场景, 是读多写少, 数据库读取的压力更大 一个很自然的思路是使用一主多从的数据库集 ...
- 使用Spring实现MySQL读写分离(转)
使用Spring实现MySQL读写分离 为什么要进行读写分离 大量的JavaWeb应用做的是IO密集型任务, 数据库的压力较大, 需要分流 大量的应用场景, 是读多写少, 数据库读取的压力更大 一个很 ...
- 基于spring的aop实现读写分离与事务配置
项目开发中经常会遇到读写分离等多数据源配置的需求,在Java项目中可以通过Spring AOP来实现多数据源的切换. 一.Spring事务开启流程 Spring中通常通过@Transactional来 ...
- Spring+mybatis 实现aop数据库读写分离,多数据库源配置
在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库.Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询.因为在实际的应用中,数据库都是读多写少 ...
- Spring 实现数据库读写分离
随着互联网的大型网站系统访问量的增高,数据库访问压力方面不断的显现而出,所以许多公司在数据库层面采用读写分离技术,也就是一个master,多个slave.master负责数据的实时更新或实时查询,而s ...
- spring实现数据库读写分离
现在大型的电子商务系统,在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库.Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询.因为在实际的应 ...
- 使用Spring+MySql实现读写分离(二)spring整合多数据库
紧接着上一章,因为现在做的项目还是以spring为主要的容器管理框架,所以写以下spring如何整合多个数据源. 1. 背景 我们一般应用对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较 ...
随机推荐
- 反射_IsDefined判断方法上有自定义的标签
在.NET 4.0(当然也包括4.0以前的版本)下,用反射判断某个方法是否运用了自定义Attribute时,可以通过调用MethodInfo的IsDefined()方法进行确认.当然,IsDefine ...
- redis未授权访问漏洞总结
Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. 漏洞介绍: Redis 默认情况下,会绑定在 0.0.0.0 ...
- 参考sectools,每个人至少查找5种安全工具、库等信息并深入研究至少两种并写出使用教程
1.Nessus Nessus是免费网络漏洞扫描器,它可以运行于几乎所有的UNIX平台之上.它不仅能永久升级,还免费提供多达11000种插件(但需要注册并接受EULA-acceptance--终端用户 ...
- System.DateTimeKind 的用法
最近在使用SQLite的数据库,发现SQLiteConnection类,有一个属性DateTimeKind 去msdn上找了下资料,http://msdn.microsoft.com/en-us/li ...
- Android 引用资源
比如在 strings.xml 中找到的 Hello world!字符串,我们有两种方式可以引用它: 1. 在代码中通过 R.string.hello_world 可以获得该字符串的引用: 2. 在 ...
- 【转】Scala基础知识
原文地址.续 课程内容: 关于这节课 表达式 值 函数 类 继承 特质 类型 apply方法 单例对象 函数即对象 包 模式匹配 样本类 try-catch-finally 关于这节课 最初的几个星期 ...
- webpack3.0之loader配置及编写(一)
loader 用于对模块的源代码进行转换.loader 可以使你在 import 或"加载"模块时预处理文件.loader 可以将文件从不同的语言(如 TypeScript)转换为 ...
- kue
尝试着看英文的文档,你发现他其实并不难. https://www.npmjs.com/package/kue
- uva10480最小割集
求最小割集 dinic处理后用dfs对所有点进行标记,遍历整个联接边,起点访问了,终点没访问或者起点没访问,终点访问了就是最小割集之一 #include<map> #include< ...
- POJ 2409 Let it Bead (Polya定理)
题意 用k种颜色对n个珠子构成的环上色,旋转翻转后相同的只算一种,求不等价的着色方案数. 思路 Polya定理 X是对象集合{1, 2, --, n}, 设G是X上的置换群,用M种颜色染N种对象,则不 ...