SpringBoot 整合多数据源:从基础切换到动态路由全解析

SpringBoot 整合多数据源:从基础切换到动态路由全解析

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

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

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

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

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

xml

<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,运行时无需切换数据源。

实现步骤

1. 配置文件(application.yml)

yaml

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 
2. 数据源配置类

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

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

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):

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); } } 
3. 业务代码示例
  • 用户 Mapper(UserMapper.java):放在com.example.demo.mapper.user包下

java

运行

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包下

java

运行

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> { } 
  • 业务层调用:

java

运行

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); } } 

方案 1 优缺点

✅ 优点:配置简单、无侵入性、性能高,适合模块隔离的场景;❌ 缺点:无法动态切换,新增数据源需新增配置类,灵活性低。

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

适用场景

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

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

实现步骤

1. 配置文件(application.yml)

yaml

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 
2. 核心工具类

数据源上下文 Holder(DynamicDataSourceContextHolder.java):

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):

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(); } } 
3. 数据源配置类

java

运行

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(); } } 
4. 自定义注解 + AOP 实现自动切换

数据源注解(DataSource.java):

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):

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"; } } 
5. 业务代码示例

java

运行

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); } } 

方案 2 优缺点

✅ 优点:灵活性高,支持运行时动态切换,可扩展至 N 个数据源;❌ 缺点:需要手动管理 ThreadLocal,切面优先级需高于事务,否则切换失效。

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

适用场景

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

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

实现步骤

1. 引入依赖

xml

<!-- 动态数据源插件(核心) --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.6.1</version> </dependency> 
2. 配置文件(application.yml)

yaml

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 
3. 业务代码示例

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

java

运行

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 取模分库):

java

运行

/** * 根据用户ID取模,路由到不同从库 */ @Override @DS("#{id % 2 == 0 ? 'slave1' : 'slave2'}") public User getUserById(Long id) { return userMapper.selectById(id); } 

方案 3 优缺点

✅ 优点:零配置成本、功能丰富(支持读写分离、负载均衡、分布式锁)、官方维护;❌ 缺点:依赖第三方插件,深度定制化场景需二次开发。

五、方案对比与选型建议

表格

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

六、注意事项

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

总结

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

本文所有代码均可直接复制运行,建议根据实际业务场景调整数据源配置和切换逻辑。如果有分布式事务、分库分表等进阶需求,可关注后续文章。

Read more

小迪安全2023-2024|第14天:信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ爬虫&笔记|web安全|渗透测试|

小迪安全2023-2024|第14天:信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ爬虫&笔记|web安全|渗透测试|

小迪安全2023-2024|第14天:信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ爬虫&笔记|web安全|渗透测试| 一、前端JS打点 00:06 1. 业务资产分类 00:35 * Web资产:包括语言/CMS/中间件/数据库/系统/WAF等 * 系统资产:包含操作系统/端口服务/网络环境/防火墙等 * 应用资产:涉及APP对象/API接口/微信小程序/PC应用等 * 架构资产:涵盖CDN/前后端/云应用/站库分离/OSS资源等 * 技术资产:包含Js爬虫/敏感扫描/端口扫描/

By Ne0inhk
实战:Spring Boot 2.7.8 原生 SSE 服务端开发

实战:Spring Boot 2.7.8 原生 SSE 服务端开发

目录 前言 一、SSE知识简介 1、SSE是什么 2、SSE工作原理 3、SSE适用场景 二、SpringBoot中SSE的实现 1、Maven中引入 2、SSE服务类实现 3、SSE控制器类实现 4、最简单页面实现 三、成果展示 1、SSE连接 2、群发消息 3、点对点消息 四、总结 前言         在当今的互联网应用开发中,实时数据交互的需求日益增长。无论是股票交易系统中实时更新的股价信息,还是社交平台上的即时消息推送,亦或是物联网场景下传感器数据的实时传输,都对后端服务提出了更高的要求。传统的轮询机制虽然简单,但效率低下且资源消耗大;而WebSocket虽然功能强大,但在某些场景下显得过于复杂且实现成本较高。在这种背景下,Server-Sent Events(SSE)作为一种轻量级的、基于HTTP协议的单向实时通信技术,逐渐受到开发者的关注。         SSE允许服务器主动向客户端推送数据,而无需客户端频繁发起请求,

By Ne0inhk

Node.js完整安装配置指南(包含国内镜像配置)

Node.js完整安装配置指南(包含国内镜像配置) 一、Node.js安装 方法1:使用Chocolatey安装(推荐) # 安装最新LTS版本 choco install nodejs # 或安装指定版本 choco install nodejs --version=20.11.0 方法2:官网下载安装 1. 访问 Node.js官网 2. 下载LTS版本(推荐) 3. 运行安装程序,勾选"Add to PATH"选项 方法3:使用国内镜像下载 # 从淘宝镜像下载# 访问: https://npmmirror.com/mirrors/node/# 选择对应版本下载 二、

By Ne0inhk
Flutter 组件 short_uuids 适配鸿蒙 HarmonyOS 实战:唯一标识微缩技术,构建高性能短 ID 生成与分布式索引架构

Flutter 组件 short_uuids 适配鸿蒙 HarmonyOS 实战:唯一标识微缩技术,构建高性能短 ID 生成与分布式索引架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 short_uuids 适配鸿蒙 HarmonyOS 实战:唯一标识微缩技术,构建高性能短 ID 生成与分布式索引架构 前言 在鸿蒙(OpenHarmony)生态迈向万物互联、涉及海量离线资源标识、蓝牙广播载荷(BLE Payload)及二维码数据极限压缩的背景下,如何生成既能保留 UUID 强随机性、又能极大缩减字符长度的唯一标识符,已成为优化存储与通讯效率的“空间必修课”。在鸿蒙设备这类强调分布式软总线传输与每一字节功耗敏感的环境下,如果应用依然直接传输长度达 36 字符的标准 UUID,由于由于有效载荷溢出,极易由于由于传输协议限制导致数据截断或多次分包带来的延迟。 我们需要一种能够实现高进制转换、支持双向编解码且具备低碰撞概率的短 ID 生成方案。 short_uuids 为 Flutter 开发者引入了将标准 UUID 转化为短格式字符串的高性能算法。它利用

By Ne0inhk