Spring boot多数据源实现动态切换

SpringBoot实现多数据源切换 随着项目规模的扩大和业务需求的复杂化,单一数据源已经不能满足实际开发中的需求。在许多情况下,我们需要同时操作多个数据库,或者需要将不同类型的数据存储在不同的数据库中。这时,多数据源场景成为必不可少的解决方案。市面上常见的多数据源实现方案如下:方案1:基于Spring框架提供的AbstractRoutingDataSourc。优点: 简单易用,支持动态切换数据源;适用于少量数据源情况。场景:适用于需要动态切换数据源,且数据库较少的情况。 阅读详情

概述

日常的业务开发项目中只会配置一套数据源,如果需要获取其他系统的数据往往是通过调用接口, 或者是通过第三方工具比如kettle将数据同步到自己的数据库中进行访问。

但是也会有需要在项目中引用多数据源的场景。比如如下场景:

  • 自研数据迁移系统,至少需要新、老两套数据源,从老库读取数据写入新库
  • 自研读写分离中间件,系统流量增加,单库响应效率降低,引入读写分离方案,写入数据是一个数据源,读取数据是另一个数据源

环境说明

  • spring boot
  • mysql
  • mybatis-plus
  • spring-aop

项目目录结构

  • controller: 存放接口类
  • service: 存放服务类
  • mapper: 存放操作数据库的mapper接口
  • entity: 存放数据库表实体类
  • vo: 存放返回给前端的视图类

  • context: 存放持有当前线程数据源key类
  • constants: 存放定义数据源key常量类
  • config: 存放数据源配置类
  • annotation: 存放动态数据源注解
  • aop: 存放动态数据源注解切面类
  • resources.config: 项目配置文件
  • resources.mapper: 数据库xml文件

关键类说明

忽略掉controller/service/entity/mapper/xml介绍。

  • jdbc.properties: 数据源配置文件。虽然可以配置到Spring boot的默认配置文件application.properties/application.yml文件当中,但是如果数据源比较多的话,根据实际使用,最佳的配置方式还是独立配置比较好。
  • DynamicDataSourceConfig: 数据源配置类
  • DynamicDataSource: 动态数据源配置类
  • DataSourceRouting: 动态数据源注解
  • DynamicDataSourceAspect: 动态数据源设置切面
  • DynamicDataSourceContextHolder: 当前线程持有的数据源key
  • DataSourceConstants: 数据源key常量类

开发流程

动态数据源流程

Spring Boot 的动态数据源,本质上是把多个数据源存储在一个 Map 中,当需要使用某个数据源时,从 Map 中获取此数据源进行处理。

在 Spring 中已提供了抽象类 AbstractRoutingDataSource 来实现此功能,继承AbstractRoutingDataSource类并覆写其determineCurrentLookupKey()方法即可,该方法只需要返回数据源key即可,也就是存放数据源的Map的key。

因此,我们在实现动态数据源的,只需要继承它,实现自己的获取数据源逻辑即可。AbstractRoutingDataSource顶级继承了DataSource,所以它也是可以做为数据源对象,因此项目中使用它作为主数据源。

AbstractRoutingDataSource原理

AbstractRoutingDataSource中有一个重要的属性:

  • targetDataSources: 目标数据源,即项目启动的时候设置的需要通过AbstractRoutingDataSource管理的数据源。
  • defaultTargetDataSource: 默认数据源,项目启动的时候设置的默认数据源,如果没有指定数据源,默认返回改数据源。
  • resolvedDataSources: 也是存放的数据源,是对targetDataSources进行处理后进行存储的。可以看一下源码。

  • resolvedDefaultDataSource: 对默认数据源进行了二次处理,源码如上图最后的两行代码。

AbstractRoutingDataSource中所有的方法和属性:

比较重要的是determineTargetDataSource方法。

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = determineCurrentLookupKey();
    DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
    }
    if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    }
    return dataSource;
}

/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
@Nullable
protected abstract Object determineCurrentLookupKey();

这个方法主要就是返回一个DataSource对象,主要逻辑就是先通过方法determineCurrentLookupKey获取一个Object对象的lookupKey,然后通过这个lookupKey到resolvedDataSources中获取数据源(resolvedDataSources就是一个Map,上面已经提到过了);如果没有找到数据源,就返回默认的数据源。determineCurrentLookupKey就是程序员配置动态数据源需要自己实现的方法。


问题

配置多数据源后启动项目报错:Property 'sqlSessionFactory' or 'sqlSession Template' are required。

翻译过来就是:需要属性“sqlSessionFactory”或“sqlSessionTemplate”。也就是说 注入数据源的时候需要这两个数据,但是这两个属性在启动容器中没有找到。

