首页 游戏 软件 资讯 排行榜 专题
首页
科技数码
在 SpringBoot 项目中如何动态切换数据源、数据库?(可直接CV)

在 SpringBoot 项目中如何动态切换数据源、数据库?(可直接CV)

热心网友
96
转载
2025-12-15

如果服务器搭建的是一主多从多个mysql数据源,主服务器用来读。从服务器用来写。此时你在代码层面用注解指定了一个增删改方法到从数据源,但是碰巧此时从数据源失效了,那么就会自动的切换到其它服务器。

前言

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

本文参考若依源码,介绍了如何在SpringBoot项目中使用AOP和自定义注解实现MySQL主从数据库的动态切换,当从库故障时,能自动切换到主库,确保服务的高可用性。

实现效果:

如果服务器搭建的是一主多从多个mysql数据源,主服务器用来读。从服务器用来写。此时你在代码层面用注解指定了一个增删改方法到从数据源,但是碰巧此时从数据源失效了,那么就会自动的切换到其它服务器。

为什么要切换数据源,有哪些应用场景?

动态切换数据源通常是为了满足以下需求:

读写分离:在数据库架构中,为了提高性能和可用性,常常使用主从复制的方式。主数据库处理写操作,而从数据库处理读操作。动态切换数据源可以在不同的操作中使用不同的数据库,以达到优化性能的目的。多租户架构:在SaaS(Software as a Service)应用中,不同的租户可能需要操作不同的数据库。动态数据源允许系统根据租户的身份来切换到对应的数据源。分库分表:在处理大规模数据时,可能会采用分库分表的策略来分散数据存储的压力。动态切换数据源可以在执行跨库或跨表操作时,根据需要切换到正确的数据源。环境隔离:在开发、测试和生产环境中,可能需要连接到不同的数据库。动态数据源可以在不同环境之间无缝切换,以确保数据的隔离和安全性。灵活的数据库管理:在复杂的业务场景下,可能需要根据不同的业务逻辑来选择不同的数据源。动态数据源提供了这种灵活性,允许开发者根据运行时的条件来选择最合适的数据源。故障转移和高可用性:当主数据库不可用时,动态切换数据源可以自动或手动切换到备用数据库,以保证服务的连续性和数据的可用性。

如何切换数据源?

SpringBoot版本:3.0.4jdk版本:JDK17
1.pom文件

org.projectlomboklombokorg.springframework.bootspring-boot-starter-aopcom.alibabadruid-spring-boot-starter1.2.20com.mysqlmysql-connector-jcom.baomidoumybatis-plus-boot-starter3.5.3.1

2.配置文件:application.yml、application-druid.yml

application.yml配置文件:

#application.yml server: port: 8000spring: profiles: active: druid

application-druid.yml配置文件:

# 数据源配置spring:datasource: type:com.alibaba.druid.pool.DruidDataSource driverClassName:com.mysql.cj.jdbc.Driver druid: # 主库数据源 master: url:jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username:root password:123456 # 从库数据源 slave: # 从数据源开关/默认关闭 enabled:true url:jdbc:mysql://localhost:3306/t_lyj?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username:root password:123456 # 初始连接数 initialSize:5 # 最小连接池数量 minIdle:10 # 最大连接池数量 maxActive:20 # 配置获取连接等待超时的时间 maxWait:60000 # 配置连接超时时间 connectTimeout:30000 # 配置网络超时时间 socketTimeout:60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis:60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis:300000 # 配置一个连接在池中最大生存的时间,单位是毫秒 maxEvictableIdleTimeMillis:900000

3、数据源名称枚举DataSourceType

/** * 数据源 * * @author ruoyi */public enum DataSourceType{ /** * 主库 */ MASTER, /** * 从库 */ SLAVE}

4、Bean工具类SpringUtils

@Componentpublicfinalclass SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { /** Spring应用上下文环境 */ privatestatic ConfigurableListableBeanFactory beanFactory; privatestatic ApplicationContext applicationContext; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws BeansException * */ @SuppressWarnings("unchecked") publicstatic T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws BeansException * */ publicstatic T getBean(Class clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return beanFactory.containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws NoSuchBeanDefinitionException * */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); } /** * @param name * @return Class 注册对象的类型 * @throws NoSuchBeanDefinitionException * */ publicstatic Class getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name * @return * @throws NoSuchBeanDefinitionException * */ publicstatic String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); } /** * 获取aop代理对象 * * @param invoker * @return */ @SuppressWarnings("unchecked") publicstatic T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); } /** * 获取当前的环境配置,无配置返回null * * @return 当前的环境配置 */ publicstatic String[] getActiveProfiles() { return applicationContext.getEnvironment().getActiveProfiles(); } /** * 获取当前的环境配置,当有多个环境配置时,只获取第一个 * * @return 当前的环境配置 */ public static String getActiveProfile() { final String[] activeProfiles = getActiveProfiles(); return StringUtils.isNotEmpty(Arrays.toString(activeProfiles)) ? activeProfiles[0] : null; } /** * 获取配置文件中的值 * * @param key 配置文件的key * @return 当前的配置文件的值 * */ public static String getRequiredProperty(String key) { return applicationContext.getEnvironment().getRequiredProperty(key); }}

