当前位置: 首页 > news >正文

跨数据源操作

在MyBatis-Plus中实现跨数据源查询,最简便的方法是使用官方推荐的dynamic-datasource-spring-boot-starter组件。它允许你通过一个简单的@DS注解来切换数据源。下面是一个详细的配置和使用指南。后面也会介绍下DynamicDataSourceContextHolder进行数据源切换。

主要步骤:

  1. 添加依赖, 引入dynamic-datasource-spring-boot-starter
  2. 配置数据源,在application.yml中配置masterbusiz数据源,并设置默认项
  3. 使用注解 在方法或类上使用@DS("busiz")注解切换数据源
  4. 注意事项 注意注解生效的优先级和事务相关的问题

详细步骤

1. 添加Maven依赖

在你的pom.xml文件中添加以下依赖:

<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.6.1</version> <!-- 建议使用最新版本 -->
</dependency>

请确保版本与你的MyBatis-Plus版本兼容 。

配置多数据源
application.yml配置文件中,配置你的master(默认)和busiz数据源。

spring:datasource:dynamic:primary: master # 设置 master 为你的默认数据源 strict: false # 设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源.datasource:master:url: jdbc:mysql://localhost:3306/master_db?useUnicode=true&characterEncoding=utf8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverbusiz:url: jdbc:mysql://localhost:3306/busiz_db?useUnicode=true&characterEncoding=utf8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver

使用@DS注解切换数据源

在你需要查询busiz数据源的Service实现类或方法上使用@DS("busiz")注解。

注解在类上:该类所有方法默认使用busiz数据源。

@Service
@DS("busiz") // 整个类默认使用busiz数据源
public class UserServiceImpl implements UserService {// ... class body
}

注解在方法上:仅该方法使用busiz数据源。方法上的注解优先级高于类上的注解 。

@Service
public class UserServiceImpl implements UserService {@Override@DS("busiz") // 此方法使用busiz数据源public List<User> selectFromBusiz() {// 你的查询逻辑return userMapper.selectList(null);}// 没有注解,使用默认的master数据源public List<User> selectFromMaster() {// 你的查询逻辑return userMapper.selectList(null);}
}

重要注意事项

事务管理器问题: 在同一个Service类中,A方法(无论有无@DS注解)调用B方法(有@DS注解),B方法的注解可能不生效。这通常是因为Spring AOP代理的机制。解决方法是将需要切换数据源的方法放到另一个Service类中调用 。

事务与数据源切换: 避免在需要切换数据源的方法上使用@Transactional注解。如果必须使用事务,需要在@Transactional注解中显式指定事务管理器,例如 @Transactional(rollbackFor = Exception.class, transactionManager = “dynamicDataSourceTransactionManager”) 。但更简单的做法是将操作不同数据源的事务拆分开。

数据源名称: 建议数据源名称不要包含下划线,以免引起无法切换的问题 。

注解位置: 官方强烈建议将@DS注解加在Service的实现类上,而不是Mapper接口上,以保证事务的正常运作 。

问题

问题1

A方法(无论有无@DS注解)调用B方法(有@DS注解),B方法的注解可能不生效。有时候可能会数据源切换不过去。
答:在同一个Service类中,A方法调用B方法时,B方法的@DS注解确实可能不生效,导致数据源切换失败。

问题根源
这个问题源于 Spring 的AOP(面向切面编程)机制的工作原理:

@DS注解本质上是通过Spring AOP实现的,Spring AOP默认使用动态代理来增强方法,在同一个类内部的方法调用,不会经过代理对象,而是直接调用目标方法,因此AOP拦截器无法介入,@DS注解也就不会生效。

具体示例

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;// A方法:没有@DS注解,应该使用默认数据源(master)public void methodA() {System.out.println("methodA - 应该使用默认master数据源");// 这里调用B方法,期望使用busiz数据源methodB();  // ❌ 实际:B方法的@DS注解不会生效,仍然使用master数据源// 其他逻辑...}// B方法:有@DS注解,期望使用busiz数据源@DS("busiz")public List<User> methodB() {System.out.println("methodB - 期望使用busiz数据源");return userMapper.selectList(null);  // ❌ 实际:仍然使用busiz数据源}
}

解决方案
方案1: 将方法拆分到不同的Service类中(推荐)

// Service1
@Service
public class ServiceA {@Autowiredprivate ServiceB serviceB;public void methodA() {System.out.println("methodA - 使用默认master数据源");// 通过注入的ServiceB调用,会经过代理,@DS注解生效serviceB.methodB();  // 会切换到busiz数据源}
}// Service2  
@Service
public class ServiceB {@Autowiredprivate UserMapper userMapper;@DS("busiz")public List<User> methodB() {System.out.println("methodB - 使用busiz数据源");return userMapper.selectList(null);  }
}

方案2: 从ApplicationContext中获取代理对象

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate ApplicationContext applicationContext;@Autowiredprivate UserMapper userMapper;public void methodA() {System.out.println("methodA - 使用默认master数据源");// 从Spring容器中获取代理对象来调用UserService proxy = applicationContext.getBean(UserService.class);proxy.methodB();  // ✅ 正确:@DS注解生效}@DS("busiz")public List<User> methodB() {System.out.println("methodB - 使用busiz数据源");return userMapper.selectList(null);}
}

优先使用方案1(方法拆分到不同Service),这是最清晰、最可靠的方式

在设计Service层时,将有不同数据源需求的方法自然地分布到不同的Service类中

如果确实需要在同一个Service中调用,考虑使用方案2

问题2

那在mapper层也可以用@DS注解吗
官方强烈不建议在Mapper层使用@DS注解,这可能会导致不可预期的问题。

为什么不建议在Mapper层使用

