在复杂业务系统中,多数据源切换已成为必备技能。本文将深入剖析三种主流实现方案,带你从入门到精通!

一、多数据源应用场景

  1. 读写分离:主库负责写操作,从库处理读请求

  2. 多租户系统:不同租户使用独立数据库

  3. 分库分表:业务数据按规则分散存储

  4. 多数据库类型:同时使用MySQL、Oracle等异构数据库

二、3种实现方案对比

方案实现复杂度侵入性维护成本适用场景
AbstractRoutingDataSource中等简单读写分离
多SqlSessionFactory异构数据库
dynamic-datasource复杂多数据源

三、方案一:AbstractRoutingDataSource

实现原理

通过继承Spring的AbstractRoutingDataSource类,动态路由数据源

实现步骤

1. 添加依赖
<dependencies><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>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency>
</dependencies>

 

2. 配置多数据源

# application.yml
spring:datasource:master:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/master_dbusername: rootpassword: root123slave:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/slave_dbusername: rootpassword: root123
3. 动态数据源配置类
@Configuration
public class DataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}@Beanpublic DataSource dynamicDataSource() {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;}
}
4. 自定义动态数据源
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSourceKey();}public static class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSourceKey(String key) {contextHolder.set(key);}public static String getDataSourceKey() {return contextHolder.get();}public static void clearDataSourceKey() {contextHolder.remove();}}
}
5. 自定义注解切换数据源
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String value() default "master";
}
6. AOP切面实现动态切换
@Aspect
@Component
public class DataSourceAspect {@Before("@annotation(dataSource)")public void beforeSwitchDataSource(JoinPoint point, DataSource dataSource) {String dataSourceKey = dataSource.value();DynamicDataSource.DataSourceContextHolder.setDataSourceKey(dataSourceKey);}@After("@annotation(dataSource)")public void afterSwitchDataSource(JoinPoint point, DataSource dataSource) {DynamicDataSource.DataSourceContextHolder.clearDataSourceKey();}
}
7. 业务层使用示例
@Service
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;// 使用主库@DataSource("master")public void createUser(User user) {String sql = "INSERT INTO users(name, email) VALUES(?, ?)";jdbcTemplate.update(sql, user.getName(), user.getEmail());}// 使用从库@DataSource("slave")public List<User> getAllUsers() {String sql = "SELECT * FROM users";return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));}
}

方案优缺点

优点

  • 纯Spring实现,无第三方依赖

  • 灵活控制数据源切换

缺点

  • 事务管理复杂

  • 需手动处理连接池

  • 切换逻辑侵入业务代码

四、方案二:多SqlSessionFactory

实现原理

为每个数据源创建独立的MyBatis SqlSessionFactory

实现步骤

1. 主数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.master", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Beanpublic 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();}@Beanpublic DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
2. 从数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.slave", sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}@Beanpublic 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();}@Beanpublic DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
3. 业务层使用
@Service
public class OrderService {// 注入主库Mapper@Autowired@Qualifier("masterOrderMapper")private OrderMapper masterOrderMapper;// 注入从库Mapper@Autowired@Qualifier("slaveOrderMapper")private OrderMapper slaveOrderMapper;@Transactional(transactionManager = "masterTransactionManager")public void createOrder(Order order) {masterOrderMapper.insert(order);}@Transactional(transactionManager = "slaveTransactionManager")public Order getOrder(Long id) {return slaveOrderMapper.selectById(id);}
}

方案优缺点

优点

  • 各数据源完全隔离

  • 事务管理清晰

  • 支持异构数据库

缺点

  • 配置复杂,冗余代码多

  • Mapper需按数据源分包

  • 动态切换不灵活

五、方案三:dynamic-datasource框架

框架优势

  • 零侵入:通过注解实现数据源切换

  • 功能丰富:支持读写分离、分库分表等

  • 简单易用:简化多数据源配置

实现步骤

