当前位置: 代码网 > it编程>编程语言>Java > SpringBoot实现整合多数据源的全攻略

SpringBoot实现整合多数据源的全攻略

2026年03月13日 Java 我要评论
在实际开发中,单数据源往往无法满足复杂的业务场景 —— 比如读写分离、分库分表、不同业务模块对接不同数据库等。springboot 作为主流的开发框架,提供了多种多数据源整合方

在实际开发中,单数据源往往无法满足复杂的业务场景 —— 比如读写分离、分库分表、不同业务模块对接不同数据库等。springboot 作为主流的开发框架,提供了多种多数据源整合方案,从简单的静态切换到灵活的动态路由,每种方案都有其适用场景。本文将从实际业务需求出发,拆解 springboot 中多数据源的核心实现方式,并附上可直接运行的代码示例。

一、多数据源核心场景与技术选型

先明确多数据源的常见使用场景,避免盲目选型:

  • 静态多数据源:不同业务模块固定对接不同数据库(如订单库、用户库),启动时加载,运行时不切换;
  • 动态切换数据源:运行时根据条件(如用户 id、业务标识)动态选择数据源(如读写分离、分库);
  • 分布式事务:多数据源操作需保证事务一致性(本文暂不展开,后续单独讲解)。

核心依赖(基于 springboot 2.7.x):

<dependencies>
    <!-- springboot核心 -->
    <dependency>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-web</artifactid>
    </dependency>
    <!-- 数据库连接 -->
    <dependency>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-jdbc</artifactid>
    </dependency>
    <dependency>
        <groupid>com.baomidou</groupid>
        <artifactid>mybatis-plus-boot-starter</artifactid>
        <version>3.5.3.1</version>
    </dependency>
    <!-- 数据库驱动(以mysql为例) -->
    <dependency>
        <groupid>mysql</groupid>
        <artifactid>mysql-connector-java</artifactid>
        <scope>runtime</scope>
    </dependency>
    <!-- 连接池 -->
    <dependency>
        <groupid>com.alibaba</groupid>
        <artifactid>druid-spring-boot-starter</artifactid>
        <version>1.2.16</version>
    </dependency>
    <!-- 测试 -->
    <dependency>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-test</artifactid>
        <scope>test</scope>
    </dependency>
</dependencies>

二、方案 1:静态多数据源(基于配置类分离)

适用场景

不同业务模块完全隔离,比如「用户模块」对接 user_db,「订单模块」对接 order_db,运行时无需切换数据源。

实现步骤

配置文件(application.yml)

spring:
  datasource:
    # 数据源1:用户库
    user:
      url: jdbc:mysql://localhost:3306/user_db?useunicode=true&characterencoding=utf8&usessl=false&servertimezone=asia/shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.driver
      type: com.alibaba.druid.pool.druiddatasource
    # 数据源2:订单库
    order:
      url: jdbc:mysql://localhost:3306/order_db?useunicode=true&characterencoding=utf8&usessl=false&servertimezone=asia/shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.driver
      type: com.alibaba.druid.pool.druiddatasource
# mybatis-plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.example.demo.entity
  configuration:
    map-underscore-to-camel-case: true

数据源配置类

分别配置两个数据源的 bean,指定不同的扫描路径:

用户数据源配置(userdatasourceconfig.java):

package com.example.demo.config;
import com.baomidou.mybatisplus.core.mybatisconfiguration;
import com.baomidou.mybatisplus.core.mybatisxmllanguagedriver;
import com.baomidou.mybatisplus.core.toolkit.stringpool;
import com.baomidou.mybatisplus.extension.plugins.mybatisplusinterceptor;
import com.baomidou.mybatisplus.extension.spring.mybatissqlsessionfactorybean;
import org.apache.ibatis.session.sqlsessionfactory;
import org.apache.ibatis.type.jdbctype;
import org.mybatis.spring.sqlsessiontemplate;
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 javax.sql.datasource;
/**
 * 用户库数据源配置
 * 扫描user模块的mapper
 */
