Spring AOP切面解决数据库读写分离实例详解
发布时间 - 2026-01-11 01:26:39 点击率:次Spring AOP切面解决数据库读写分离实例详解

为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如何做到把数据写到master库,而读取数据的时候,从slave库读取。如果应用程序判断失误,把数据写入到slave库,会给系统造成致命的打击。
解决读写分离的方案很多,常用的有SQL解析、动态设置数据源。SQL解析主要是通过分析sql语句是insert/select/update/delete中的哪一种,从而对应选择主从。而动态设置数据源,则是通过拦截方法名称的方式来决定主从的,例如:save*(),insert*() 形式的方法使用master库,select()开头的,使用slave库。蛮多公司会使用在方法上标上自定义的@Master、@Slave之类的标签来选择主从,也有公司直接就调用setxxMaster,setxxSlave之类的代码进行主从选择。
下面我主要介绍一下基于Spring AOP动态设置数据源这种方式。注意这篇文章是基于自己项目的实际情况的,不是通用的方案,请知晓。
原理图
Spring AOP的切面主要的职责是拦截Mybatis的Mapper接口,通过判断Mapper接口中的方法名称来决定主从。
Spring AOP 切面配置
<aop:config expose-proxy="true">
<aop:pointcut id="txPointcut" expression="execution(* com.test..persistence..*.*(..))" />
<aop:aspect ref="readWriteInterceptor" order="1">
<aop:around pointcut-ref="txPointcut" method="readOrWriteDB"/>
</aop:aspect>
</aop:config>
<bean id="readWriteInterceptor" class="com.test.ReadWriteInterceptor">
<property name="readMethodList">
<list>
<value>query*</value>
<value>use*</value>
<value>get*</value>
<value>count*</value>
<value>find*</value>
<value>list*</value>
<value>search*</value>
</list>
</property>
<property name="writeMethodList">
<list>
<value>save*</value>
<value>add*</value>
<value>create*</value>
<value>insert*</value>
<value>update*</value>
<value>merge*</value>
<value>del*</value>
<value>remove*</value>
<value>put*</value>
<value>write*</value>
</list>
</property>
</bean>
把所有Mybatis接口类都放置在persistence下。配置的切面类是ReadWriteInterceptor。这样当Mapper接口的方法被调用时,会先调用这个切面类的readOrWriteDB方法。在这里需要注意<aop:aspect>中的order="1" 配置,主要是为了解决切面于切面之间的优先级问题,因为整个系统中不太可能只有一个切面类。
Spring AOP 切面类实现
public class ReadWriteInterceptor {
private static final String DB_SERVICE = "dbService";
private List<String> readMethodList = new ArrayList<String>();
private List<String> writeMethodList = new ArrayList<String>();
public Object readOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
if (isChooseReadDB(methodName)) {
//选择slave数据源
} else if (isChooseWriteDB(methodName)) {
//选择master数据源
} else {
//选择master数据源
}
return pjp.proceed();
}
private boolean isChooseWriteDB(String methodName) {
for (String mappedName : this.writeMethodList) {
if (isMatch(methodName, mappedName)) {
return true;
}
}
return false;
}
private boolean isChooseReadDB(String methodName) {
for (String mappedName : this.readMethodList) {
if (isMatch(methodName, mappedName)) {
return true;
}
}
return false;
}
private boolean isMatch(String methodName, String mappedName) {
return PatternMatchUtils.simpleMatch(mappedName, methodName);
}
public List<String> getReadMethodList() {
return readMethodList;
}
public void setReadMethodList(List<String> readMethodList) {
this.readMethodList = readMethodList;
}
public List<String> getWriteMethodList() {
return writeMethodList;
}
public void setWriteMethodList(List<String> writeMethodList) {
this.writeMethodList = writeMethodList;
}
覆盖DynamicDataSource类中的getConnection方法
ReadWriteInterceptor中的readOrWriteDB方法只是决定选择主还是从,我们还必须覆盖数据源的getConnection方法,以便获取正确的connection。一般来说,是一主多从,即一个master库,多个slave库的,所以还得解决多个slave库之间负载均衡、故障转移以及失败重连接等问题。
1、负载均衡问题,slave不多,系统并发读不高的话,直接使用随机数访问也是可以的。就是根据slave的台数,然后产生随机数,随机的访问slave。
2、故障转移,如果发现connection获取不到了,则把它从slave列表中移除,等其回复后,再加入到slave列表中
3、失败重连,第一次连接失败后,可以多尝试几次,如尝试10次。
处理业务方法中的@Transactional注解
我参与的这个项目,大部分业务代码是不需要事务的,只有极个别情况需要。那么按照上面提到的方案,如果不对业务方法中@Transactional注解进行特殊处理的话,主从的选择会出现问题。大家都知道,如果使用了Spring的事务,那么在同一个业务方法内,只会调用一次数据源的getConnection方法,如果该业务方法内,调用的mapper接口刚好以select开头的,就会选择slave库,那么接下来调用以insert开头的mapper接口方法时,会把数据写入到slave库。如何解决这个问题呢?必须在进入标有@Transactional注解的业务方法前,指定选择master主库。可以通过覆盖DataSourceTransactionManager类中的doBegin方法,如下:
public class MyTransactionManager extendsDataSourceTransactionManager{
@Override
protected void doBegin(Object transaction, TransactionDefinitiondefinition) {
//选择master数据库
super.doBegin(transaction, definition);
}
}
这样既可以避免,把数据写入到从库的问题。
总结
本人的解决方案是基于项目实际的,不一定合适你,我只是展示了解决方案而已。当然你可以选择开源的框架,像阿里的Cobar,360的Atlas。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
# Spring
# AOP切面解决数据库读写分离
# AOP切面
# Spring AOP使用接口方式实现
# Springboot接口项目如何使用AOP记录日志
# Spring AOP实现Redis缓存数据库查询源码
# Spring+Mybatis 实现aop数据库读写分离与多数据库源配置操作
# 使用Spring AOP实现MySQL数据库读写分离案例分析(附demo)
# Spring AOP实现接口请求记录到数据库的示例代码
# 随机数
# 多个
# 应用程序
# 标上
# 会给
# 类中
# 负载均衡
# 就会
# 在这里
# 也有
# 你可以
# 大家都
# 不太
# 不需要
# 是基于
# 则是
# 列表中
# 不多
# 几次
# 是从
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
如何在云主机上快速搭建网站?
Laravel队列任务超时怎么办_Laravel Queue Timeout设置详解
phpredis提高消息队列的实时性方法(推荐)
在Oracle关闭情况下如何修改spfile的参数
实例解析Array和String方法
用v-html解决Vue.js渲染中html标签不被解析的问题
潮流网站制作头像软件下载,适合母子的网名有哪些?
Laravel怎么配置S3云存储驱动_Laravel集成阿里云OSS或AWS S3存储桶【教程】
东莞专业网站制作公司有哪些,东莞招聘网站哪个好?
Internet Explorer官网直接进入 IE浏览器在线体验版网址
宙斯浏览器文件分类查看教程 快速筛选视频文档与图片方法
Gemini怎么用新功能实时问答_Gemini实时问答使用【步骤】
北京网站制作的公司有哪些,北京白云观官方网站?
电商网站制作价格怎么算,网上拍卖流程以及规则?
Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】
如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?
移动端手机网站制作软件,掌上时代,移动端网站的谷歌SEO该如何做?
EditPlus中的正则表达式实战(6)
javascript读取文本节点方法小结
如何用wdcp快速搭建高效网站?
Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】
Laravel怎么定时执行任务_Laravel任务调度器Schedule配置与Cron设置【教程】
电商网站制作多少钱一个,电子商务公司的网站制作费用计入什么科目?
Laravel辅助函数有哪些_Laravel Helpers常用助手函数大全
如何用搬瓦工VPS快速搭建个人网站?
怎么制作网站设计模板图片,有电商商品详情页面的免费模板素材网站推荐吗?
简历在线制作网站免费版,如何创建个人简历?
Laravel如何使用软删除(Soft Deletes)功能_Eloquent软删除与数据恢复方法
Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道
Laravel如何理解并使用服务容器(Service Container)_Laravel依赖注入与容器绑定说明
悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤
如何在宝塔面板中修改默认建站目录?
如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环
Laravel如何使用Telescope进行调试?(安装和使用教程)
为什么要用作用域操作符_php中访问类常量与静态属性的优势【解答】
Laravel如何处理异常和错误?(Handler示例)
Laravel如何使用withoutEvents方法临时禁用模型事件
Android实现代码画虚线边框背景效果
北京的网站制作公司有哪些,哪个视频网站最好?
Laravel如何安装使用Debugbar工具栏_Laravel性能调试与SQL监控插件【步骤】
简单实现jsp分页
电视网站制作tvbox接口,云海电视怎样自定义添加电视源?
JS弹性运动实现方法分析
Claude怎样写约束型提示词_Claude约束提示词写法【教程】
最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?
网站制作价目表怎么做,珍爱网婚介费用多少?
JavaScript实现Fly Bird小游戏
Win11怎么设置默认图片查看器_Windows11照片应用关联设置
javascript中对象的定义、使用以及对象和原型链操作小结
今日头条微视频如何找选题 今日头条微视频找选题技巧【指南】

