在 Spring Boot 中配置多数据源是一个非常常见的需求,主要用于以下场景:

  • 读写分离:一个主数据库(Master)负责写操作,一个或多个从数据库(Slave)负责读操作,以提高性能和可用性。
  • 业务拆分:不同的业务模块使用不同的数据库(例如,用户库、订单库、商品库)。
  • 连接异构数据库:同时连接 MySQL、PostgreSQL 等不同类型的数据库。

下面我将详细介绍两种主流的实现方式:

  1. 静态方式(推荐用于业务隔离场景):通过包路径区分不同的数据源,配置简单,结构清晰。
  2. 动态方式(推荐用于读写分离场景):使用 AOP 和自定义注解,在方法级别动态切换数据源,更灵活。

方案一:静态方式(按包路径隔离)

这种方式的核心思想是为每个数据源创建一套独立的配置(DataSource, SqlSessionFactory, TransactionManager),并使用 @MapperScan 注解扫描不同包路径下的 Mapper 接口,将它们绑定到对应的数据源上。

1. 添加依赖 (pom.xml)

确保有以下依赖。通常 Spring Boot Starter 会包含大部分。

<dependencies><!-- Spring Boot Web Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- MyBatis-Plus Starter --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version> <!-- 请使用较新版本 --></dependency><!-- MySQL Driver --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- Connection Pool (HikariCP is default) --><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId></dependency>
</dependencies>
2. 配置文件 (application.yml)

为不同的数据源定义各自的连接信息,并用不同的前缀区分。

spring:datasource:# 主数据源配置 (master)master:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/db_master?serverTimezone=UTCusername: rootpassword: your_passwordtype: com.zaxxer.hikari.HikariDataSource # 指定连接池类型# 从数据源配置 (slave)slave:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3307/db_slave?serverTimezone=UTCusername: rootpassword: your_passwordtype: com.zaxxer.hikari.HikariDataSource
3. 创建数据源配置类

为每个数据源创建一个 Java 配置类。

主数据源配置 (MasterDataSourceConfig.java)

package com.example.config.datasource;import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
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.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;@Configuration
// 扫描 Master 库的 Mapper 接口
@MapperScan(basePackages = "com.example.mapper.master", sqlSessionTemplateRef = "masterSqlSessionTemplate")
public class MasterDataSourceConfig {@Bean(name = "masterDataSource")@ConfigurationProperties(prefix = "spring.datasource.master")@Primary // 标记为主数据源public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean(name = "masterSqlSessionFactory")@Primarypublic SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();bean.setDataSource(dataSource);// 如果有 XML 文件,指定位置// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));return bean.getObject();}@Bean(name = "masterTransactionManager")@Primarypublic DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Bean(name = "masterSqlSessionTemplate")@Primarypublic SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}
}

从数据源配置 (SlaveDataSourceConfig.java)

package com.example.config.datasource;import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
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.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;@Configuration
// 扫描 Slave 库的 Mapper 接口
@MapperScan(basePackages = "com.example.mapper.slave", sqlSessionTemplateRef = "slaveSqlSessionTemplate")
public class SlaveDataSourceConfig {@Bean(name = "slaveDataSource")@ConfigurationProperties(prefix = "spring.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}@Bean(name = "slaveSqlSessionFactory")public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();bean.setDataSource(dataSource);// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/slave/*.xml"));return bean.getObject();}@Bean(name = "slaveTransactionManager")public DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Bean(name = "slaveSqlSessionTemplate")public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}
}
4. 创建 Mapper 接口

将不同数据源的 Mapper 接口放到各自的包下。

  • com.example.mapper.master -> UserMasterMapper.java
  • com.example.mapper.slave -> OrderSlaveMapper.java
5. 使用

现在你可以在 Service 中直接注入并使用对应的 Mapper,Spring 会自动为它们关联正确的数据源。