@configuration
@mapperscan(basepackages = "com.example.demo.mapper.user", sqlsessiontemplateref = "usersqlsessiontemplate")
public class userdatasourceconfig {
    /**
     * 配置用户库数据源
     */
    @bean(name = "userdatasource")
    @configurationproperties(prefix = "spring.datasource.user")
    @primary // 主数据源(必须指定一个主数据源)
    public datasource userdatasource() {
        return datasourcebuilder.create().type(com.alibaba.druid.pool.druiddatasource.class).build();
    }
    /**
     * 配置用户库sqlsessionfactory
     */
    @bean(name = "usersqlsessionfactory")
    @primary
    public sqlsessionfactory usersqlsessionfactory(@qualifier("userdatasource") datasource datasource,
                                                   mybatisplusinterceptor interceptor) throws exception {
        mybatissqlsessionfactorybean sqlsessionfactory = new mybatissqlsessionfactorybean();
        sqlsessionfactory.setdatasource(datasource);
        // 配置mybatis-plus
        mybatisconfiguration configuration = new mybatisconfiguration();
        configuration.setdefaultscriptinglanguage(mybatisxmllanguagedriver.class);
        configuration.setjdbctypefornull(jdbctype.null);
        sqlsessionfactory.setconfiguration(configuration);
        // 分页插件(可选)
        sqlsessionfactory.setplugins(interceptor);
        // mapper文件路径
        sqlsessionfactory.setmapperlocations(new pathmatchingresourcepatternresolver()
                .getresources("classpath:mapper/user/*.xml"));
        return sqlsessionfactory.getobject();
    }
    /**
     * 配置用户库sqlsessiontemplate
     */
    @bean(name = "usersqlsessiontemplate")
    @primary
    public sqlsessiontemplate usersqlsessiontemplate(@qualifier("usersqlsessionfactory") sqlsessionfactory sqlsessionfactory) {
        return new sqlsessiontemplate(sqlsessionfactory);
    }
}

订单数据源配置(orderdatasourceconfig.java):

package com.example.demo.config;

import com.baomidou.mybatisplus.core.mybatisconfiguration;
import com.baomidou.mybatisplus.core.mybatisxmllanguagedriver;
import com.baomidou.mybatisplus.extension.plugins.mybatisplusinterceptor;
import com.baomidou.mybatisplus.extension.spring.mybatissqlsessionfactorybean;
import org.apache.ibatis.session.sqlsessionfactory;
import org.apache.ibatis.type.jdbctype;
import org.mybatis.spring.sqlsessiontemplate;
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 javax.sql.datasource;

/**
 * 订单库数据源配置
 * 扫描order模块的mapper
 */
@configuration
@mapperscan(basepackages = "com.example.demo.mapper.order", sqlsessiontemplateref = "ordersqlsessiontemplate")
public class orderdatasourceconfig {

    @bean(name = "orderdatasource")
    @configurationproperties(prefix = "spring.datasource.order")
    public datasource orderdatasource() {
        return datasourcebuilder.create().type(com.alibaba.druid.pool.druiddatasource.class).build();
    }

    @bean(name = "ordersqlsessionfactory")
    public sqlsessionfactory ordersqlsessionfactory(@qualifier("orderdatasource") datasource datasource,
                                                   mybatisplusinterceptor interceptor) throws exception {
        mybatissqlsessionfactorybean sqlsessionfactory = new mybatissqlsessionfactorybean();
        sqlsessionfactory.setdatasource(datasource);
        mybatisconfiguration configuration = new mybatisconfiguration();
        configuration.setdefaultscriptinglanguage(mybatisxmllanguagedriver.class);
        configuration.setjdbctypefornull(jdbctype.null);
        sqlsessionfactory.setconfiguration(configuration);
        sqlsessionfactory.setplugins(interceptor);
        sqlsessionfactory.setmapperlocations(new pathmatchingresourcepatternresolver()
                .getresources("classpath:mapper/order/*.xml"));
        return sqlsessionfactory.getobject();
    }

    @bean(name = "ordersqlsessiontemplate")
    public sqlsessiontemplate ordersqlsessiontemplate(@qualifier("ordersqlsessionfactory") sqlsessionfactory sqlsessionfactory) {
        return new sqlsessiontemplate(sqlsessionfactory);
    }
}

业务代码示例

用户 mapper(usermapper.java):放在com.example.demo.mapper.user包下

package com.example.demo.mapper.user;

import com.baomidou.mybatisplus.core.mapper.basemapper;
import com.example.demo.entity.user;
import org.apache.ibatis.annotations.mapper;

@mapper
public interface usermapper extends basemapper<user> {
}

订单 mapper(ordermapper.java):放在com.example.demo.mapper.order包下

package com.example.demo.mapper.order;

import com.baomidou.mybatisplus.core.mapper.basemapper;
import com.example.demo.entity.order;
import org.apache.ibatis.annotations.mapper;

@mapper
public interface ordermapper extends basemapper<order> {
}

业务层调用:

package com.example.demo.service.impl;

