SpringBoot多数据源配置实战:从手动配置到dynamic-datasource组件详解 📅 发布时间:2026/8/26 6:18:02 👁 浏览次数: 1. 项目概述为什么需要多数据源在真实的业务开发里一个SpringBoot应用只连一个数据库的场景反而更像是“学生作业”或者“演示Demo”。稍微有点规模的项目数据来源往往不止一个。比如你的核心业务数据放在MySQL主库用户行为日志为了高性能写入可能放在另一个MySQL实例而一些报表查询又需要从专门的只读从库或者像PostgreSQL这样的分析型数据库里拉数据。更常见的场景是公司内部系统整合你需要同时连接一个自研的业务库和一个采购的第三方系统数据库。这时候如果还死守着单数据源的配置要么代码里到处写死JDBC连接要么就得搞出一堆别扭的“曲线救国”方案维护起来简直是噩梦。所以掌握SpringBoot配置多数据源不是一个“炫技”的选修课而是一个合格后端开发者必须面对的“生存技能”。它直接关系到你项目的架构清晰度、代码可维护性以及未来面对复杂数据需求时的扩展能力。网上教程很多但要么讲得太浅只告诉你怎么配application.yml要么讲得太深一上来就分析AbstractRoutingDataSource的源码让新手望而却步。这篇内容我会从一个老鸟的实战视角带你从“是什么”、“为什么”到“怎么做”把配置多数据源这件事掰开揉碎了讲清楚重点不是让你照抄配置而是理解每一步背后的设计意图和可能踩的坑。2. 核心思路与方案选型不止一种玩法在动手写代码之前我们得先搞清楚有哪几条路可以走。不同的方案适用于不同的场景和复杂度选错了后期重构的成本可不低。2.1 方案一基于配置类的显式声明推荐新手入门这是最直观、也是最容易理解的方式。核心思想就是放弃SpringBoot的自动配置我们自己手动创建多个DataSource、SqlSessionFactory、TransactionManager等Bean。为什么推荐新手从这里开始因为这种方式将每个数据源的“生命周期”完全掌控在自己手里。从数据源创建、事务管理器绑定到MyBatis的会话工厂每一步你都看得见摸得着。它虽然代码量稍多但逻辑极其清晰排错也方便。你能够非常明确地知道UserDao用的是哪个数据源LogDao又绑定了哪个事务管理器。当配置出错时你很容易定位到是primaryDataSource这个Bean没创建成功还是secondaryTransactionManager注入错了。它的工作原理是什么简单说就是利用Spring的Configuration配置类定义两套或多套完整的、彼此独立的JPA/MyBatis数据访问组件。然后通过Bean注解给它们起不同的名字或者使用Qualifier注解在注入时进行区分。SpringBoot默认的DataSourceAutoConfiguration会因为我们手动定义了DataSourceBean而自动退出不会和我们“打架”。适用场景数据源数量固定且较少比如2-3个。不同数据源访问的数据库类型可能不同如一个MySQL一个PostgreSQL。团队对Spring Boot自动配置原理还不算特别熟悉追求稳定和可控。2.2 方案二使用dynamic-datasource-spring-boot-starter推荐生产级项目这是一个非常流行的开源组件作者是国内的开发者。它的核心思想是引入一个“动态数据源”并通过AOP在方法执行前根据注解如DS(“slave”)来动态切换当前线程使用的数据源。为什么它在生产环境更受青睐因为它极大地简化了代码。你不需要为每个数据源写一堆重复的配置类只需要在application.yml里定义好所有数据源然后在Service层的方法上打一个DS注解就行了。它内部通过AbstractRoutingDataSource和Spring AOP实现了数据源的动态路由对业务代码的侵入性非常小。这对于有大量读写分离、分库分表简单场景需求的项目来说简直是神器。需要注意的坑这个组件虽好但也不是银弹。我踩过最大的一个坑就是事务管理。如果你在同一个Transactional注解的方法内调用了多个带有不同DS注解的方法那么数据源切换可能会失效因为事务管理器通常是在方法入口处就确定了数据源。组件官方文档对此有详细说明通常的解决方案是避免在事务方法内跨数据源调用或者使用其提供的事务增强特性。另一个常见错误是配置问题比如在spring.datasource.dynamic.primary没设置对或者数据源名称在注解里写错了启动时就会报类似dynamic-datasource failed to configure a datasource: url attribute的错误其实就是在说“老大你让我用的那个数据源我找不到它的连接信息啊”。适用场景数据源数量较多或可能动态增加。有明确的读写分离需求。希望业务代码保持简洁通过注解控制数据源。团队愿意引入并学习一个第三方组件。2.3 方案三完全手写AbstractRoutingDataSource高阶定制这个方案是方案二的“手动版”。你需要自己继承AbstractRoutingDataSource实现determineCurrentLookupKey()方法返回当前线程应该使用的数据源标识key。然后你需要自己用AOP或者HandlerInterceptor在请求的某个环节比如根据请求头、线程变量来设置这个key。什么情况下你会需要自己手写当你的数据源路由逻辑非常复杂超出了DS注解的能力范围。比如你的数据源选择不是基于方法而是基于登录用户的租户ID多租户系统或者基于某个复杂的业务规则计算出来的分片键。这时候你就需要把路由逻辑牢牢抓在自己手里。不推荐新手直接尝试的原因这个方案需要对Spring的AOP、事务管理、线程上下文ThreadLocal有比较深的理解。你需要自己处理好数据源切换的时机以及最关键的事务上下文传播问题否则极易出现数据源混乱或连接泄露。它更偏向于一种“框架级”的定制适用于有特殊架构需求的场景。我的选择建议对于绝大多数业务项目我强烈推荐方案二。它平衡了易用性、功能和社区支持。本篇文章的后续实操部分也将以方案一显式配置和方案二动态数据源组件作为重点因为这两个覆盖了90%以上的应用场景。方案三作为知识拓展大家了解其思想即可。3. 方案一实操手动配置多数据源MyBatis版我们假设一个经典场景应用需要连接两个MySQL数据库一个叫primary_db主库负责核心业务读写一个叫report_db报表库只读。3.1 项目结构与依赖准备首先创建一个标准的SpringBoot项目。pom.xml中需要的基础依赖如下dependencies !-- Spring Boot Starter -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 我们使用MyBatis这是它的SpringBoot官方整合包 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version !-- 请使用最新稳定版 -- /dependency !-- MySQL驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 其他工具依赖如Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies关键点这里我们引入了mybatis-spring-boot-starter它会自动配置单数据源下的MyBatis。但当我们手动配置多数据源时它的部分自动配置会失效这正是我们想要的——我们需要夺回控制权。3.2 配置文件拆分与定义我们不把所有配置堆在application.yml里那样会显得很乱。采用多配置文件的方式更清晰。application.yml(主配置设置激活的profile和公共属性)spring: profiles: active: multi-ds # 激活名为 multi-ds 的配置 # 其他全局配置比如服务器端口 server: port: 8080application-multi-ds.yml(多数据源专属配置)# 第一个数据源主库 primary: datasource: url: jdbc:mysql://localhost:3306/primary_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # 连接池配置这里使用HikariCPSpringBoot默认 hikari: pool-name: PrimaryHikariPool maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 # 第二个数据源报表库 secondary: datasource: url: jdbc:mysql://localhost:3307/report_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: report_user password: report_pass driver-class-name: com.mysql.cj.jdbc.Driver hikari: pool-name: SecondaryHikariPool maximum-pool-size: 10 # 报表库通常压力小连接数可以设少点 minimum-idle: 2 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 # MyBatis的公共配置比如别名、mapper位置这里配置的会被两个数据源共享基础设置但各自工厂会覆盖 mybatis: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发时开启方便看SQL注意这里我们把数据源配置的前缀从默认的spring.datasource改成了primary.datasource和secondary.datasource。这是关键一步目的是让SpringBoot的DataSourceAutoConfiguration无法识别这些配置从而避免它自动创建单数据源Bean。我们自己会在配置类里读取这些前缀的属性。3.3 主数据源Primary配置类我们为primary_db创建一套完整的访问组件。package com.example.demo.config.primary; import com.zaxxer.hikari.HikariDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; // 重要注解 import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; Configuration // 指定这个数据源对应的MyBatis Mapper接口所在的包 MapperScan( basePackages com.example.demo.mapper.primary, sqlSessionFactoryRef primarySqlSessionFactory ) public class PrimaryDataSourceConfig { /** * 创建主数据源Bean。 * ConfigurationProperties 会读取 primary.datasource 开头的配置并注入到HikariDataSource的属性中。 * Primary 注解是关键它告诉Spring当有多个同类型DataSource的Bean时优先使用这个。 * 这确保了那些没有指定名字的自动注入比如JdbcTemplate会用到这个主数据源。 */ Bean(name primaryDataSource) ConfigurationProperties(prefix primary.datasource) Primary public DataSource primaryDataSource() { // 使用Spring Boot的Builder模式创建它会自动根据配置绑定Hikari属性 return DataSourceBuilder.create().type(HikariDataSource.class).build(); } /** * 创建主数据源的事务管理器Bean。 * 事务管理器需要知道它管理的是哪个数据源所以通过Qualifier指定我们上面创建的primaryDataSource。 */ Bean(name primaryTransactionManager) Primary public DataSourceTransactionManager primaryTransactionManager( Qualifier(primaryDataSource) DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } /** * 创建主数据源的MyBatis SqlSessionFactory。 * 这是MyBatis的核心负责创建SqlSession。我们需要为它设置具体的数据源和Mapper XML文件的位置。 */ Bean(name primarySqlSessionFactory) Primary public SqlSessionFactory primarySqlSessionFactory(Qualifier(primaryDataSource) DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactoryBean new SqlSessionFactoryBean(); // 设置数据源 sessionFactoryBean.setDataSource(dataSource); // 设置Mapper XML文件的位置。这里我们约定主数据源的Mapper XML放在 classpath:mapper/primary/ 下 sessionFactoryBean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources(classpath:mapper/primary/*.xml) ); // 如果你有全局的MyBatis配置比如在application.yml里定义的也可以在这里设置 // sessionFactoryBean.setConfiguration(mybatisConfiguration); return sessionFactoryBean.getObject(); } }逐行解析与避坑指南Primary注解这是多数据源配置的灵魂。Spring容器里如果有多个DataSource或TransactionManager在自动注入时就会困惑。Primary标记了“默认选项”。通常你业务中最主要、最常用的那个数据源应该被标记为Primary。这样像JdbcTemplate这种没有指定Qualifier的Bean就会自动使用主数据源避免报NoUniqueBeanDefinitionException错误。MapperScan注解它的basePackages属性指定了这个SqlSessionFactory负责扫描的Mapper接口包。sqlSessionFactoryRef属性则明确绑定到我们下面定义的primarySqlSessionFactoryBean。务必确保这两个属性对应正确否则你的Mapper接口会被错误的SqlSessionFactory管理导致连接错数据库。DataSourceBuilder.create().type(HikariDataSource.class).build()这里显式指定了使用HikariCP连接池。虽然SpringBoot 2.x默认就是Hikari但显式声明可以避免因依赖或版本变化导致的意外。通过ConfigurationProperties配置文件里primary.datasource.hikari.*下的所有属性都会自动注入到这个HikariDataSource实例中。Mapper XML路径classpath:mapper/primary/*.xml。这是一种良好的实践将不同数据源的SQL映射文件物理隔离到不同的目录清晰且不易出错。你需要在resources目录下建立mapper/primary文件夹来存放对应的XML文件。3.4 从数据源Secondary配置类第二个数据源的配置类与主数据源几乎是对称的但绝对不能再用Primary注解。package com.example.demo.config.secondary; import com.zaxxer.hikari.HikariDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; Configuration // 注意这里扫描的是 secondary 的Mapper包 MapperScan( basePackages com.example.demo.mapper.secondary, sqlSessionFactoryRef secondarySqlSessionFactory ) public class SecondaryDataSourceConfig { // Bean的名字不能重复 Bean(name secondaryDataSource) ConfigurationProperties(prefix secondary.datasource) public DataSource secondaryDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); } Bean(name secondaryTransactionManager) public DataSourceTransactionManager secondaryTransactionManager( Qualifier(secondaryDataSource) DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } Bean(name secondarySqlSessionFactory) public SqlSessionFactory secondarySqlSessionFactory(Qualifier(secondaryDataSource) DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactoryBean new SqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource); // XML文件路径指向 secondary 目录 sessionFactoryBean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources(classpath:mapper/secondary/*.xml) ); return sessionFactoryBean.getObject(); } }关键区别移除了所有Primary注解。所有Bean的name或value属性都改为了secondaryXXX。MapperScan扫描的包路径和sqlSessionFactoryRef指向了新的Bean。ConfigurationProperties前缀读取secondary.datasource。Mapper XML路径指向classpath:mapper/secondary/。3.5 编写Mapper与Service进行测试现在我们来创建对应的Mapper和Service验证配置是否生效。1. 实体类简单示例package com.example.demo.entity.primary; import lombok.Data; Data public class User { private Long id; private String name; private String email; }package com.example.demo.entity.secondary; import lombok.Data; Data public class Report { private Long id; private String reportName; private Integer viewCount; }2. Primary数据源的Mapper接口和XMLpackage com.example.demo.mapper.primary; import com.example.demo.entity.primary.User; import org.apache.ibatis.annotations.Mapper; import java.util.List; Mapper // 这里Mapper可加可不加因为MapperScan已经扫描了这个包 public interface UserMapper { ListUser selectAllUsers(); }resources/mapper/primary/UserMapper.xml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.demo.mapper.primary.UserMapper select idselectAllUsers resultTypecom.example.demo.entity.primary.User SELECT id, name, email FROM user /select /mapper3. Secondary数据源的Mapper接口和XMLpackage com.example.demo.mapper.secondary; import com.example.demo.entity.secondary.Report; import org.apache.ibatis.annotations.Mapper; import java.util.List; Mapper public interface ReportMapper { ListReport selectAllReports(); }resources/mapper/secondary/ReportMapper.xml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.demo.mapper.secondary.ReportMapper select idselectAllReports resultTypecom.example.demo.entity.secondary.Report SELECT id, report_name, view_count FROM report /select /mapper4. Service层调用package com.example.demo.service; import com.example.demo.entity.primary.User; import com.example.demo.entity.secondary.Report; import com.example.demo.mapper.primary.UserMapper; import com.example.demo.mapper.secondary.ReportMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.sql.DataSource; import java.util.List; Service public class DemoService { Autowired private UserMapper userMapper; // 自动注入由于UserMapper在primary的MapperScan下所以绑定primarySqlSessionFactory Autowired private ReportMapper reportMapper; // 自动注入绑定secondarySqlSessionFactory /** * 演示无事务的查询 */ public void queryFromMultipleSources() { ListUser users userMapper.selectAllUsers(); System.out.println(主库用户数: users.size()); ListReport reports reportMapper.selectAllReports(); System.out.println(报表库报告数: reports.size()); } /** * 演示在主数据源上使用事务。 * 注意Transactional 默认使用标记了Primary的TransactionManager即primaryTransactionManager。 */ Transactional public void updateUserWithTransaction(User user) { // 这里执行更新用户的SQL会使用primaryDataSource的事务 // userMapper.update(user); System.out.println(在主库事务中更新用户); } /** * 演示在从数据源上使用事务。 * 必须通过Transactional的value或transactionManager属性指定具体的事务管理器Bean名称。 */ Transactional(transactionManager secondaryTransactionManager) public void updateReportWithTransaction(Report report) { // 这里执行更新报告的SQL会使用secondaryDataSource的事务 // reportMapper.update(report); System.out.println(在报表库事务中更新报告); } /** * 演示注入特定的DataSource或JdbcTemplate。 * 如果你想直接使用JdbcTemplate操作某个特定的数据源可以这样注入。 */ Autowired Qualifier(secondaryDataSource) // 指定注入名为secondaryDataSource的Bean private DataSource secondaryDataSource; // 或者直接注入一个针对secondaryDataSource的JdbcTemplate Bean需要在配置类中定义 // Autowired // Qualifier(secondaryJdbcTemplate) // private JdbcTemplate secondaryJdbcTemplate; }启动与测试确保你的primary_db和report_db两个MySQL实例已启动并且库表存在。启动SpringBoot应用。观察控制台日志应该能看到两个Hikari连接池分别初始化的信息。调用DemoService.queryFromMultipleSources()方法如果控制台分别打印出两个库的查询结果数量恭喜你多数据源配置成功了4. 方案二实操使用dynamic-datasource-spring-boot-starter手动配置虽然清晰但每个数据源都要写一堆模板代码。对于追求效率的项目我们上“全家桶”。4.1 引入依赖与基础配置首先在pom.xml中添加依赖。请务必查看官方仓库使用最新版本。dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version4.3.0/version !-- 示例版本请使用最新 -- /dependency !-- 其他依赖如mybatis-plus-starter、web等照常 --然后在application.yml中配置。这里和方案一有本质区别我们又要用回spring.datasource.dynamic这个标准前缀了因为组件要读取它。spring: datasource: dynamic: primary: master # 设置默认的数据源必须默认值就是master strict: false # 是否严格匹配数据源默认false。true时未匹配到指定数据源会报错false则使用默认数据源 datasource: master: # 数据源名称可以自定义这里用master代表主库 url: jdbc:mysql://localhost:3306/master_db?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 slave1: # 第二个数据源名称slave1 url: jdbc:mysql://localhost:3307/slave_db?useSSLfalseserverTimezoneAsia/Shanghai username: slave_user password: slave_pass driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 15 # 你可以继续添加 slave2, oracle-db, postgres-db 等等配置解读primary: 指定当没有显式使用数据源注解或者注解指定的数据源找不到且strictfalse时默认使用的数据源名称。这里设为master。datasource节点下每一个key如master、slave1就是一个数据源的名称其下的配置和单数据源时完全一样。组件会自动根据这些配置创建对应的DataSourceBean。4.2 在代码中使用DS注解切换数据源这是最核心、最便捷的部分。你几乎不需要额外的配置类。1. 在Service或Mapper层的方法上使用DSpackage com.example.demo.service; import com.baomidou.dynamic.datasource.annotation.DS; import com.example.demo.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; Service public class DynamicDataSourceService { Autowired private UserMapper userMapper; // 假设这个Mapper是使用MyBatis-Plus或普通MyBatis生成的 /** * 这个方法默认使用 DS 注解指定的数据源。 * 如果类上没有DS方法上也没有DS则使用配置文件中的 primary 数据源即master。 */ DS(slave1) // 指定这个方法使用名为 slave1 的数据源 public ListUser getUsersFromSlave() { return userMapper.selectList(null); // 假设使用MyBatis-Plus } /** * 这个方法没有DS注解将使用类上注解的数据源。 * 如果类上也没有则回退到默认的 primary (master) 数据源。 */ public User getUserByIdFromMaster(Long id) { return userMapper.selectById(id); } /** * 在类级别注解表示这个类里所有方法默认使用 master 数据源。 * 方法上的注解优先级高于类上的注解。 */ DS(master) Service public class MasterService { public void updateUser(User user) { // 默认在master数据源上执行 userMapper.updateById(user); } DS(slave1) public User findUserFromSlave(Long id) { // 这个方法覆盖了类注解使用slave1数据源 return userMapper.selectById(id); } } }DS注解的优先级规则务必牢记方法注解类注解默认数据源primary这意味着你可以在类上定义一个常用的数据源然后在某个特殊方法上覆盖它非常灵活。4.3 深入理解事务管理与DSTransactional这是使用dynamic-datasource时最容易出问题的地方必须单独拿出来讲。问题场景假设你有一个Service方法它内部调用了两个DAO方法一个需要写主库一个只需要读从库。如果你在Service方法上使用了Spring原生的Transactional那么在整个事务范围内数据源会被固定为事务开始时确定的那一个通常是默认数据源或第一个被调用的DS方法所在数据源导致DS注解失效所有数据库操作都跑到同一个库去了。解决方案使用DSTransactional组件提供了DSTransactional注解来处理多数据源事务。但它不支持跨数据源的分布式事务比如XA事务它只能保证单个数据源内的事务性。它的作用是确保在注解的方法内所有数据库操作都在同一个数据源上执行并在这个数据源上开启事务。import com.baomidou.dynamic.datasource.annotation.DS; import com.baomidou.dynamic.datasource.annotation.DSTransactional; Service public class TransactionService { DS(master) DSTransactional // 这个注解确保下面所有操作在master数据源上并开启事务 public void businessMethod() { // 操作1更新master库的用户表 userMapper.update(...); // 操作2在master库插入一条日志 logMapper.insert(...); // 这两个操作在同一个物理事务里要么都成功要么都回滚。 } DS(slave1) DSTransactional // 这个注解确保下面所有操作在slave1数据源上并开启事务 public void readOnlyMethod() { // 复杂查询可能需要多次查询slave1并保证在同一个连接/事务视图内可重复读 reportMapper.complexQuery1(...); reportMapper.complexQuery2(...); } }重要限制与最佳实践严禁混用在同一个方法内不要同时使用Transactional和DS。要么用DSTransactional要么就不要事务。避免跨库事务DSTransactional无法保证“更新A库和更新B库”同时成功或失败。如果你有强一致性要求需要引入Seata等分布式事务中间件或者从业务设计上避免跨库写操作如最终一致性。只读事务对于纯查询可以使用DS(“slave”)Transactional(readOnly true)。但注意这仍然会绑定到某个数据源的事务管理器上。对于简单的查询不加任何事务注解往往是更轻量的选择。传播行为DSTransactional支持Spring的事务传播属性如Propagation.REQUIRES_NEW但行为是针对当前数据源的。4.4 高级特性与配置除了基本的DS该组件还提供了一些高级功能1. SPI扩展与自定义数据源选择你可以实现DynamicDataSourceStrategy接口来自定义负载均衡策略比如多个从库随机选、轮询。也可以在determineCurrentLookupKey前后进行自定义逻辑实现基于线程变量、请求参数等复杂路由。2. 支持多种数据源类型在配置中除了url方式还支持jndi-name、hikari、druid等多种连接池的直接配置。3. 健康检查与监控组件会暴露数据源的健康指标需要Actuator依赖你可以在/actuator/health端点查看各个数据源的状态。4. 配置分离对于敏感的生产环境密码强烈建议将数据源配置放在spring.datasource.dynamic.datasource.master.password这样的属性中并通过spring.config.import或Apollo/Nacos等配置中心引入而不是硬编码在YAML文件里。5. 常见问题、排查技巧与性能优化实录配置多数据源的过程中我踩过的坑不计其数。下面把这些血泪教训整理成表希望能帮你快速排雷。问题现象可能原因排查步骤与解决方案启动报错Failed to configure a DataSource: ‘url’ attribute is not specified1. SpringBoot自动配置试图创建单数据源但没找到spring.datasource.url。2. 在方案一中你可能忘了在ConfigurationProperties中指定正确的前缀或者配置项根本没加载。1.方案一检查你的配置类ConfigurationProperties(prefix“xxx”)中的prefix是否与application.yml中的配置前缀完全一致。检查配置文件是否被正确激活spring.profiles.active。2.方案二检查spring.datasource.dynamic.datasource下的每个数据源是否都正确配置了url。检查缩进是否正确YAML对缩进敏感。启动报错No qualifying bean of type ‘javax.sql.DataSource’ availableSpring容器中存在多个DataSource类型的Bean但某个地方如JdbcTemplate、EntityManager尝试自动注入时没有用Qualifier指定名字Spring无法决定用哪个。1.方案一确保你的主数据源Bean上标记了Primary注解。2. 检查是否有其他地方比如第三方库自动配置了DataSource。可以尝试在启动类上加SpringBootApplication(exclude {DataSourceAutoConfiguration.class})暂时排除自动配置但这不是根本解法需找到冲突源。启动报错Bean named ‘xxx’ is expected to be of type ‘SqlSessionFactory’ but was actually of type ‘…’MapperScan注解的sqlSessionFactoryRef属性指向的Bean名称错误或者该Bean根本没有被成功创建。1. 检查配置类中Bean(name “primarySqlSessionFactory”)等方法名是否与MapperScan(sqlSessionFactoryRef “primarySqlSessionFactory”)中的字符串完全一致。2. 检查SqlSessionFactoryBean的setDataSource注入的DataSourceBean是否存在且名称正确。DS注解切换数据源无效所有操作都跑到默认库1.事务问题方法被Spring的Transactional注解包裹导致数据源在事务开始时就被固定。2.注解位置错误DS注解在了Controller层或私有方法上AOP无法切入。3.数据源名称写错DS(“slave”)但配置中数据源名是slave1。1. 检查方法是否被Transactional注解。如果是考虑使用DSTransactional或移除事务注解对于纯查询。2. 确保DS注解在Service层的public方法上或者其所属的类上。3. 仔细核对DS注解中的字符串与application.yml中配置的数据源名称如datasource.master里的master是否完全一致包括大小写。DSTransactional内跨数据源操作数据源不切换DSTransactional的设计就是不支持跨数据源事务。它只是确保在它管理的事务内所有操作使用同一个数据源。重新设计业务逻辑这是架构限制不是bug。将需要跨库写操作拆分成多个方法分别用不同的DSTransactional管理并考虑最终一致性方案。或者评估是否真的需要强一致性如果必须引入分布式事务框架。多数据源下MyBatis二级缓存混乱如果多个SqlSessionFactory共享了同一个缓存实例默认是同一个可能导致从不同数据库查到的数据被错误缓存。为每个SqlSessionFactory配置独立的缓存实例。在配置类中创建SqlSessionFactoryBean时通过setCache方法指定不同的Cache实现或配置不同的cacheNamespace。更简单的做法是在生产环境直接关闭二级缓存用Redis等外部缓存更可控。连接池资源耗尽为每个数据源配置的连接池大小总和超过了数据库服务器的最大连接数限制。1.合理规划根据每个数据源的实际压力读写比、QPS分别设置maximum-pool-size。报表库、日志库可以设小点。2.监控启用HikariCP的JMX监控或通过/actuator/metrics/hikaricp.connections.*端点查看连接池状态。3.设置超时务必配置connection-timeout获取连接超时和idle-timeout连接空闲超时防止连接泄露。从库延迟导致读到旧数据读写分离场景业务上在写入主库后立刻查询从库由于主从复制有延迟可能查不到刚写入的数据。1.强制读主对于需要强一致性的读请求使用DS(“master”)强制走主库。2.延迟查询写入后如果业务允许等待几百毫秒再查从库。3.业务设计区分“实时性要求高”和“允许延迟”的查询分别路由到主库和从库。性能优化心得连接池配置不是越大越好maximum-pool-size应该根据数据库服务器性能和业务并发量来定。一个经验公式是连接数 ≈ (核心数 * 2) 有效磁盘数。对于Web应用初始可以设置为CPU核心数的2-4倍再根据监控调整。设置过大反而会导致数据库负载过高上下文切换频繁。善用minimum-idle对于流量波动大的应用可以设置一个较小的minimum-idle如2-5让连接池在空闲时收缩高峰时再扩容。避免长期占用大量空闲连接。为不同用途的数据源设置不同的超时时间对于核心交易库connection-timeout可以设短一点如3秒快速失败。对于报表查询库可以设长一点如10秒因为查询可能本来就很慢。监控与告警一定要将数据源的健康状态连接数、活跃数、等待数纳入监控。当连接等待时间(connection-timeout)频繁超时或者空闲连接(idle-connections)长期为0都是需要扩容或优化SQL的强烈信号。配置多数据源从“配通”到“配优”是一个需要结合业务特性和监控数据不断调整的过程。希望这篇超详细的指南能帮你不仅跑通代码更能理解背后的原理在遇到问题时能快速找到方向。记住技术选型没有绝对的好坏只有适合与否。对于简单固定的多库访问手动配置清晰可控对于需要动态路由和读写分离的场景dynamic-datasource这类组件能极大提升开发效率。根据你的实际场景做出最适合的选择吧。