SpringBoot如何整合Mybatis与Druid实现多数据源动态切换?

来源:网络推广作者:深圳SEO公司头衔:草根站长
导读:本期聚焦于深圳SEO公司创作的《SpringBoot如何整合Mybatis与Druid实现多数据源动态切换?》,敬请观看详情。单数据源在业务增长后往往会遇到主从分离、多租户隔离、报表与业务库分拆等需求,如果继续使用单一连接池和固定Mapper扫描,改造成本会非常高。本文从零开始,用一个SpringBoot工程演示如何同时接入两个或更多Druid数据源,并配合Mybatis完成SqlSessionFactory隔离与动态数据源路由。内容覆盖依赖引入、YAML多数据源配置、Druid连接池参数调优、DataSource注册、Mapper扫描、基于AbstractRoutingDataSource的动态切换、ThreadLocal上下文传递以及注解驱动的AOP切面。文章还单独说明了Druid监控页面的开启方式和多数据源下事务管理容易踩的坑。按照步骤操作,即使对多数据源不熟悉也能快速搭出可运行示例。

在SpringBoot工程里只维护一个数据源通常能满足早期需求,但随着读写分离、业务库拆分或多租户隔离的落地,单个连接池越来越难以支撑。本文不会停留在概念层面,而是直接给出一个能够运行的多数据源改造方案,重点说明Druid连接池如何同时管理多个数据库,Mybatis如何为每个数据源建立独立的SqlSessionFactory,以及怎样通过动态路由让DAO层无感切换。

SpringBoot如何整合Mybatis与Druid实现多数据源动态切换?

一、多数据源要解决的核心场景

首先需要明确,多数据源并不是简单地把多个数据库地址写进配置文件。真正要处理的是连接池隔离、Mapper绑定、事务边界和切换时机。常见的需求包括主从复制下的读写分离、订单库与报表库互相独立、不同租户使用不同schema等。如果只注册多个DataSource而不做路由,Mybatis默认只会使用标注了@Primary的那个数据源,其余数据源不会参与SQL执行。

例如同一个服务里,主库负责写入,从库负责查询,此时DAO层不能固定指向某一个数据源,否则读请求会全部压在主库上。又比如业务库和日志库使用同一套Mapper接口,但表结构相同、数据不同,就需要根据调用方传入的标识来动态选择。理解了这些场景,再去看Druid多数据源的配置会更有针对性。

二、Druid与Mybatis的基础整合

依赖层面只需引入druid-spring-boot-starter、mybatis-spring-boot-starter以及数据库驱动。SpringBoot的自动装配会根据spring.datasource前缀创建单个数据源,但多数据源场景下不能依赖自动装配,需要手动注册所有DataSource、SqlSessionFactory和SqlSessionTemplate。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.23</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.2</version>
</dependency>

这里展示的是Maven依赖片段,实际使用时版本号根据自己的SpringBoot版本调整。Druid的starter会自动注册一个DruidDataSource,但那是基于单数据源默认前缀的,多数据源时我们要手动创建。mybatis-spring-boot-starter则提供了SqlSessionFactoryBean和SqlSessionTemplate,方便自定义。

接下来在application.yml中为每个数据源定义连接信息。注意不要使用spring.datasource作为前缀,避免自动装配冲突。可以自定义spring.datasource.master和spring.datasource.slave之类的结构,也可以完全自定义前缀。

spring:
  datasource:
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/master_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
      username: root
      password: root
      druid:
        initial-size: 5
        min-idle: 5
        max-active: 20
        max-wait: 60000
    slave:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/slave_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
      username: root
      password: root
      druid:
        initial-size: 5
        min-idle: 5
        max-active: 20
        max-wait: 60000

这段配置里master和slave是两个独立的数据源,Druid参数写在各自节点下。实际环境可以配置更多数据源,只需要继续追加。连接池参数不要照搬,建议根据数据库连接数和机器性能调整max-active与min-idle。url中的&是YAML转义后的写法,看起来会比普通URL多一个amp;,这是为了确保解析正确。

配置好连接信息后,需要创建配置类来手动注册数据源。SpringBoot不会自动把这些自定义前缀转换成DataSource,需要借助@ConfigurationProperties或手动读取配置。这里给出一个基于DruidDataSource手动创建的示例。