import com.example.demo.entity.order;
import com.example.demo.entity.user;
import com.example.demo.mapper.order.ordermapper;
import com.example.demo.mapper.user.usermapper;
import com.example.demo.service.datasourceservice;
import org.springframework.stereotype.service;
import javax.annotation.resource;

@service
public class datasourceserviceimpl implements datasourceservice {

    @resource
    private usermapper usermapper;

    @resource
    private ordermapper ordermapper;

    @override
    public user getuserbyid(long id) {
        // 自动使用用户库数据源
        return usermapper.selectbyid(id);
    }

    @override
    public order getorderbyid(long id) {
        // 自动使用订单库数据源
        return ordermapper.selectbyid(id);
    }
}

优缺点

优点:配置简单、无侵入性、性能高,适合模块隔离的场景;

缺点:无法动态切换,新增数据源需新增配置类,灵活性低。

三、方案 2:动态切换数据源(基于 abstractroutingdatasource)

适用场景

需要根据业务逻辑动态切换数据源,比如「读写分离」(读库 / 写库切换)、「分库」(按用户 id 路由到不同库)。

核心思路:继承 spring 提供的abstractroutingdatasource,重写determinecurrentlookupkey方法,通过 threadlocal 存储当前线程的数据源标识,实现动态路由。

实现步骤

配置文件(application.yml)

spring:
  datasource:
    # 主库(写)
    master:
      url: jdbc:mysql://localhost:3306/master_db?useunicode=true&characterencoding=utf8&usessl=false&servertimezone=asia/shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.driver
    # 从库(读)
    slave:
      url: jdbc:mysql://localhost:3306/slave_db?useunicode=true&characterencoding=utf8&usessl=false&servertimezone=asia/shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.driver
    type: com.alibaba.druid.pool.druiddatasource

核心工具类

数据源上下文 holder(dynamicdatasourcecontextholder.java):

package com.example.demo.config.dynamic;

/**
 * 数据源上下文 holder,基于threadlocal存储当前线程的数据源标识
 */
public class dynamicdatasourcecontextholder {

    /**
     * 线程本地变量:存储当前线程使用的数据源标识
     */
    private static final threadlocal<string> context_holder = new threadlocal<>();

    /**
     * 设置数据源标识
     */
    public static void setdatasourcekey(string key) {
        context_holder.set(key);
    }

    /**
     * 获取数据源标识
     */
    public static string getdatasourcekey() {
        return context_holder.get();
    }

    /**
     * 清除数据源标识
     */
    public static void cleardatasourcekey() {
        context_holder.remove();
    }
}

动态数据源路由类(dynamicroutingdatasource.java):

package com.example.demo.config.dynamic;

import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource;

/**
 * 动态数据源路由:重写determinecurrentlookupkey方法,返回当前线程的数据源标识
 */
public class dynamicroutingdatasource extends abstractroutingdatasource {

    @override
    protected object determinecurrentlookupkey() {
        // 从threadlocal中获取当前线程的数据源标识
        return dynamicdatasourcecontextholder.getdatasourcekey();
    }
}

数据源配置类

package com.example.demo.config.dynamic;

import com.baomidou.mybatisplus.extension.plugins.mybatisplusinterceptor;
import com.baomidou.mybatisplus.extension.spring.mybatissqlsessionfactorybean;
import org.apache.ibatis.session.sqlsessionfactory;
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 javax.sql.datasource;
import java.util.hashmap;
import java.util.map;

/**
 * 动态数据源配置
 */
@configuration
@mapperscan(basepackages = "com.example.demo.mapper", sqlsessionfactoryref = "dynamicsqlsessionfactory")
public class dynamicdatasourceconfig {

    /**
     * 配置主库数据源
     */
    @bean(name = "masterdatasource")
    @configurationproperties(prefix = "spring.datasource.master")
    public datasource masterdatasource() {
        return datasourcebuilder.create().type(com.alibaba.druid.pool.druiddatasource.class).build();
    }

    /**
     * 配置从库数据源
     */
    @bean(name = "slavedatasource")
    @configurationproperties(prefix = "spring.datasource.slave")
    public datasource slavedatasource() {
        return datasourcebuilder.create().type(com.alibaba.druid.pool.druiddatasource.class).build();
    }

    /**
     * 配置动态数据源(核心)
     */
    @bean(name = "dynamicdatasource")
    @primary
    public datasource dynamicdatasource(@qualifier("masterdatasource") datasource masterdatasource,
                                       @qualifier("slavedatasource") datasource slavedatasource) {
        dynamicroutingdatasource dynamicroutingdatasource = new dynamicroutingdatasource();
        // 1. 配置默认数据源(主库)
        dynamicroutingdatasource.setdefaulttargetdatasource(masterdatasource);
        // 2. 配置所有数据源(key为数据源标识,value为数据源)
        map<object, object> datasourcemap = new hashmap<>();
        datasourcemap.put("master", masterdatasource);
        datasourcemap.put("slave", slavedatasource);
        dynamicroutingdatasource.settargetdatasources(datasourcemap);
        return dynamicroutingdatasource;
    }

