Spring动态数据源实现读写分离详解

发布时间 - 2026-01-11 02:17:54    点击率:

一、创建基于ThreadLocal的动态数据源容器,保证数据源的线程安全性

package com.bounter.mybatis.extension;

/**
 * 基于ThreadLocal实现的动态数据源容器,保证DynamicDataSource的线程安全性
 * @author simon
 *
 */
public class DynamicDataSourceHolder {

 private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>();

 public static void setDataSource(String dataSourceKey) {
 dataSourceHolder.set(dataSourceKey);
 }

 public static String getDataSource() {
 return dataSourceHolder.get();
 }

 public static void clearDataSource() {
 dataSourceHolder.remove();
 }
}

二、定义Spring动态数据源扩展类,用来实现Master、Slave数据源动态切换

package com.bounter.mybatis.extension;

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

/**
 * 自定义的Spring 动态数据源扩展类,用来实现Master、Slave数据源动态切换
 * @author simon
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

 @Override
 protected Object determineCurrentLookupKey() {
 //使用DynamicDataSourceHolder保证线程安全
 return DynamicDataSourceHolder.getDataSource();
 }

}

三、配置Master、Slave数据源

1. db.properties配置Master、Slave数据信息

# Master DB
db.master.url=jdbc:mysql://192.168.168.110:3306/bounter?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=PRC&useSSL=false
db.master.username=bounter
# AES encrypt,Base64 encode
db.master.password=ZNhnEjauk3pecZxxS84ofA==

# Slave DB
db.slave.url=jdbc:mysql://192.168.168.111:3306/database?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=PRC&useSSL=false
db.slave.username=bounter
# AES encrypt,Base64 encode
db.slave.password=jFYmt2f57RHhzItYDhWiSA==
 

2. Spring 配置文件配置Master、Slave连接池,动态数据源

<!-- Master数据源 -->
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource"
 init-method="init" destroy-method="close">
 <!-- 基本属性 url、user、password -->
 <property name="url" value="${db.master.url}" />
 <property name="username" value="${db.master.username}" />
 <property name="password" value="${db.master.password}" />
 <!-- 配置初始化大小、最小、最大 -->
 <property name="initialSize" value="20" />
 <property name="minIdle" value="1" />
 <property name="maxActive" value="40" />
 <!-- 配置获取连接等待超时的时间 -->
 <property name="maxWait" value="60000" />
 <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
 <property name="timeBetweenEvictionRunsMillis" value="60000" />
 <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
 <property name="minEvictableIdleTimeMillis" value="300000" />
 <property name="validationQuery" value="SELECT 'x'" />
 <property name="testWhileIdle" value="true" />
 <property name="testOnBorrow" value="false" />
 <property name="testOnReturn" value="false" />
 <!-- 配置监控统计拦截的filters -->
 <property name="filters" value="stat" />
</bean>

<!-- Slave数据源 -->
<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource"
 init-method="init" destroy-method="close">
 <!-- 基本属性 url、user、password -->
 <property name="url" value="${db.slave.url}" />
 <property name="username" value="${db.slave.username}" />
 <property name="password" value="${db.slave.password}" />
 <!-- 配置初始化大小、最小、最大 -->
 <property name="initialSize" value="20" />
 <property name="minIdle" value="1" />
 <property name="maxActive" value="40" />
 <!-- 配置获取连接等待超时的时间 -->
 <property name="maxWait" value="60000" />
 <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
 <property name="timeBetweenEvictionRunsMillis" value="60000" />
 <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
 <property name="minEvictableIdleTimeMillis" value="300000" />
 <property name="validationQuery" value="SELECT 'x'" />
 <property name="testWhileIdle" value="true" />
 <property name="testOnBorrow" value="false" />
 <property name="testOnReturn" value="false" />
 <!-- 配置监控统计拦截的filters -->
 <property name="filters" value="stat" />
</bean>

<!-- 自定义动态数据源 -->
 <bean id="dataSource" class="com.bounter.mybatis.extension.DynamicDataSource">
 <property name="targetDataSources">
  <map key-type="java.lang.String">
  <!-- 配置读写数据源 -->
  <entry value-ref="masterDataSource" key="write"></entry>
  <entry value-ref="slaveDataSource" key="read"></entry>
  </map>
 </property>
 <property name="defaultTargetDataSource" ref="masterDataSource"></property>
 </bean>

四、创建数据源切面,通过AOP实现根据Dao层方法前缀动态选取读、写数据源

package com.bounter.mybatis.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import com.bounter.mybatis.extension.DynamicDataSourceHolder;

/**
 * 数据源切面,通过dao方法前缀决定访问读、写数据源
 * @author simon
 *
 */