@Configuration
public class DataSourceConfig {

    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return new DruidDataSource();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return new DruidDataSource();
    }

    @Bean(name = "masterSqlSessionFactory")
    public SqlSessionFactory masterSqlSessionFactory(
            @Qualifier("masterDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/master/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "slaveSqlSessionFactory")
    public SqlSessionFactory slaveSqlSessionFactory(
            @Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/slave/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "masterSqlSessionTemplate")
    public SqlSessionTemplate masterSqlSessionTemplate(
            @Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "slaveSqlSessionTemplate")
    public SqlSessionTemplate slaveSqlSessionTemplate(
            @Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

上面代码创建了两个完全独立的SqlSessionFactory,并分别指定了不同的Mapper XML路径。这样主库和从库的SQL可以分开管理。如果两个数据源的表结构完全一致,也可以共用同一组Mapper XML,但在多租户或分库场景下建议分开,便于后续排查问题。

三、使用AbstractRoutingDataSource实现动态切换

如果每次访问数据库都要手动选择SqlSessionTemplate,代码侵入性会很大。更优雅的做法是让DAO层只调用一个Mapper接口,由底层根据上下文自动决定走哪个数据源。Spring提供了AbstractRoutingDataSource抽象类,它会在获取连接时调用determineCurrentLookupKey()方法,返回值作为数据源的key去匹配之前注册的targetDataSources。

先自定义一个DynamicDataSource继承该类,并维护一个ThreadLocal上下文,保证每个线程的数据源选择互不干扰。

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceKey();
    }
}
public class DataSourceContextHolder {

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

    public static void setDataSourceKey(String key) {
        CONTEXT.set(key);
    }

    public static String getDataSourceKey() {
        return CONTEXT.get();
    }

    public static void clearDataSourceKey() {
        CONTEXT.remove();
    }
}

然后将所有真实数据源打包进DynamicDataSource,并把默认数据源设为主库。这样在没有显式切换时,所有请求都会落到主库,避免读请求意外打到从库造成数据不一致。

@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(
        @Qualifier("masterDataSource") DataSource master,
        @Qualifier("slaveDataSource") DataSource slave) {
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put("master", master);
    targetDataSources.put("slave", slave);
    dynamicDataSource.setTargetDataSources(targetDataSources);
    dynamicDataSource.setDefaultTargetDataSource(master);
    return dynamicDataSource;
}

有了动态数据源还不够,需要一个触发切换的机制。最简单的做法是自定义一个注解@DataSource,配合Spring AOP在Service方法执行前设置数据源key,执行后清理。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    String value() default "master";
}
@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(dataSource)")
    public void before(DataSource dataSource) {
        DataSourceContextHolder.setDataSourceKey(dataSource.value());
    }

    @After("@annotation(dataSource)")
    public void after(DataSource dataSource) {
        DataSourceContextHolder.clearDataSourceKey();
    }
}

使用时只需要在Service方法上标注@DataSource("slave"),该方法内调用Mapper就会走从库。需要特别注意的是,切换数据源的AOP切面必须作用在Service层,而不是Mapper层。因为Mapper接口的代理对象在Spring容器中生成较早,切面顺序和事务切面的先后会影响最终连接选择。如果切在Controller层,进入Service前切换也有效,但通常不建议放在过外层,避免其他无关逻辑误切换。

四、Druid监控与事务边界问题

Druid提供了强大的监控统计功能,多数据源场景下同样可以开启。需要注册StatViewServlet和WebStatFilter,并在数据源上设置filters为stat,wall。YAML里可以添加druid.filters,但要注意多数据源时每个数据源都要单独配置。

@Bean
public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
    ServletRegistrationBean<StatViewServlet> registrationBean =
            new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
    registrationBean.addInitParameter("loginUsername", "admin");
    registrationBean.addInitParameter("loginPassword", "admin123");
    return registrationBean;
}

@Bean
public FilterRegistrationBean<WebStatFilter> druidWebStatFilter() {
    FilterRegistrationBean<WebStatFilter> filterBean =
            new FilterRegistrationBean<>(new WebStatFilter());
    filterBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
    filterBean.setUrlPatterns(Arrays.asList("/*"));
    return filterBean;
}

访问http://localhost:8080/druid/可以查看SQL执行时间、连接池状态和慢查询记录。多数据源时,监控页面会显示所有已注册的数据源实例,点击对应名称即可查看各自连接池的活跃连接数、等待线程数等指标。如果发现某个从库长期空闲或主库连接一直占满,说明路由规则需要调整。

最后是事务问题。多数据源切换最大的坑在于事务管理器。默认的DataSourceTransactionManager绑定的是固定数据源,如果事务开启后动态数据源切换了key,连接可能已经被固定,导致切换失效。单库事务可以继续使用@Transactional,但对于跨库操作,需要引入分布式事务方案,比如Seata或者基于消息的最终一致性,否则不要轻易在一个事务里同时操作主库和从库。

此外,AbstractRoutingDataSource切换的时机是在获取连接时生效。如果事务在Service方法上开启,而数据源切面在事务切面之后执行,那么事务管理器已经拿到连接,后面的切换不会改变当前事务使用的连接。因此注解切换的切面顺序建议设置在事务切面之前,或者在事务方法外完成切换。

五、完整结构回顾与调优建议

按照上面的步骤改造后,工程结构大致包含配置类、动态数据源类、上下文持有类、注解类和切面类。启动项目后,可以用一个只读接口标注@DataSource("slave"),验证返回的数据是否来自从库;再调用写入接口,确认主库产生变更。如果从库连接失败,检查targetDataSources是否注册完整,以及YAML里的URL是否使用了正确的转义。

性能调优方面,Druid的max-active不是越大越好,过多连接会消耗数据库端资源。建议先通过压测确定单库连接数,再决定两个库各自的上限。SQL映射文件尽量按数据源拆分成不同目录,可以减少SqlSessionFactoryBean扫描范围,启动速度也会更快。多数据源环境下日志里会打印多个连接池的初始化信息,如果看到大量DruidDataSource报错,优先检查驱动版本是否匹配。

通过这套方案,新手也能快速实现一个灵活的SpringBoot多数据源工程,后续无论是增加第三个数据源还是接入主从切换,都能在现有架构上平滑扩展。

SpringBoot多数据源Druid连接池Mybatis整合修改时间:2026-09-23 03:47:18

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0923/60741.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。