    /**
     * 配置sqlsessionfactory
     */
    @bean(name = "dynamicsqlsessionfactory")
    public sqlsessionfactory dynamicsqlsessionfactory(@qualifier("dynamicdatasource") datasource datasource,
                                                     mybatisplusinterceptor interceptor) throws exception {
        mybatissqlsessionfactorybean sqlsessionfactory = new mybatissqlsessionfactorybean();
        sqlsessionfactory.setdatasource(datasource);
        sqlsessionfactory.setmapperlocations(new pathmatchingresourcepatternresolver()
                .getresources("classpath:mapper/**/*.xml"));
        sqlsessionfactory.setplugins(interceptor);
        return sqlsessionfactory.getobject();
    }
}

自定义注解 + aop 实现自动切换

数据源注解(datasource.java):

package com.example.demo.annotation;

import java.lang.annotation.*;

/**
 * 自定义数据源注解:标注在方法/类上,指定使用的数据源
 */
@target({elementtype.method, elementtype.type})
@retention(retentionpolicy.runtime)
@documented
public @interface datasource {
    /**
     * 数据源标识,对应dynamicdatasource中的key
     */
    string value() default "master";
}

aop 切面(datasourceaspect.java):

package com.example.demo.aspect;

import com.example.demo.annotation.datasource;
import com.example.demo.config.dynamic.dynamicdatasourcecontextholder;
import org.aspectj.lang.proceedingjoinpoint;
import org.aspectj.lang.annotation.around;
import org.aspectj.lang.annotation.aspect;
import org.aspectj.lang.annotation.pointcut;
import org.aspectj.lang.reflect.methodsignature;
import org.springframework.core.annotation.order;
import org.springframework.stereotype.component;

import java.lang.reflect.method;

/**
 * 数据源切换切面:拦截@datasource注解,设置对应的数据源标识
 */
@aspect
@component
@order(-1) // 保证切面优先级高于事务
public class datasourceaspect {

    /**
     * 切入点:拦截所有标注@datasource的方法/类
     */
    @pointcut("@annotation(com.example.demo.annotation.datasource) || @within(com.example.demo.annotation.datasource)")
    public void datasourcepointcut() {}

    /**
     * 环绕通知:切换数据源
     */
    @around("datasourcepointcut()")
    public object around(proceedingjoinpoint point) throws throwable {
        // 1. 获取注解中的数据源标识
        string datasourcekey = getdatasourcekey(point);
        // 2. 设置数据源标识到threadlocal
        dynamicdatasourcecontextholder.setdatasourcekey(datasourcekey);
        try {
            // 3. 执行目标方法
            return point.proceed();
        } finally {
            // 4. 清除数据源标识,避免线程复用导致的问题
            dynamicdatasourcecontextholder.cleardatasourcekey();
        }
    }

    /**
     * 获取方法/类上的数据源标识
     */
    private string getdatasourcekey(proceedingjoinpoint point) {
        methodsignature signature = (methodsignature) point.getsignature();
        method method = signature.getmethod();
        // 优先获取方法上的注解
        datasource methodannotation = method.getannotation(datasource.class);
        if (methodannotation != null) {
            return methodannotation.value();
        }
        // 方法上没有则获取类上的注解
        class<?> targetclass = point.gettarget().getclass();
        datasource classannotation = targetclass.getannotation(datasource.class);
        if (classannotation != null) {
            return classannotation.value();
        }
        // 默认使用主库
        return "master";
    }
}

业务代码示例

package com.example.demo.service.impl;

import com.example.demo.annotation.datasource;
import com.example.demo.entity.user;
import com.example.demo.mapper.usermapper;
import com.example.demo.service.userservice;
import org.springframework.stereotype.service;
import javax.annotation.resource;

@service
public class userserviceimpl implements userservice {

    @resource
    private usermapper usermapper;

    /**
     * 写操作:使用主库
     */
    @override
    @datasource("master")
    public void saveuser(user user) {
        usermapper.insert(user);
    }

    /**
     * 读操作:使用从库
     */
    @override
    @datasource("slave")
    public user getuserbyid(long id) {
        return usermapper.selectbyid(id);
    }
}

优缺点