@Service
public class MyService {@Autowiredprivate UserMasterMapper userMasterMapper; // 操作 master 库@Autowiredprivate OrderSlaveMapper orderSlaveMapper; // 操作 slave 库public void doSomething() {// ...userMasterMapper.insert(someUser); // 写入主库Order order = orderSlaveMapper.selectById(1); // 从从库读取}
}

优点:配置隔离,结构非常清晰,不会混淆。
缺点:如果一个 Service 方法需要同时操作两个库,代码会稍微复杂,且默认的 @Transactional 不能跨数据源生效。


方案二:动态方式(AOP + 自定义注解)

这种方式更灵活,适用于读写分离等需要在同一个 Service 中切换数据源的场景。

1. 配置文件 (application.yml)

与方案一相同。

2. 创建自定义注解

创建一个注解,用于标记方法应该使用哪个数据源。

package com.example.config.dynamic;import java.lang.annotation.*;@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String value() default "master"; // 默认使用 master 数据源
}
3. 创建数据源上下文持有者

使用 ThreadLocal 来存储当前线程需要使用的数据源 Key。

package com.example.config.dynamic;public class DataSourceContextHolder {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();}
}
4. 创建动态数据源类

继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey 方法,从 DataSourceContextHolder 获取当前数据源 Key。

package com.example.config.dynamic;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSourceKey();}
}
5. 创建AOP切面

创建一个切面,拦截 @DataSource 注解,在方法执行前设置数据源 Key,在方法执行后清除它。

package com.example.config.dynamic;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;@Aspect
@Component
@Order(1) // 保证该AOP在@Transactional之前执行
public class DataSourceAspect {@Pointcut("@annotation(com.example.config.dynamic.DataSource)")public void dsPointCut() {}@Around("dsPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();DataSource dataSource = method.getAnnotation(DataSource.class);// 设置数据源if (dataSource != null) {DataSourceContextHolder.setDataSourceKey(dataSource.value());}try {return point.proceed();} finally {// 清除数据源,防止内存泄漏DataSourceContextHolder.clearDataSourceKey();}}
}
6. 整合数据源配置

创建一个统一的配置类来管理所有数据源。

package com.example.config;import com.example.config.dynamic.DynamicDataSource;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;@Configuration
public class DynamicDataSourceConfig {@Bean(name = "masterDataSource")@ConfigurationProperties(prefix = "spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean(name = "slaveDataSource")@ConfigurationProperties(prefix = "spring.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}@Bean@Primary // 必须!将动态数据源设置为主数据源public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("master", masterDataSource);targetDataSources.put("slave", slaveDataSource);DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(masterDataSource); // 设置默认数据源return dynamicDataSource;}
}

注意:这种方式下,SqlSessionFactoryTransactionManager 只需要配置一个,并让它们使用这个 @PrimaryDynamicDataSource 即可。Spring Boot 会自动配置好。

7. 使用

在 Service 方法上添加 @DataSource 注解来切换数据源。

@Service
public class ProductService {@Autowiredprivate ProductMapper productMapper;// 默认不加注解,使用 master 数据源(因为我们配置了默认值)@Transactional // 事务仍然有效public void addProduct(Product product) {productMapper.insert(product);}// 显式指定使用 slave 数据源@DataSource("slave")public Product getProductById(Integer id) {return productMapper.selectById(id);}
}

重要提醒:关于事务

  • 单数据源事务:在以上两种方案中,只要 DataSourceTransactionManagerSqlSessionFactory 绑定的是同一个 DataSource@Transactional 注解就能正常工作。在动态方案中,事务管理器绑定的是 DynamicDataSource,它能确保事务在当前线程选择的数据源上生效。
  • 跨数据源事务(分布式事务):标准的 @Transactional 无法管理跨多个数据源的事务。如果你需要在同一个方法中对 masterslave 都进行写操作,并保证它们的原子性,你需要引入 JTA(Java Transaction API)事务管理器,例如 AtomikosNarayana。这会增加配置的复杂度。

总结