@Component
@Aspect
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {

 //读库数据源key
 private static final String DATASOURCE_KEY_READ = "read";
 //查询方法清单
 String[] queryMethods = {"find","get","query","count","select"};

 /**
 * dao层方法执行前选择数据源
 * @param point
 */
 @Before("execution(* com.bounter.mybatis.dao..*.*(..))")
 public void before(JoinPoint point) {
 // 获取到当前执行的方法名
 String methodName = point.getSignature().getName();
 //匹配查询方法
 for(String queryMethod : queryMethods) {
  if(methodName.startsWith(queryMethod)) {
  //查询方法设置数据源为读库
  DynamicDataSourceHolder.setDataSource(DATASOURCE_KEY_READ);
  break;
  }
 }
 }

 /**
 * dao层方法执行完后清空数据源选择
 * @param point
 */
 @After("execution(* com.bounter.mybatis.dao..*.*(..))")
 public void after(JoinPoint point) {
 DynamicDataSourceHolder.clearDataSource();
 }
}


github源码地址:https://github.com/13babybear/bounter-mybatis

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# Spring  # 数据源  # 读写分离  # 详解Nginx服务器中HTTP Headers相关的模块配置使用  # Spring配置动态数据源实现读写分离的方法  # Mybatis注解实现多数据源读写分离详解  # resty更新header控制api版本数据源读写分离  # 自定义  # 池中  # 多久  # 完后  # 配置文件  # 大家多多  # 清空  # 连接池  # mysql  # url  # DB  # master  # useUnicode  # true  # Object  # determineCurrentLookupKey  # protected  # extends  # Override  # properties 


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


相关推荐: 焦点电影公司作品,电影焦点结局是什么?  深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?  如何在Tomcat中配置并部署网站项目?  Laravel项目结构怎么组织_大型Laravel应用的最佳目录结构实践  极客网站有哪些,DoNews、36氪、爱范儿、虎嗅、雷锋网、极客公园这些互联网媒体网站有什么差异?  利用vue写todolist单页应用  Laravel Livewire是什么_使用Laravel Livewire构建动态前端界面  奇安信“盘古石”团队突破 iOS 26.1 提权  如何在建站宝盒中设置产品搜索功能?  如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框  android nfc常用标签读取总结  百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧  如何实现建站之星域名转发设置?  详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)  浅谈Javascript中的Label语句  制作公司内部网站有哪些,内网如何建网站?  如何彻底删除建站之星生成的Banner?  Mybatis 中的insertOrUpdate操作  如何用y主机助手快速搭建网站?  利用 Google AI 进行 YouTube 视频 SEO 描述优化  如何快速查询域名建站关键信息?  教学论文网站制作软件有哪些,写论文用什么软件 ?  软银砸40亿美元收购DigitalBridge 强化AI资料中心布局  ChatGPT回答中断怎么办 引导AI继续输出完整内容的方法  原生JS实现图片轮播切换效果  如何使用 Go 正则表达式精准提取括号内首个纯字母标识符(忽略数字与嵌套)  Laravel如何配置中间件Middleware_Laravel自定义中间件拦截请求与权限校验【步骤】  Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】  制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?  EditPlus中的正则表达式实战(6)  动图在线制作网站有哪些,滑动动图图集怎么做?  laravel怎么通过契约(Contracts)编程_laravel契约(Contracts)编程方法  Edge浏览器提示“由你的组织管理”怎么解决_去除浏览器托管提示【修复】  北京企业网站设计制作公司,北京铁路集团官方网站?  javascript基本数据类型及类型检测常用方法小结  Laravel怎么生成URL_Laravel路由命名与URL生成函数详解  Laravel如何处理和验证JSON类型的数据库字段  成都网站制作公司哪家好,四川省职工服务网是做什么用?  Laravel如何实现多级无限分类_Laravel递归模型关联与树状数据输出【方法】  长沙企业网站制作哪家好,长沙水业集团官方网站?  Laravel如何安装Breeze扩展包_Laravel用户注册登录功能快速实现【流程】  Laravel怎么上传文件_Laravel图片上传及存储配置  Laravel的契約(Contracts)是什么_深入理解Laravel Contracts与依赖倒置  佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】  mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?  linux top下的 minerd 木马清除方法  Laravel distinct去重查询_Laravel Eloquent去重方法  Swift中switch语句区间和元组模式匹配  Laravel Sail是什么_基于Docker的Laravel本地开发环境Sail入门  如何快速查询网站的真实建站时间?