优点:灵活性高,支持运行时动态切换,可扩展至 n 个数据源;

缺点:需要手动管理 threadlocal,切面优先级需高于事务,否则切换失效。

四、方案 3:基于 mybatis-plus 插件(dynamic-datasource-spring-boot-starter)

适用场景

追求极简配置,快速实现多数据源切换(推荐生产环境使用)。

dynamic-datasource-spring-boot-starter 是 mybatis-plus 团队提供的多数据源插件,封装了 abstractroutingdatasource 的底层逻辑,支持注解、spel 表达式、分布式场景等,无需手动编写切面和路由类。

实现步骤

引入依赖

<!-- 动态数据源插件(核心) -->
<dependency>
    <groupid>com.baomidou</groupid>
    <artifactid>dynamic-datasource-spring-boot-starter</artifactid>
    <version>3.6.1</version>
</dependency>

配置文件(application.yml)

spring:
  # 动态数据源配置
  dynamic:
    datasource:
      # 主库(默认)
      master:
        url: jdbc:mysql://localhost:3306/master_db?useunicode=true&characterencoding=utf8&usessl=false&servertimezone=asia/shanghai
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.driver
      # 从库1
      slave1:
        url: jdbc:mysql://localhost:3306/slave1_db?useunicode=true&characterencoding=utf8&usessl=false&servertimezone=asia/shanghai
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.driver
      # 从库2
      slave2:
        url: jdbc:mysql://localhost:3306/slave2_db?useunicode=true&characterencoding=utf8&usessl=false&servertimezone=asia/shanghai
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.driver
    # 配置默认数据源
    primary: master
    # 配置druid连接池
    type: com.alibaba.druid.pool.druiddatasource

业务代码示例

直接使用插件提供的@ds注解切换数据源:

package com.example.demo.service.impl;

import com.baomidou.dynamic.datasource.annotation.ds;
import com.example.demo.entity.user;
import com.example.demo.mapper.usermapper;
import com.example.demo.service.userservice;
import org.springframework.stereotype.service;
import javax.annotation.resource;

@service
public class userserviceimpl implements userservice {

    @resource
    private usermapper usermapper;

    /**
     * 默认使用主库(可不加注解)
     */
    @override
    public void saveuser(user user) {
        usermapper.insert(user);
    }

    /**
     * 使用从库1
     */
    @override
    @ds("slave1")
    public user getuserbyid(long id) {
        return usermapper.selectbyid(id);
    }

    /**
     * 使用从库2
     */
    @override
    @ds("slave2")
    public user getuserbyphone(string phone) {
        return usermapper.selectone(new lambdaquerywrapper<user>().eq(user::getphone, phone));
    }
}

进阶用法:spel 表达式动态路由

支持根据方法参数动态选择数据源(比如按用户 id 取模分库):

/**
 * 根据用户id取模,路由到不同从库
 */
@override
@ds("#{id % 2 == 0 ? 'slave1' : 'slave2'}")
public user getuserbyid(long id) {
    return usermapper.selectbyid(id);
}

优缺点

优点:零配置成本、功能丰富(支持读写分离、负载均衡、分布式锁)、官方维护;

缺点:依赖第三方插件,深度定制化场景需二次开发。

五、方案对比与选型建议

方案优点缺点适用场景
静态多数据源(配置类)配置简单、性能高、无侵入无法动态切换、扩展性差模块隔离的固定多数据源场景
自定义动态数据源高度自定义、灵活性高需手动编写代码、易出问题特殊定制化的动态切换场景
dynamic-datasource极简配置、功能丰富、稳定性高依赖第三方插件大部分生产环境(推荐)

六、注意事项

  • 事务问题:动态数据源切换需保证切面优先级高于事务(@order (-1)),否则事务内切换数据源失效;
  • 线程安全:threadlocal 需在方法执行完毕后清除,避免线程池复用导致数据源串用;
  • 连接池配置:多数据源场景下需合理配置连接池大小,避免连接耗尽;
  • 读写分离:从库建议设置为只读,避免写入数据导致主从同步异常。

总结

springboot 整合多数据源的核心思路是「数据源路由」,不同方案只是封装程度不同。对于大部分开发者来说,优先选择 dynamic-datasource 插件,兼顾效率和稳定性;特殊定制化场景可基于 abstractroutingdatasource 手动实现;模块隔离场景则用静态多数据源即可。

本文所有代码均可直接复制运行,建议根据实际业务场景调整数据源配置和切换逻辑。

以上就是springboot实现整合多数据源的全攻略的详细内容,更多关于springboot整合多数据源的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com