5、多数据源切换注解DataSource

/** * 自定义多数据源切换注解 * * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 * * @author lyj */@Target({ ElementType.METHOD, ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface DataSource{ /** * 切换数据源名称 */ public DataSourceType value() default DataSourceType.MASTER;}

6、数据源解析配置类DruidConfig

@Configurationpublicclass DruidConfig { @Bean @ConfigurationProperties("spring.datasource.druid.master") public DataSource masterDataSource(DruidProperties druidProperties){ DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean @ConfigurationProperties("spring.datasource.druid.slave") @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") public DataSource slaveDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dataSource(DataSource masterDataSource) { Map targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); returnnew DynamicDataSource(masterDataSource, targetDataSources); } /** * 设置数据源 * * @param targetDataSources 备选数据源集合 * @param sourceName 数据源名称 * @param beanName bean名称 */ public void setDataSource(Map targetDataSources, String sourceName, String beanName) { try { DataSource dataSource = SpringUtils.getBean(beanName); targetDataSources.put(sourceName, dataSource); } catch (Exception e) { } }}

7、数据源注入核心类DynamicDataSource

/** * 动态数据源 * * @author lyj */publicclass DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) { //设置默认数据源 super.setDefaultTargetDataSource(defaultTargetDataSource); //设置目标数据源的映射 super.setTargetDataSources(targetDataSources); //初始化 super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); }}

8、数据源切换处理类DynamicDataSourceContextHolder

/** * 数据源切换处理 * * @author lyj */publicclass DynamicDataSourceContextHolder{ publicstaticfinal Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 */ privatestaticfinal ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); /** * 设置数据源的变量 */ public static void setDataSourceType(String dsType) { log.info("切换到{}数据源", dsType); CONTEXT_HOLDER.set(dsType); } /** * 获得数据源的变量,默认使用主数据源 */ public static String getDataSourceType() { return CONTEXT_HOLDER.get() == null ? DataSourceType.MASTER.name() : CONTEXT_HOLDER.get(); } /** * 清空数据源变量 */ public static void clearDataSourceType() { CONTEXT_HOLDER.remove(); }}

9、Aop切面类

@Aspect@Order(1)@Componentpublicclass DataSourceAspect { @Pointcut("@annotation(com.LYJ.study.DynamicDataSource.annocation.DataSource)" + "|| @within(com.LYJ.study.DynamicDataSource.annocation.DataSource)") public void dsPointCut(){} @Around("dsPointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ DataSource dataSource = getDataSource(joinPoint); if (dataSource != null){ DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return joinPoint.proceed(); } finally { // 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); } } /** * 获取需要切换的数据源 */ public DataSource getDataSource(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); com.LYJ.study.DynamicDataSource.annocation.DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), com.LYJ.study.DynamicDataSource.annocation.DataSource.class); if (Objects.nonNull(dataSource)) { return dataSource; } return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); }}

10、在业务中使用

图片图片

@Service@RequiredArgsConstructor@DataSource(value=DataSourceType.MASTER)//@DataSource(value=DataSourceType.SLAVE)publicclass UserServiceImpl extends ServiceImpl implements UserService { privatefinal UserMapper userMapper; @Override @DataSource(value=DataSourceType.MASTER) //@DataSource(value=DataSourceType.SLAVE) public List queryAll() { return userMapper.selectList(null); }}

我们在service、mapper的类和方法上使用都可以。

补充:有很多从数据源怎么办?

我们上面已经配置了一个从数据源了,接下来我们继续配置多个从数据源

首先在application-druid.yml文件添加新的数据源

图片图片

在枚举添加数据源名称

//如果配置多数据源,继续添加即可public enum DataSourceType{ /** * 主库 */ MASTER, /** * 从库 */ SLAVE, /** * 从库2 */ SLAVE2}

图片图片

如何切换数据库?

我们就以Oracle为例

com.oracle ojdbc6 11.2.0.3

在application-druid.yml添加

slave3: # 从数据源开关/默认关闭 enabled: true url: jdbc:oracle:thin:@127.0.0.1:1521:oracle username: root password: password

然后删除指定的mysql驱动,默认会自动寻找驱动

图片

添加数据源和用法参考上面即可,都是一样的。

注意:在切换数据库时,因为mysql跟Oracle的sql语法有差别,启动时可能报错。

来源:https://www.51cto.com/article/823464.html
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

相关攻略

LangChain之外,为何还需关注Spring AI?
AI
LangChain之外,为何还需关注Spring AI?

如果面试官问你这个问题,你可以这样总结:LangChain功能繁多、反应迅速,是探索 AI 前沿的最佳工具,而 Spring AI 更像是一套工业级的生产线。对于企业而言,我们不仅需要调用大模型,更

热心网友
01.07
Spring Boot + sshd-sftp:SSH 命令与文件传输实践
科技数码
Spring Boot + sshd-sftp:SSH 命令与文件传输实践

在现代分布式系统中,服务器间的远程操作与文件传输是常见需求。SSH作为一种安全的网络协议,为远程登录和文件传输提供了可靠保障。 前言在现代分布式系统中,服务器间的远程操作与文件传输是常见需求。SSH

热心网友
12.15
Spring Boot + Pcap4j 实现网络流量抓包与实时分析
科技数码
Spring Boot + Pcap4j 实现网络流量抓包与实时分析

在当今数字化时代,网络流量如同信息社会的血液,承载着海量的数据交互。对网络流量进行有效的抓包与实时分析,是保障网络安全、优化网络性能的关键环节。无论是及时发现潜在的网络攻击,还是排查网络拥塞等问题,

热心网友
12.15
震惊!SpringBoot 接口耗时监控还能这么玩,简单到离谱!
科技数码
震惊!SpringBoot 接口耗时监控还能这么玩,简单到离谱!

随着业务规模的增长,我们还可以在此基础上拓展更多维度,比如统计 QPS、采集请求来源、结合用户信息等,形成完整的 接口性能监控体系。这不仅能帮助我们快速定位问题,更能为系统优化提供强有力的数据支撑。

热心网友
12.15
在 SpringBoot 项目中如何动态切换数据源、数据库?(可直接CV)
科技数码
在 SpringBoot 项目中如何动态切换数据源、数据库?(可直接CV)

如果服务器搭建的是一主多从多个mysql数据源,主服务器用来读。从服务器用来写。此时你在代码层面用注解指定了一个增删改方法到从数据源,但是碰巧此时从数据源失效了,那么就会自动的切换到其它服务器。 前

热心网友
12.15

最新APP

恶魔秘境
恶魔秘境
角色扮演 03-29
猫和老鼠华为
猫和老鼠华为
休闲益智 03-29
暗黑之地
暗黑之地
角色扮演 03-28
你比我猜
你比我猜
休闲益智 03-26
锦绣商铺
锦绣商铺
模拟经营 03-26

热门推荐

vivo X300 Ultra 摄影师套装开箱:重塑复古影像,专业拍摄体验
网络安全
vivo X300 Ultra 摄影师套装开箱:重塑复古影像,专业拍摄体验

三月的丽江,春风载着光影的诗意,vivo于此启幕春季新品发布会,年度影像旗舰vivo X300 Ultra将如约登场。我们有幸提前执掌这款被赋予“专业V单”称号的新机,今日便与你一同,沉浸式解锁这台

热心网友
03.30
悟空浏览器悬浮搜索用法详解,轻松启用高效搜索窗
电脑教程
悟空浏览器悬浮搜索用法详解,轻松启用高效搜索窗

悟空浏览器支持五种调出搜索悬浮窗的方式:一、地址栏长按;二、左边缘手势滑动;三、启用chrome: flags中的“Enable search bubble UI”;四、侧边栏添

热心网友
03.30
13年前就拥抱增程动力的宝马,如何看待增程式车型?
科技数码
13年前就拥抱增程动力的宝马,如何看待增程式车型?

快科技3月30日消息,日前,宝马产品负责人贝恩德·克尔伯在接受BMW Blog采访时明确表示,公司对增程车型持观望态度,虽技术上完全可行,但现实需求尚未明确,暂未做出量产决策。这一表态与宝马研发负责

热心网友
03.30
腾讯AI全景亮相中国网络媒体论坛郑州举行
科技数码
腾讯AI全景亮相中国网络媒体论坛郑州举行

大象新闻·大象财富记者 李莉 张迪驰3月29日,以“发挥主流媒体引领力 激发多元主体创造力——共创繁荣网络内容生态”为主题的2026中国网络媒体论坛在河南郑州开幕。腾讯携AI全景亮相,展示“从效率伙

热心网友
03.30
铂智第二款纯电上市,广汽丰田新能源转型稳步推进
科技数码
铂智第二款纯电上市,广汽丰田新能源转型稳步推进

3月29日,广汽丰田首款大型纯电轿车铂智7正式上市。新车推出600km和700km两种续航版本共5款配置,限时补贴权益后售价区间为14 78万元至19 98万元。作为广汽丰田继铂智3X之后的第二款纯

热心网友
03.30