1. 添加依赖
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.0</version>
</dependency>
2. 配置数据源
spring:datasource:dynamic:primary: master # 默认数据源strict: false   # 是否严格匹配数据源datasource:master:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/master_dbusername: rootpassword: root123slave1:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/slave_db1username: rootpassword: root123slave2:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/slave_db2username: rootpassword: root123oracle_db:driver-class-name: oracle.jdbc.OracleDriverurl: jdbc:oracle:thin:@localhost:1521:orclusername: systempassword: oracle123
3. 使用@DS注解切换数据源
@Service
public class ProductService {// 默认使用主库@Autowiredprivate JdbcTemplate jdbcTemplate;// 使用主库@DS("master")public void createProduct(Product product) {jdbcTemplate.update("INSERT INTO product(...) VALUES(...)");}// 随机使用从库@DS("slave")public Product getProduct(Long id) {return jdbcTemplate.queryForObject("SELECT * FROM product WHERE id = ?", new BeanPropertyRowMapper<>(Product.class), id);}// 指定特定从库@DS("slave1")public List<Product> getHotProducts() {return jdbcTemplate.query("SELECT * FROM product WHERE hot = 1", new BeanPropertyRowMapper<>(Product.class));}// 使用Oracle数据库@DS("oracle_db")public List<Category> getOracleCategories() {return jdbcTemplate.query("SELECT * FROM categories", new BeanPropertyRowMapper<>(Category.class));}
}
4. 高级功能:读写分离
spring:datasource:dynamic:primary: masterdatasource:master:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://master-host:3306/dbusername: rootpassword: root123slave_1:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://slave1-host:3306/dbusername: rootpassword: root123slave_2:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://slave2-host:3306/dbusername: rootpassword: root123strategy: # 读写分离配置load-balance: # 负载均衡策略slave: round_robin # 从库轮询策略
5. 事务管理
@DS("master")
@Transactional
public void placeOrder(Order order) {// 1. 扣减库存productService.reduceStock(order.getProductId(), order.getQuantity());// 2. 创建订单orderMapper.insert(order);// 3. 记录日志logService.logOrder(order);// 所有操作都在主库事务中执行
}

最佳实践技巧

  1. 数据源分组管理

    spring:datasource:dynamic:datasource:master_1: # 配置...master_2:# 配置...slave_1:# 配置...slave_2:# 配置...group:masters: master_1, master_2slaves: slave_1, slave_2
  2. 多租户数据源动态注册

    @Autowired
    private DynamicRoutingDataSource routingDataSource;public void addTenantDataSource(String tenantId, DataSourceProperty property) {DataSource dataSource = dataSourceCreator.createDataSource(property);routingDataSource.addDataSource(tenantId, dataSource);
    }
  3. 自定义负载均衡策略

    public class RandomStrategy implements LoadBalanceStrategy {@Overridepublic String determineDataSource(List<String> dataSourceNames) {Random random = new Random();return dataSourceNames.get(random.nextInt(dataSourceNames.size()));}
    }

六、性能优化建议

  1. 连接池配置优化

    spring:datasource:dynamic:datasource:master:# ...hikari:maximum-pool-size: 20minimum-idle: 5connection-timeout: 30000idle-timeout: 600000max-lifetime: 1800000
  2. 避免频繁切换数据源

    • 将同一数据源操作集中处理

    • 使用@DSTransactional管理跨库事务

  3. 监控数据源状态

    @RestController
    public class DataSourceMonitor {@Autowiredprivate DynamicRoutingDataSource routingDataSource;@GetMapping("/datasources")public Map<String, DataSource> listDataSources() {return routingDataSource.getDataSources();}@GetMapping("/datasources/stats")public Map<String, Object> getDataSourceStats() {Map<String, Object> stats = new HashMap<>();routingDataSource.getDataSources().forEach((key, ds) -> {if(ds instanceof HikariDataSource) {HikariDataSource hikari = (HikariDataSource) ds;HikariPoolMXBean pool = hikari.getHikariPoolMXBean();stats.put(key, Map.of("active", pool.getActiveConnections(),"idle", pool.getIdleConnections(),"total", pool.getTotalConnections()));}});return stats;}
    }
     

七、方案选型建议

  1. 中小型项目:优先选用dynamic-datasource,开发效率高

  2. 异构数据库系统:选择多SqlSessionFactory方案,隔离性好

  3. 需要高度定制:AbstractRoutingDataSource提供最大灵活性

  4. 云原生环境:dynamic-datasource + Seata分布式事务

八、常见问题解决方案

  1. 数据源切换失效

    • 检查方法是否被AOP代理

    • 确保@DS注解在public方法上

    • 避免类内部方法调用

  2. 跨库事务问题

    // 使用分布式事务
    @DS("order")
    @GlobalTransactional
    public void createOrder(Order order) {// 操作订单库orderMapper.insert(order);// 操作库存库stockService.reduce(order.getProductId(), order.getQuantity());
    }
  3. 连接泄露检测

    @Bean
    public DataSource dataSource(DataSourceProperties properties) {HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();dataSource.setLeakDetectionThreshold(5000); // 5秒泄露检测return dataSource;
    }

九、结语