当引入mybatis-plus依赖mybatis-plus-boot-starter后,会添加一个自动配置类 MybatisPlusAutoConfiguration 。 其中有两个方法sqlSessionFactory()、sqlSessionTemplate()。这两个方法就是 给容器中注入“sqlSessionFactory”或“sqlSessionTemplate”两个属性。


@Configuration(
        proxyBeanMethods = false
)
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }

        this.applyConfiguration(factory);
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }

        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }

        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }

        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }

        if (this.properties.getTypeAliasesSuperType() != null) {
            factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
        }

        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }

        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            factory.setTypeHandlers(this.typeHandlers);
        }

        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }

        Objects.requireNonNull(factory);
        this.getBeanThen(TransactionFactory.class, factory::setTransactionFactory);
        Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
        if (!ObjectUtils.isEmpty(this.languageDrivers)) {
            factory.setScriptingLanguageDrivers(this.languageDrivers);
        }

        Optional var10000 = Optional.ofNullable(defaultLanguageDriver);
        Objects.requireNonNull(factory);
        var10000.ifPresent(factory::setDefaultScriptingLanguageDriver);
        this.applySqlSessionFactoryBeanCustomizers(factory);
        GlobalConfig globalConfig = this.properties.getGlobalConfig();
        Objects.requireNonNull(globalConfig);
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        this.getBeansThen(IKeyGenerator.class, (i) -> {
            globalConfig.getDbConfig().setKeyGenerators(i);
        });
        Objects.requireNonNull(globalConfig);
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        Objects.requireNonNull(globalConfig);
        this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
        factory.setGlobalConfig(globalConfig);
        return factory.getObject();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }
}    

这里主要关注配置类上面的注解,详细如下:

@Configuration(
        proxyBeanMethods = false
)
// 当类路径下有SqlSessionFactory.class、SqlSessionFactoryBean.class时才生效
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
// 容器中只能有一个符合条件的DataSource
// 因为容器中有3个数据源,且没有指定主数据源,这个条件不通过,就不会初始化这个配置类了
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {

}

因为MybatisPlusAutoConfiguration不满足@ConditionalOnSingleCandidate(DataSource.class) 条件,所以自动配置不会生效,就不会执行sqlSessionFactory()、sqlSessionTemplate()两个方法。 容器中就不会有sqlSessionFactory、sqlSessionTemplate 两个bean对象,因此会报错。

所以在数据源配置类中的动态数据源配置上添加@Primary注解即可。

@Configuration
@PropertySource("classpath:config/jdbc.properties")
@MapperScan(basePackages = {"com.xinxing.learning.datasource.mapper"})
public class DynamicDataSourceConfig {
    @Bean
    @Primary
    public DataSource dynamicDataSource() {
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
        targetDataSource.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        dataSource.setDefaultTargetDataSource(masterDataSource());

        return dataSource;
    }
}
springboot之多数据源配置 应用直接操作的是abstractRoutingDataSource的实现类,告诉AbstractRoutingDataSource访问哪个数据库,然后由AbstractRoutingDataSource从事先配置好的数据源 (ds1,ds2)选择哪一个 ,来访问对应的数据库。 阅读详情

相关推荐

springboot整合多数据源的配置以及动态切换数据源,注解切换数据源

springboot整合多数据源的配置以及动态切换数据源,注解切换数据源

todoitbo的博客 9万+

Springboot设置多数据源,随时切换