  • 如果你的业务模块和数据库绑定关系固定,方案一(静态方式) 更简单、更直观。
  • 如果你需要实现读写分离,或者在业务逻辑中频繁切换数据源,方案二(动态方式) 提供了更高的灵活性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/88181.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88181.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/88181.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FAAC 在海思平台使用得到aac实时音频流

FAAC 在海思平台使用得到aac实时音频流 使用 FAAC将音频 pcm转为 aac 主要参见这篇博客 FAAC 在君正平台使用得到aac实时音频流_君正 x2600 音频-CSDN博客

javascript函数参数类似python函数参数星号*解耦数组

序言通常情况下&#xff0c;我们很可能不清楚参数有多少&#xff0c;这个时候用的都是数组。但是使用数组和单个元素&#xff0c;从内心情感来说&#xff0c;它们是两种维度&#xff0c;为了让参数成为一个数组&#xff0c;把单个输入的参数强加一个数组的外壳&#xff0c;并不…

C语言基础(1)

1.编译器的选择 我们的c语言是一门&#xff0c;我们写的c语言代码是文本文件(存放在.c为后缀的文件中)&#xff0c;文本文件本身无法被执行&#xff0c;必须通过编译器的编译和链接器的链接&#xff0c;生成可执行的二进制文件&#xff0c;才能够被执行注意&#xff1a; 每个源…

Rust赋能美团云原生DevOps实践

Rust 云原生 DevOps 实践 在云原生环境中,Rust 的高性能与安全性使其成为构建微服务和基础设施工具的理想选择。Docker 作为容器化标准工具,结合 Rust 的跨平台特性,可高效实现持续集成与部署(CI/CD)。 构建优化的 Rust Docker 镜像 多阶段构建是 Rust 项目容器化的关键…

计算机网络实验——配置ACL

ACL基础一、实验目的1. 配置H3C路由器基本ACL。二、实验要求1. 熟练掌握网络配置能力。2. 熟练掌握ACL基本配置。三、实验步骤&#xff08;1&#xff09;使用reset saved-configuration命令和reboot命令&#xff0c;重置路由器原有配置&#xff0c;如图1所示。图 1&#xff08;…

在本地部署mcp服务器实现自然语言操作mysql数据库,轻松实现数据表的增~ 删~ 改~ 查~

1.将写好的mcp_server代码放在本地任意盘&#xff01; import asyncio import logging import os import sys from mysql.connector import connect, Error from mcp.server import Server from mcp.types import Resource, Tool, TextContent from pydantic import AnyUrl# Co…

2025快手创作者中心发布视频python实现

难度还行&#xff0c;只有一个__NS_sig3加密&#xff0c;流程麻烦点cookies_list cookie.split("; ")cookie_dict {}# 遍历每个 Cookie&#xff0c;根据等号将键值对拆分并添加到字典中for cookie in cookies_list:key_value cookie.split("")if len(ke…

Android 组件内核

文章目录什么是binder1. 什么是Binder&#xff1f;2. Binder架构组成3. 工作原理与通信流程1&#xff09;服务注册2&#xff09;服务查询3&#xff09;通信过程4&#xff09;核心数据结构4. 关键技术点5. 常见面试考点1&#xff09;Binder与传统IPC&#xff08;Socket、管道、共…

java类加载机制:Tomcat的类加载机制

Tomcat类加载机制深度解析&#xff1a;打破双亲委派的Web容器实现 Tomcat作为Java Web容器&#xff0c;其类加载机制为满足Web应用的隔离性、热部署和兼容性需求&#xff0c;对标准Java类加载机制进行了定制化扩展&#xff0c;核心是打破双亲委派模型并引入多层级类加载器。以下…

【PTA数据结构 | C语言版】从顺序表 list 中删除第 i 个元素

本专栏持续输出数据结构题目集&#xff0c;欢迎订阅。 文章目录题目代码题目 请编写程序&#xff0c;将 n 个整数存入顺序表&#xff0c;对任一指定的第 i 个位置&#xff0c;将这个位置上的元素从顺序表中删除。注意&#xff1a;i 代表位序&#xff0c;从 1 开始&#xff0c;…

VS2022 C++ EasyX库 扫雷游戏项目开发:打造经典游戏的详细之旅

老样子&#xff0c;先上效果 视频演示 C经典扫雷-介绍一、引言 在这篇博客中&#xff0c;我将详细介绍扫雷游戏项目的开发过程。扫雷作为一款经典的游戏&#xff0c;其规则简单但富有挑战性。通过开发这个项目&#xff0c;我不仅加深了对 C 编程的理解&#xff0c;还提升了自己…

Go语言网络游戏服务器模块化编程

本文以使用origin框架&#xff08;一款使用Go语言写的开源游戏服务器框架&#xff09;为例进行说明&#xff0c;当然也可以使用其它的框架或者自己写。 在框架中PBProcessor用来处理Protobuf消息&#xff0c;在使用之前&#xff0c;需要使用Register函数注册网络消息&#xff…

【机器人】Aether 多任务世界模型 | 4D动态重建 | 视频预测 | 视觉规划

Aether 是一个的世界模型&#xff0c;整合几何重建与生成建模的统一框架&#xff0c;实现类人空间推理能力。 来自ICCV 2025&#xff0c;该框架具有三大核心功能&#xff1a; (1) 4D动态重建&#xff0c;(2) 动作条件视频预测&#xff0c; (3) 目标条件视觉规划。 代码地址&…

MiniMind:3小时训练26MB微型语言模型,开源项目助力AI初学者快速入门

开发&#xff5c;界面&#xff5c;引擎&#xff5c;交付&#xff5c;副驾——重写全栈法则&#xff1a;AI原生的倍速造应用流来自全栈程序员 nine 的探索与实践&#xff0c;持续迭代中。 欢迎关注评论私信交流~ 在大型语言模型(LLaMA、GPT等)日益流行的今天&#xff0c;一个名为…

相机Camera日志实例分析之五:相机Camx【萌拍闪光灯后置拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…

[2-02-02].第03节:环境搭建 - Win10搭建ES集群环境

ElasticSearch学习大纲 基于ElasticSearch7.8版本 一、ElasticStack下载&#xff1a; 1.Elasticsearch 的官方地址 2.Elasticsearch 下载地址&#xff1a; 二、集群搭建: 第1步&#xff1a;创建es目录&#xff1a; 1.创建 elasticsearch-cluster 文件夹&#xff0c;在内部…

操作系统核心技术剖析:从Android驱动模型到鸿蒙微内核的国产化实践

目录 一、移动端操作系统技术细节 1. Android 内核版本 核心模块 驱动架构 国内定制案例 2. iOS XNU内核关键模块 安全机制 3. HarmonyOS 多内核架构 驱动隔离 二、PC端操作系统技术细节 1. Windows NT内核 模块分层 驱动模型 国内适配 2. macOS&#xff08;X…

整合Spring、Spring MVC与MyBatis:构建高效Java Web应用

本文将详细讲解如何整合Spring、Spring MVC和MyBatis&#xff08;SSM框架&#xff09;&#xff0c;通过一个人员信息查询案例展示完整开发流程。所有代码基于提供的文件实现。一、项目结构src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── qcb…

视频插帧技术:从流畅观影到AI创作的革命

一、起源&#xff1a;为什么需要视频插帧&#xff1f; 视频的本质是连续播放的静态帧序列&#xff0c;帧率&#xff08;FPS&#xff09; 决定了流畅度。早期电影受限于拍摄技术和存储成本&#xff0c;普遍采用24FPS&#xff0c;而现代显示设备&#xff08;如120Hz屏幕&#xf…

【一起来学AI大模型】PyTorch 实战示例:使用 BatchNorm 处理张量(Tensor)

PyTorch 实战示例 演示如何在神经网络中使用 BatchNorm 处理张量&#xff08;Tensor&#xff09;&#xff0c;涵盖关键实现细节和常见陷阱。示例包含数据准备、模型构建、训练/推理模式切换及结果分析。示例场景&#xff1a;在 CIFAR-10 数据集上实现带 BatchNorm 的 CNNimport…