多数据源管理是现代应用开发的必备技能。通过本文介绍的三种方案:

  1. AbstractRoutingDataSource:Spring原生方案,适合定制化场景

  2. 多SqlSessionFactory:适合异构数据库系统

  3. dynamic-datasource:生产环境首选,功能强大易用

最佳实践提示:对于大多数Java项目,推荐使用dynamic-datasource框架,它提供了一站式的解决方案,大大降低了多数据源管理的复杂度。同时结合Spring Cloud Alibaba Seata,可轻松实现分布式事务管理。

扩展阅读

  • Spring官方文档:数据访问

  • dynamic-datasource高级用法

  • MyBatis多数据源最佳实践

掌握多数据源切换技术,让你的应用从容应对复杂数据场景!

 

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

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

相关文章

Kafka性能压测报告撰写

在大数据生态体系中&#xff0c;Kafka以其卓越的高吞吐、低延迟特性&#xff0c;成为消息队列领域的中流砥柱。然而&#xff0c;随着业务规模不断扩张&#xff0c;数据流量日益激增&#xff0c;Kafka的性能表现直接关乎业务系统的稳定运行与效率提升。通过科学严谨的性能压测&a…

使用DevEco Testing快速创建HarmonyOS5单元测试

1.测试环境准备 确保已安装DevEco Studio 5.0在module的build.gradle添加依赖&#xff1a; dependencies {testImplementation org.junit.jupiter:junit-jupiter:5.8.2ohosTestImplementation com.huawei.ohos.testkit:runner:1.0.0.200 }2.创建测试类&#xff08;示例测试计…

开源物联网(IoT)平台对比

一些 开源物联网&#xff08;IoT&#xff09;平台&#xff0c;它们广泛应用于设备管理、数据采集、远程监控和边缘计算等场景&#xff1a; &#x1f31f; 主流开源物联网平台 平台描述技术栈许可证ThingsBoard功能丰富&#xff0c;支持设备管理、遥测数据收集、规则引擎、告警…

插值与模板字符串

背景。表单渲染需要获取对象中属性进行赋值操作。 插值错误使用。以下方举例。其中的placeholder不能被正确渲染。因为Vue 不会解析 {{ }} 在属性中的内容&#xff1b;如果这样写编译会出问题&#xff0c;而且比较难找出是哪的问题 模板字符串。正确做法时使用。模板字符串用…

Luckfox Pico Pi RV1106学习<4>:RV1106的帧率问题

Luckfox Pico Pi RV1106学习&#xff1c;4&#xff1e;&#xff1a;RV1106的帧率问题 1. 背景2. 问题 1. 背景 接上篇。我在应用中创建3个线程&#xff1a; CAM线程&#xff0c;使用V4L2驱动&#xff0c;从 /dev/video11 获取图像。ENC线程&#xff0c;使用硬件编码器&#x…

内测分发平台应用的异地容灾和负载均衡处理和实现思路?

在软件开发过程中&#xff0c;内测分发平台扮演着至关重要的角色。它不仅帮助开发者将应用程序传播给内部测试人员&#xff0c;还负责收集反馈、跟踪错误并改进产品。然而&#xff0c;为了确保一个平稳、连贯的内测过程&#xff0c;对内测分发平台实施异地容灾和负载均衡机制是…

国内用户如何高效升级npm:使用阿里云镜像加速指南

文章目录 引言为什么需要升级npm?环境检查使用阿里云镜像安装nvm配置阿里云镜像加速npm使用nvm安装最新Node.js验证安装结果升级npm到最新版本解决常见问题1. 权限问题2. 镜像源验证3. 项目创建失败创建测试项目总结引言 作为前端开发者,npm(Node Package Manager)是我们日…

LeetCode--34.在排序数组中查找元素的第一个和最后一个位置

解题思路&#xff1a; 1.获取信息&#xff1a; 给定一个非递减顺序的整数数组&#xff0c;要求找出给定元素在该数组中从左往右第一次出现的位置和最后一个出现的位置&#xff0c;即&#xff1a;最右边的位置和最左边的位置 如果不存在该元素&#xff0c;则返回{ -1 , -1 } 限制…

低秩分解的本质是通过基矩阵和系数矩阵的线性组合,以最小的存储和计算代价近似表示复杂矩阵

低秩分解的本质是通过基矩阵和系数矩阵的线性组合&#xff0c;以最小的存储和计算代价近似表示复杂矩阵 flyfish 一、最基础起点&#xff1a;数字与数组 数字与标量&#xff08;Scalar&#xff09; 单独的数&#xff0c;如 1 , 2.5 , − 3 1, 2.5, -3 1,2.5,−3&#xff0c;…