在使用的springboot项目中,数据存在两个数据库中,需要经常在代码中切换数据库,所以特地记录一些切换数据库的工具类,如下: public class DataSourceContextHolder { /** * 默认数据源 */ public static final String DEFAULT_DS = "dev"; priv.........

qiaodaima0的博客 2万+

Spring Boot实现Redis多数据源动态切换 | Spring Cloud 32

在前面我们通过以下章节对Redis使用有了基础的了解: Spring Boot实现Redis同数据源动态切换DB | Spring Cloud 31 此章节基于spring-boot-starter-data-redis模块,实现了Redis多数据源动态切换,具体功能如下: 突破一个项目只能一个连接Redis数据源的限制 提供多种操作切换Redis数据源的方式(@Autowired方式 和RedisSelectSupport方式) 提供完善的代码使用示例

gmHappy 1万+

SpringBoot整合多数据源,并支持动态新增与切换(详细教程)

SpringBoot整合多数据源,并支持动态新增与切换(详细教程)

qq_35989920的博客 8546

Spring Boot整合MyBatis Plus实现MySQL多数据源动态切换

Spring Boot 是基于 Spring 框架的扩展,旨在简化 Spring 应用的初始搭建与开发。它通过自动配置(Auto-Configuration)和起步依赖(Starter Dependency)机制,极大地减少了配置复杂度,使开发者能够快速构建生产级应用。MyBatisPlus 是基于 MyBatis 的增强工具,它并没有对 MyBatis 的底层实现进行修改,而是在其基础上封装了一层通用操作接口和功能组件。其核心目标是简化数据库操作代码的编写,提升开发效率。特性。

weixin_42578963的博客 1123

Spring Boot多数据源动态切换的MyBatis注解实现

多数据源指的是在一个应用中配置和使用多个数据库连接。其目的在于解决不同业务模块对数据库性能、数据类型或访问策略的不同需求,提高系统的灵活性和可维护性。使用多数据源可以将不同模块的数据操作逻辑独立开来,降低模块间的耦合,便于维护和扩展。在现代化的企业级应用开发中,随着业务逻辑的日益复杂,单个数据库往往难以满足系统对性能和功能的综合需求。

weixin_31974443的博客 925

基于Spring Boot实现Mybatis的多数据源切换和动态数据源加载

Spring Boot Mybaits mybatis基本配置、多数据源切换、动态加载数据源

【逸辰杳】的博客 2万+

Spring Boot中实现动态多数据源切换的全攻略

Spring Boot是Spring框架中的一个模块,它提供了快速、广泛的解决方案,用于简化基于Spring的应用开发。Spring Boot的核心设计理念是约定优于配置,它允许开发者通过最小的配置来启动和运行Spring应用程序。数据源通常指的是数据的来源,它为应用程序提供数据访问能力。在企业级应用中,数据源可能包含关系型数据库、文件系统、消息队列等多种类型。在Spring框架中,数据源是指数据库连接池的抽象,通过它可以在应用程序中实现对数据库的高效访问。

weixin_31776191的博客 361

Spring Boot 实战:多数据源动态切换完整指南(含原理、实现与扩展)

本文介绍了Spring Boot中实现多数据源动态切换的完整方案。通过ThreadLocal上下文隔离、AbstractRoutingDataSource路由机制和自定义数据源上下文持有者,实现运行时动态切换数据源。核心包括:1)DataSourceContextHolder管理线程级数据源选择;2)DynamicDataSource继承AbstractRoutingDataSource实现路由逻辑;3)配置多个数据源Bean并通过Map建立映射。方案适用于多租户、分库分表等场景。

qq_46371374的博客 1281

华为昇腾Ascend C中级认证考试-tanh

注册昇腾社区账号后可以免费领券考试。

2600_96284887的博客 468

AI医疗影像诊断工具2025 完整教程

本教程所述工具均为辅助诊断设备,不可替代执业医师判断。部署使用需遵守当地医疗器械管理法规。推荐开源框架组合:代码太多打包上传网盘了 自行下载。2025年的AI医疗影像诊断工具将以。

m0_60084632的博客 2028

Spring Boot 3 整合 Mybatis-Plus 动态数据源实现多数据源切换

Spring Boot多数据源集成的dynamic-datasource-spring-boot-starter,轻松支持注解与手动方式切换数据源,提升在多数据库场景下的灵活性与开发便捷性。

有来技术 9648

MATLAB实战:手把手教你用三频四步相移法搞定结构光三维重建(附完整代码)

本文详细介绍了如何使用MATLAB实现三频四步相移法进行高精度结构光三维重建。通过光栅图像预处理、相位计算、多频外差与相位解包裹等步骤,结合完整代码示例,帮助读者掌握从二维图像到三维点云的关键技术。该方法在工业检测和逆向工程中具有抗干扰能力强、高分辨率和鲁棒性等优势。

weixin_29201933的博客 338

Spring Boot中@DS注解实现多数据源动态切换的实战指南

本文详细介绍了在Spring Boot中使用@DS注解实现多数据源动态切换的实战方法。通过配置示例和代码演示,展示了如何简化多数据源管理,提升系统灵活性和维护效率。特别适合需要同时操作多个数据库的Java开发者,涵盖基础配置、注解使用、事务处理及性能优化等关键知识点。

weixin_27330481的博客 231

基于Simulink/Matlab的VSG并网系统单相接地与相间短路故障仿真建模与特性分析

本文详细介绍了基于Simulink/Matlab搭建虚拟同步发电机(VSG)并网系统仿真模型的方法,并重点分析了单相接地与相间短路故障下的系统特性。通过设置三相故障模块参数,模拟了故障发生时的电压、电流变化及VSG动态响应,为理解故障机理、优化控制策略及设计继电保护提供了仿真依据与实践指导。