  1. 事务管理问题
// 不推荐的写法 - 在Mapper上使用@DS
@DS("busiz")  // 官方不推荐这样做
@Mapper
public interface UserMapper extends BaseMapper<User> {List<User> selectFromMaster();
}
@Service
public class UserService {@Transactional  // 事务注解public void businessMethod() {// 在事务中混合不同数据源的操作userMapper.selectFromBusiz();  // 使用busiz数据源userMapper.insert(user);     // 使用默认master数据源// ❌ 事务一致性无法保证}
}
  1. 数据源切换混乱
    多个Mapper使用不同数据源,在service层调用容易造成混乱

DynamicDataSourceContextHolder

代码含义解析

// 1. 将"sys"数据源标识压入栈中,切换到sys数据源
DynamicDataSourceContextHolder.push("sys");// 2. 在sys数据源上执行插入job的操作
jobService.insertJob(job);// 3. 从栈中弹出当前数据源标识,恢复到之前的数据源
DynamicDataSourceContextHolder.poll();

工作原理

数据源切换机制

  • push("sys"): 将"sys"数据源标识压入线程局部的栈中

  • poll(): 从栈中弹出当前数据源标识,恢复到前一个数据源

基于ThreadLocal实现,确保线程安全

执行流程

  • 保存当前数据源上下文

  • 切换到"sys"数据源

  • 执行数据库操作

  • 恢复原来的数据源

/必须清理数据源上下文,避免内存泄漏和数据源污染
DynamicDataSourceContextHolder.clear();

public void complexMultiDataSourceOperation() {List<User> busizUsers = null;List<User> masterUsers = null;try {// 切换到 busiz 数据源DynamicDataSourceContextHolder.push("busiz");busizUsers = userMapper.selectList(new LambdaQueryWrapper<User>().eq(User::getType, "GGG"));// 切换到master 数据源DynamicDataSourceContextHolder.push("master");masterUsers = userMapper.selectList(new LambdaQueryWrapper<User>().eq(User::getType, "CCC"));} finally {// 必须清理数据源上下文,避免内存泄漏和数据源污染DynamicDataSourceContextHolder.clear();}// 处理业务逻辑...}
}

批量操作多个数据源


@Service
public class BatchDataService {public void syncDataBetweenDataSources() {List<String> dataSources = Arrays.asList("ds1", "ds2", "ds3");for (String ds : dataSources) {try {DynamicDataSourceContextHolder.push(ds);// 从每个数据源读取数据List<User> users = userMapper.selectList(null);processUsers(users);} finally {DynamicDataSourceContextHolder.clear();}}}
}
http://www.dtcms.com/a/491691.html

相关文章:

  • 数据库圣经——第三章CRUD(一)
  • 信创学习小手册【第一天】
  • 动漫网站建设规划书模板制作网站主要包括哪些步骤
  • 基于Vue社区共享游泳馆预约系统n897q36e (程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • PyTorch Dataloader工作原理 之 default collate_fn操作
  • 2022年英语笔记
  • 东莞市的网站公司哪家好shopnc
  • 建站工具上市家居网站建设行业现状
  • 三、配置MapReduce
  • JavaScript基础提升【二】
  • 珠海网站建设培训班贵州省健康码二维码图片下载
  • orangepi lan口数据转发
  • 奥地利网站后缀wordpress扒站工具
  • 网站联系我们 怎么做地图泉州建设银行网站
  • ⸢ 柒-V⸥⤳ 可信纵深防御建设方案:技术保障 体系演进
  • 河北网站建设与推广外国人做家具的网站
  • 餐饮网站源码wordpress微信h5登录页面
  • 【Qt】9.信号和槽_信号和槽存在的意义
  • 力扣热题100道之3无重复字符的最长字串
  • java无法写入到系统盘下文件
  • java中final关键字的含义
  • wordpress封采集站ip微信营销平台有哪些
  • Golang相关知识总结
  • LeetCode算法日记 - Day 73: 最小路径和、地下城游戏
  • 设计案例网站网站自身seo优化怎么做
  • 手绘风格制图新选择:Excalidraw+cpolar让视觉化工作流无缝协作
  • apt 安装任意软件产生 `libc6:amd64 package post-installation` 异常问题
  • Product Hunt 每日热榜 | 2025-10-16
  • 2025最新如何申请Google Translate API免费版图文教程
  • 提供常州微信网站建设单页企业网站模板