SVN本地使用--管理个人仓库

1.SVN官网下载链接 Download – TortoiseGit – Windows Shell Interface to Git 一路安装即可&#xff0c;安装后在桌面空白处右键菜单可以看到选项即安装成功。 2.建立个人SVN数据库 选择一个磁盘新建一个文件夹&#xff0c;在文件夹中右键创建数据库。 3.上传文件到SVN…

Cloud Automation-Resource optimization, cleanup and dashboard

如何使用Automation Account Run Book实现自动化 1. 什么是 Runbook&#xff1f; Azure Automation Account 中的 Runbook 是一套自动化脚本&#xff0c;用于在云中或混合环境中执行常规任务。Runbook 支持多种脚本语言&#xff0c;包括 PowerShell、Python、Graphical、Powe…

leetcode_3583 统计特殊三元组

1. 题意 求给定数组中下标 ( i , j , k ) (i,j,k) (i,j,k)的对数&#xff0c; 且满足 i < j < k , 2 a [ j ] a [ i ] a [ k ] i < j <k,2 a[j]a[i]a[k] i<j<k,2a[j]a[i]a[k] 2. 题解 2.1 枚举中间 三个数枚举中间那个数&#xff0c;再存前缀和后缀个数…

Sentinel(一):Sentinel 介绍和安装

一、Sentinel 介绍 1、什么是 Sentinel&#xff1f; 一句话来说&#xff0c;Sentinel就是&#xff1a;分布式系统的流量卫兵&#xff08;官网&#xff09;。 随着微服务的普及&#xff0c;服务调用的稳定性变得越来越重要。Sentinel以“流量”为切入点&#xff0c;在流量 控制…

pyspark 初试

1、安装jdk sudo apt-get install openjdk-17-jdk 2、安装spark curl -o spark.tgz https://mirrors.tuna.tsinghua.edu.cn/apache/spark/spark-4.0.0/spark-4.0.0-bin-hadoop3.tgz tar -xvf spark.tgz mv spark-4.0.0-bin-hadoop3 /opt/spark修改 /etc/profile 添加 exp…

深入解析select模型:FD_SET机制与1024限制的终极指南

在Linux网络编程中&#xff0c;select函数是最经典的I/O多路复用技术之一&#xff0c;但其核心机制FD_SET的1024限制常成为高并发系统的瓶颈。本文将深入剖析FD_SET实现原理&#xff0c;并提供突破限制的实战方案。 一、FD_SET底层结构解析 FD_SET本质是固定长度的位图数组&am…

C函数基础.go

前言&#xff1a; 在Go语言中&#xff0c;函数是构成程序的基本模块&#xff0c;它封装了一段具有特定功能的代码&#xff0c;使得代码更易读&#xff0c;更易维护和重用。熟练掌握函数的定义、调用以及相关特性是成为Go语言开发者的必经之路。 目录 函数定义&#xff1a;给代…

什么是池化

池化是深度学习中用于降低数据维度、提取核心特征的一种操作&#xff0c;主要应用于卷积神经网络&#xff08;CNN&#xff09;。其核心思想是通过对局部区域进行聚合统计&#xff08;如取最大值、平均值&#xff09;&#xff0c;保留关键信息的同时减少计算量。 池化的作用 降维…

C++ 性能分析工具:Valgrind 与 perf

在 C 开发中&#xff0c;性能优化是提升软件质量的关键环节。内存泄漏和 CPU 资源消耗是最常见的性能瓶颈&#xff0c;而 Valgrind 和 perf 作为专业的性能分析工具&#xff0c;能帮助开发者精准定位这些问题。下面将从工具原理、使用方法、实战案例等方面进行详细介绍。 一、…

ABP VNext + MongoDB 数据存储:多模型支持与 NoSQL 扩展

&#x1f680; ABP VNext MongoDB 数据存储&#xff1a;多模型支持与 NoSQL 扩展&#xff08;生产级实践&#xff09; 目录 &#x1f680; ABP VNext MongoDB 数据存储&#xff1a;多模型支持与 NoSQL 扩展&#xff08;生产级实践&#xff09;&#x1f3af; 引言&#x1f9f0…

Cursor Rules 的核心定位与作用 DevOps是

Cursor Rules 是 AI 编程工具 Cursor IDE 中的核心功能&#xff0c;用于约束 AI 生成代码的行为&#xff0c;确保其符合项目规范、编码风格或特定技术需求。它本质上是一套持久化、可复用的指令集&#xff0c;会动态插入到 AI 模型的上下文提示中&#xff0c;指导其生成代码的逻…