t8u9v0w1x的博客 514

SAP PI实战:5分钟搞定REST适配器同步接口配置(含Postman测试技巧)

本文详细介绍了如何在SAP PI中快速配置REST适配器同步接口,包括ESR设计、集成流配置及Postman测试技巧。通过实战步骤和关键参数设置,帮助开发者高效实现系统集成,特别适合需要实时数据交互的业务场景。

weixin_33738555的博客 485

Spring Boot多数据源配置并通过注解实现动态切换数据源

Spring Boot提供了AbstractRoutingDataSource 根据用户定义的规则选择使用的数据源,这样我们可以在每次数据库操作前设置使用的数据源,实现可动态路由的数据源,从而实现读写分离的功能。

huangjhai的博客 6578

Palantir的本体论建模跟数仓建模的异同

Palantir的Foundry Ontology通过构建组织的数字孪生体,将数据集和模型映射为对象类型、属性、链接和操作类型,形成完整的语义知识网络。与传统数仓建模相比,本体建模以业务问题为驱动,强调细粒度语义和实时决策能力,采用图语义技术实现灵活的映射关系。

轩哥的个人研究和成长记录 1589

Spring Boot实现多数据源动态切换

Spring Boot实现多数据源动态切换

byx的博客 1222

机器人中的数值优化(二十一)—— 伴随灵敏度分析、线性方程组求解器的分类和特点、优化软件

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考,主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等,本系列文章篇数较多,不定期更新,上半部分介绍无约束优化,下半部分介绍带约束的优化,中间会穿插一些路径规划方面的应用实例

慕羽★的博客 4205

大数据领域数据产品的交通运输行业应用

您是否经历过早高峰被堵在桥上,看着导航显示“前方5公里缓行”却无能为力?是否见过货车司机绕远路导致成本飙升?这些“交通痛点”的背后,是传统交通管理依赖经验决策的局限。本文将聚焦“大数据数据产品”这一核心工具,从技术原理到实际应用,全面解析它如何为交通运输行业“开天眼”,解决从城市拥堵到物流效率的一系列难题。本文将按照“问题引入→概念拆解→技术原理→实战案例→应用场景→未来趋势”的逻辑展开,用生活化比喻降低理解门槛,用代码和数学模型提升专业性,最终帮您建立“大数据+交通”的完整认知框架。交通数据。

操作系统内核探秘的博客 236

【多数据源动态切换】Spring Boot中实现多数据源动态切换效果(2):通过开源项目Dynamic Datasource Spring Boot Starter实现

在Spring Boot中,可以通过多种方式实现多数据源的动态切换效果,本篇介绍第二种实现方案。

wufaqidong1的博客 1020

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Spring-Boot 多数据源配置+动态数据源切换+多数据源事物配置实现主从数据库存储分离

网上关于动态数据源配置的博文一搜一大堆,都是拿来主义,往往把需要的人弄得不是太明白,也没有个具体的好用的简单的demo例子供人参考,本篇,我也是拿来主义,,我拿来核心的core,进行demo案列整理,我只挑重要的部分讲,demo会在最后提供GitHub下载(博主 2018年3月16日14:26:47 注: 这种多数据源的动态切换确实可以解决数据的主从分库操作,但是却有一个致命的BUG,那就是事务不...

Appleyk的专栏 1万+

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Spring-Boot + AOP实现多数据源动态切换

最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库。所以涉及到需要在一个项目中配置多数据源,并且能够动态切换。 设计总体思路 Spring-Boot+AOP方式实现多数据源切换,继承AbstractRoutingDataSource实现数据源动态的获取,在service层使用注解指定...

weixin_40685905的博客 488

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

使用 dynamic-datasource-spring-boot-starter 实现多数据源动态切换

参考文档Spring Boot 官方文档MyBatis 官方文档。

技术分享 2994

Spring Boot动态数据源切换:优雅实现多数据源管理

摘要:本文介绍了一种基于Spring Boot的动态数据源切换方案,通过自定义注解和AOP实现透明化切换。方案采用三层结构:注解层标记数据源、路由层管理上下文、切面层执行切换。核心代码包括数据源注解定义、动态数据源上下文和AOP切面实现。方案支持方法级和类级数据源指定,具有非侵入式、线程安全和易于扩展等特点。文章提供了完整配置示例和使用说明,并特别强调了与事务注解的联用注意事项。该方案适用于多租户系统、读写分离等复杂场景。

qq_62923382的博客 1064
上一篇: 面试必问的HashCode技术内幕
下一篇: 你会用Java抠图吗?【图像处理】获取用户感兴趣的区域
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值