Spring Boot 动态数据源切换

devtools/2024/11/28 4:30:37/

背景

随着互联网应用的快速发展,多数据源的需求日益增多。Spring Boot 以其简洁的配置和强大的功能,成为实现动态数据源切换的理想选择。本文将通过具体的配置和代码示例,详细介绍如何在 Spring Boot 应用中实现动态数据源切换,帮助开发者高效应对不同业务场景下的数据管理需求。无论是读写分离还是数据隔离,都能轻松搞定。

AOP动态代理

AOP注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {String name();
}

AOP切面类

@Aspect
@Component
public class DataSourceAspect {@Pointcut("@annotation(com.example.aliyunai.db.TargetDataSource)")public void dataSourcePointcut() {}@Before("dataSourcePointcut()")public void changeDataSource(JoinPoint point) {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);if (targetDataSource != null) {String dataSourceName = targetDataSource.name();DataSourceContextHolder.setDataSourceKey(dataSourceName);}}@After("dataSourcePointcut()")public void clearDataSource(JoinPoint point) {DataSourceContextHolder.clearDataSourceKey();}
}

数据源配置

@Configuration
public class DataSourceConfig {@Bean(name = "master")public DataSource primaryDataSource() {return DataSourceBuilder.create().type(HikariDataSource.class).driverClassName("").url("").username("").password("").build();}@Bean(name = "slave")public DataSource secondaryDataSource() {return DataSourceBuilder.create().type(HikariDataSource.class).driverClassName("").url("").username("").password("").build();}@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource dynamicDataSource(@Qualifier("master") DataSource master,@Qualifier("slave") DataSource slave) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("master", master);targetDataSources.put("slave", slave);DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(master);return dynamicDataSource;}}

线程上下文


public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSourceKey(String key) {contextHolder.set(key);}public static String getDataSourceKey() {return contextHolder.get();}public static void clearDataSourceKey() {contextHolder.remove();}
}

动态数据源设置

public class DynamicDataSource extends AbstractRoutingDataSource {private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);@Overrideprotected Object determineCurrentLookupKey() {String dataSourceKey = DataSourceContextHolder.getDataSourceKey();logger.info("Determining current data source: {}", dataSourceKey);return dataSourceKey;}
}

service类

@Service
public class UserService {@Resourceprivate UserMapper userMapper;@TargetDataSource(name = "master")public User queryFromPrimary() {User user = userMapper.selectById(1);return user;}@TargetDataSource(name = "slave")public User queryFromSecondary() {User user = userMapper.selectById(1L);return user;}
}

Mybatis 拦截器


@Component
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
public class DynamicDataSourceInterceptor implements Interceptor {private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);// 假设这里有一个获取当前数据源标识的方法,你需要根据实际项目中的实现来替换private String getCurrentDataSourceKey() {// 示例:从某个上下文持有者中获取数据源标识,这里只是示意,实际要替换return DataSourceContextHolder.getDataSourceKey();}@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];BoundSql boundSql = mappedStatement.getBoundSql(parameter);String sql = boundSql.getSql();//对单个表进行处理logger.info("sql:"+sql);String mappedStatementId = mappedStatement.getId();//对所有查询进行处理if (mappedStatementId.contains("query")) {String table = getTable(sql);if (table.equals("user")){DataSourceContextHolder.setDataSourceKey(getCurrentDataSourceKey());}//增删改}else if (mappedStatementId.contains("update")) {DataSourceContextHolder.setDataSourceKey(getCurrentDataSourceKey());}return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}private static String getTable(String sql) {// 定义正则表达式模式,用于匹配 "from" 和 "where" 之间的表名Pattern pattern = Pattern.compile("FROM\\s+(\\w+)\\s+WHERE");Matcher matcher = pattern.matcher(sql);if (matcher.find()) {return matcher.group(1);}return null;}
}


http://www.ppmy.cn/devtools/137567.html

相关文章

MATLAB中Simulink的基础知识

Simulink是MATLAB中的一种可视化仿真工具&#xff0c; 是一种基于MATLAB的框图设计环境&#xff0c;是实现动态系统建模、仿真和分析的一个软件包&#xff0c;被广泛应用于线性系统、非线性系统、数字控制及数字信号处理的建模和仿真中。 Simulink提供一个动态系统建模、仿真和…

《免费的学习网站推荐3》

《免费的学习网站推荐3》 综合教育类 国家教育资源公共服务平台&#xff1a;教育部主导的平台&#xff0c;汇聚了大量中小学教育资源&#xff0c;包括课件、教案、试题等&#xff0c;为教师教学和学生学习提供了丰富的素材.爱课程网&#xff1a;由高教社的“爱课程网”与网易…

Redis的几种持久化方式

Redis 提供了两种主要的持久化方式&#xff0c;它们分别是&#xff1a; 1. RDB&#xff08;Redis Database Snapshotting&#xff09; RDB 是 Redis 的一种数据持久化方式&#xff0c;它会在指定的时间间隔内对 Redis 中的数据进行快照并保存到硬盘上。 特点&#xff1a; 触…

设计模式之 备忘录模式

备忘录模式是一种行为型设计模式&#xff0c;它允许你在不暴露对象实现细节的情况下&#xff0c;捕获和保存对象的内部状态。之后&#xff0c;可以通过保存的状态将对象恢复到原先的状态。备忘录模式的核心思想是“在不暴露对象的内部实现的情况下&#xff0c;保存对象的状态&a…

前端自动化运营:提升效率与体验的实践指南

在现代的前端开发与运营中&#xff0c;业务需求的快速变化与用户体验的高标准要求正变得越来越普遍。如何在有限的时间内快速迭代、精准上线、实时响应用户反馈&#xff1f;答案是&#xff1a;前端自动化运营。 本文将从概念出发&#xff0c;结合实际场景&#xff0c;深入探讨前…

OpenOCD之J-Link下载

NOTE&#xff1a;此篇文章由笔者的 VSCode编辑GCC for ARM交叉编译工具链Makefile构建OpenOCD调试&#xff08;基于STM32的标准库&#xff09;派生而来。 1.下载USB Dirver Tool.exe&#xff0c;选择J-Link dirver&#xff0c;替换成WinUSB驱动。&#xff08;⭐USB Dirver Tool…

设计模式-创建型-单例模式

1.概念 该设计模式保证全局只有一个实例对象可以使用&#xff0c;并且自动实例化&#xff0c;向外部提供一个使用接口。 2.作用 保证某些对象在项目中只有一份。 3.应用场景 比如&#xff1a; 全局的计数器——web页面文章阅读计数 全局的资源共享——用户登录后各个页面之…

设计模式学习[9]---模板方法模式

文章目录 前言1.原理阐述1.1 C模板说明1.2 设计模式模板 2.举例3.和原型模式的区别 总结 前言 大型的C项目&#xff0c;都会用到很多模板&#xff0c;C中关于模板的书也不少&#xff0c;那么在设计模式中的模板模式和C的模板又有什么区别呢&#xff1f;模板和上篇的原型又有哪…