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

【SringBoot项目中MyBatis-Plus多数据源应用实践】

文章目录

前言

一、Mybatis-Plus是什么?

二、多数据源是什么?

三、使用步骤

1. 新建一个SpringBoot项目

2. 引入必要的MyBatis架包

3. 新建两个数据库及两张表

3.3.1 新建数据库:DB_A,并创建一张数据表alarm_kind,以及一些测试数据。

3.3.2 同样再建一个数据库DB_B,并创建一张数据表test,以及一些测试数据。

4. 修改配置文件application.yml

5. 编码

5.1 entity 实体类

5.2 mapper接口

5.3 service服务接口

5.4 service服务实现

6. 测试及验证 

总结




前言

在日常大型项目中经常会遇到分库分表的数据库设计,咱们普通的SpringBoot 单体项目通过默认配置及注解可以很容易实现对单个数据库的操作管理,但遇到需要多个数据库操作可能会比较麻烦,因此咱们今天主要介绍如何使用MyBatis-Plus提供的一个多数据源动态加载组件简单轻松实现在单个项目中实现对多个数据库的操作管理。

关键词:SpringBoot 、MyBatis、MyBatis-Plus、多数据源

一、Mybatis-Plus是什么?

Mybatis-Plus是一个Mybatis(opens new window)的增强工具,在Mybatis的基础上只做增强不做改变,为简化开发。

二、多数据源是什么?

多数据源是指在分布式环境或集群环境下,为不同的数据库(如MySQL、Oracle、SQLServer等)配置数据源,以便在项目中能够随意切换以获取数据。这种配置方式允许应用程序根据需要连接到不同的数据库,以实现特定的业务需求,如数据隔离、水平扩展、读写分离等。

三、使用步骤

1. 新建一个SpringBoot项目

可以通过Ide工具向导建立,这里不详细描述了。

2. 引入必要的MyBatis架包

在项目的pom.xml加入如何内容

  <!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.18</version></dependency><!-- 连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.24</version></dependency><!-- mybatis plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatisplus-spring-boot-starter.version}</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId><version>>3.4.2</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.4.1</version></dependency><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.3</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.0</version></dependency>

3. 新建两个数据库及两张表

3.3.1 新建数据库:DB_A,并创建一张数据表alarm_kind,以及一些测试数据。

CREATE DATABASE IF NOT EXISTS `DB_A` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `DB_A`;DROP TABLE IF EXISTS `alarm_kind`;
CREATE TABLE IF NOT EXISTS `alarm_kind` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`type_no` varchar(255) DEFAULT NULL COMMENT '类型编号',`type_name` varchar(255) DEFAULT NULL COMMENT '类型名称',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COMMENT='报警类型字典表';DELETE FROM `alarm_kind`;
INSERT INTO `alarm_kind` (`id`, `type_no`, `type_name`) VALUES(1, 'Powon', '市电'),(2, 'Baton', 'UPS电源'),(3, 'Move', '位移'),(4, 'Alarm', '震动'),(5, 'Cut', '切割检测'),(6, 'Smoke', '烟雾'),(7, 'LBJ', '声光'),(8, 'HW', '红外'),(9, 'Box4_NoPutTimeOut', '四号柜款箱未放入');

3.3.2 同样再建一个数据库DB_B,并创建一张数据表test,以及一些测试数据。

CREATE DATABASE IF NOT EXISTS `DB_B` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `DB_B`;DROP TABLE IF EXISTS `test`;
CREATE TABLE IF NOT EXISTS `alarm_kind` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`type_no` varchar(255) DEFAULT NULL COMMENT '类型编号',`type_name` varchar(255) DEFAULT NULL COMMENT '类型名称',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COMMENT='报警类型字典表';DELETE FROM `test`;
INSERT INTO `test` (`id`, `type_no`, `type_name`) VALUES(1, 'Powon', '测试1'),(2, 'Baton', '测试2'),(3, 'Move', '测试3');

4. 修改配置文件application.yml

server:port: 8085  #服务端口配置
spring:application:name: ms-basedatasource:dynamic:primary: baseA #默认加载的数据库是DB_Astrict: falsedatasource:baseA:driver-class-name: com.mysql.cj.jdbc.Driver #数据源配置url: jdbc:mysql://127.0.0.1:13306/DB_A?serverTimezone=GMT%2B8&useSSL=falseusername: rootpassword: data@123456!type: com.alibaba.druid.pool.DruidDataSource  #连接池配置max-idle: 10  #No operations allowed after connection closed错误原因及解决max-wait: 10000min-idle: 5initial-size: 5validation-query: SELECT 1test-on-borrow: falsetest-while-idle: truetime-between-eviction-runs-millis: 18800baseB:driver-class-name: com.mysql.cj.jdbc.Driver #数据源配置url: jdbc:mysql:///127.0.0.1:13306/DB_B?serverTimezone=GMT%2B8&useSSL=falseusername: rootpassword: data@123456!type: com.alibaba.druid.pool.DruidDataSource  #连接池配置max-idle: 10  #No operations allowed after connection closed错误原因及解决max-wait: 10000min-idle: 5initial-size: 5validation-query: SELECT 1test-on-borrow: falsetest-while-idle: truetime-between-eviction-runs-millis: 18800

5. 编码

5.1 entity 实体类

AlarmKind.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** <p>* 报警类型字典表* </p>** @since 2022-05-24*/
@Data
@EqualsAndHashCode(callSuper = false)
public class AlarmKind implements Serializable {private static final long serialVersionUID = 1L;/*** 主键ID*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 类型编号*/private String typeNo;/*** 类型名称*/private String typeName;
}

 Test.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** <p>* 测试表* </p>**/
@Data
@EqualsAndHashCode(callSuper = false)
public class Test implements Serializable {private static final long serialVersionUID = 1L;/*** 主键ID*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 类型编号*/private String typeNo;/*** 类型名称*/private String typeName;
}

5.2 mapper接口

 AlarmKIndMapper.java


import com.xxxx.rest.entity.AlarmKind;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;/*** <p>* 报警类型字典表 Mapper 接口* </p>**/
public interface AlarmKindMapper extends BaseMapper<AlarmKind> {List<AlarmKind> getAll();
}

 TestMapper.java


import com.xxxx.rest.entity.Test;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;/*** <p>* 测试表 Mapper 接口* </p>**/
public interface TestMapper extends BaseMapper<Test> {List<Test> getAll();
}

5.3 service服务接口

IAlarmKIndService.java

import com.xxx.rest.entity.AlarmKind;
import com.baomidou.mybatisplus.extension.service.IService;import com.xianban.rest.model.msbase.vo.platform.BaseLineVO;import java.util.List;/*** <p>* 报警类型表 服务类* </p>**/
public interface IAlarmKindService extends IService<AlarmKind> {List<AlarmKind> getAll();
}

ITestService.java

import com.xxx.rest.entity.Test;
import com.baomidou.mybatisplus.extension.service.IService;import com.xianban.rest.model.msbase.vo.platform.BaseLineVO;import java.util.List;/*** <p>*测试表 服务类* </p>**/
public interface ITestService extends IService<Test> {List<Test> getAll();
}

5.4 service服务实现

AlarmKindServiceImpl.java

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xxxx.rest.entity.AlarmKind;
import com.xxxx.rest.mapper.AlarmKindMapper;
import com.xxxx.rest.service.IAlarmKindService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;/*** <p>* 报警类型表 服务实现类* </p>**/
@Service
@DS("baseA") //这个注解是指定操作哪个数据源,如果没有这个注解则代表操作配置文件中的主数据源
public class AlarmKindServiceImpl extends ServiceImpl<AlarmKindMapper, AlarmKind> implements IAlarmKindService {@Resourceprivate AlarmKindMapper mapper;@Overridepublic List<AlarmKind> getAll() {QueryWrapper<AlarmKind> wrapper=new QueryWrapper();return mapper.selectList(wwapper);}
}

TestServiceImpl.java

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xxxx.rest.entity.Test;
import com.xxxx.rest.mapper.TestMapper;
import com.xxxx.rest.service.ITestService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;/*** <p>* 测试表 服务实现类* </p>**/
@Service
@DS("baseB") //这个注解是指定操作哪个数据源,如果没有这个注解则代表操作配置文件中的主数据源
public class TestServiceImpl extends ServiceImpl<TestMapper, Test> implements ITestService {@Resourceprivate TestMapper mapper;@Overridepublic List<Test> getAll() {QueryWrapper<Test> wrapper=new QueryWrapper();return mapper.selectList(wwapper);}
}


    

6. 测试及验证 

编写测试Controller

@RestController
@RequestMapping("/test")
@CrossOrigin
@Api(tags = "手动测试接口管理")
public class TestController {@Resourceprivate IAlarmKindService alarmKindService;@Resourceprivate ITestService testService;//查询报警类型数据@GetMapping("/getAlarmKindList")public ResponseEntity<Object> getAlarmKindList(){
return getRespOk(alarmKindService.getAll());
}
//查询测试表数据@GetMapping("/getTestList")public ResponseEntity<Object> getTestList(){
return getRespOk(testService.getAll());
}protected ResponseEntity<Object> getRespOk(Object resData) {return new ResponseEntity<>(resData, HttpStatus.OK); }}
启动项目

在浏览器访问
1.报警类型信息接口地址:http://localhost:8085/test/getAlarmKindList,如果返回结果说明正确

2.测试信息接口地址:http://localhost:8085/test/getTestList,如果返回结果说明正确

总结

通过以上内容分析,其实关键就引入架包、修改配置文件、服务的实现类中增加@DS("配置文件中定义的数据源名称"),代码改动可以说非常小。

相关文章:

【SringBoot项目中MyBatis-Plus多数据源应用实践】

文章目录 前言 一、Mybatis-Plus是什么&#xff1f; 二、多数据源是什么&#xff1f; 三、使用步骤 1. 新建一个SpringBoot项目 2. 引入必要的MyBatis架包 3. 新建两个数据库及两张表 3.3.1 新建数据库&#xff1a;DB_A&#xff0c;并创建一张数据表alarm_kind&#xff0c;以及…...

Android 图表开发开源库 MPAndroidChart 使用总结

1. 引言 电视项目中需要一个折线图表示节电数据变化情况&#xff0c;类比 H5 来说&#xff0c;Android 中也应该有比较成熟的控件&#xff0c;经过调研后&#xff0c;发现 MPAndroidChart 功能比较强大&#xff0c;网上也有人说可能是目前 Android 开发最好用的一个三方库了&a…...

手机号脱敏

手机号脱敏 // 手机号脱敏subTelephone(telphone) {let result telphone.substr(0, 4) **** telphone.substr(8);return result;},...

java基础篇(1)

JDK是什么?有哪些内容组成?JDK是Java开发工具包 JVM虚拟机: Java程序运行的地方 核心类库: Java已经写好的东西&#xff0c;我们可以直接用开发工具: javac、java、jdb、jhat.. JRE是什么?有哪些内容组成? JRE是Java运行环境 JVM、核心类库、运行工具 JDK&#xff0c;JRE&…...

2022年全国职业院校技能大赛高职组“信息安全管理与评估”赛项第三阶段任务书

第三阶段竞赛项目试题 本文件为信息安全管理与评估项目竞赛-第三阶段试题。根据信息安全管理与评估项目技术文件要求&#xff0c;第三阶段为夺旗挑战CTF&#xff08;网络安全渗透&#xff09;。 本次比赛时间为180分钟。 介绍 夺旗挑战赛&#xff08;CTF&#xff09;的目标…...

微信小程序蓝牙连接部分Android14调用wx.setBLEMTU协商低功耗最大传输单元失败解决方案(部分安卓14设置超过23就会报错)

1.解决方案的核心内容&#xff1a;第一次设置失败不要管&#xff0c;在complate函数里面继续往下连接&#xff0c;然后设置一个定时器每1秒钟在重新设置一次&#xff0c;肯定会成功的&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&am…...

PDF格式分析(八十二)——电影注释(movie)

电影注释(PDF1.2及其以上版本)&#xff0c;该注释包含图像和声音&#xff0c;声音通过扬声器进行播放&#xff0c;图像则显示在计算机屏幕上&#xff0c;如同一个视频播放器一样。当该类型注释被激活时&#xff0c;视频将被播放。 下表将显示电影注释的字典条目&#xff1a; 条…...

Opentracing 代码Demo

背景 OpenTracing 是一个提供标准化分布式追踪功能的API和工具。它的主要作用包括: 跨系统边界追踪请求流程:OpenTracing 允许开发者跟踪一个请求从开始到结束在整个分布式系统中的所有经过的点(包括异构系统),帮助理解系统中的请求流程和服务间的相互依赖。 性能分析和瓶…...

笔记93:关于 C++ 中的 Eigen 库

注意1&#xff1a;Eigen 是一个基于 C 模板的线性代数库&#xff0c;以支持在 C 中进行矩阵运算&#xff1b; 注意2&#xff1a;要在 C 中使用 Eigen&#xff0c;需要在在程序开始前要包含所需头文件路径&#xff1b; #include <Eigen> a a 基础用法汇总 定义向量 E…...

【微服务】部署mysql集群,主从复制,读写分离

两台服务器做如下操作 1.安装mysqldocker pull mysql:5.72.启动以及数据挂载 mkdir /root/mysql/data /root/mysql/log /root/mysql/conf touch my.conf //mysql的配置文件docker run --name mysql \ -e MYSQL_ROOT_PASSWORD123456 \ -v /root/mysql/data:/var/lib/mysql \ -v…...

【Java】设计一个支持敏感数据存储和传输安全的加解密平台

一、问题解析 在一个应用系统运行过程中&#xff0c;需要记录、传输很多数据&#xff0c;这些数据有的是非常敏感的&#xff0c;比如用户姓名、手机号码、密码、甚至信用卡号等等。这些数据如果直接存储在数据库&#xff0c;记录在日志中&#xff0c;或者在公网上传输的话&…...

iOS AVFoundation 音视频源码分享

引言 在现代移动开发中&#xff0c;音视频处理是一个不可忽视的重要领域。iOS 提供了强大的 AVFoundation 框架&#xff0c;使开发者能够轻松实现音视频录制、播放、编辑等功能。无论是创建高效的视频播放器&#xff0c;还是实现复杂的音频处理&#xff0c;AVFoundation 都能提…...

Ubuntu开发入门之“制作Ubuntu rootfs根文件系统镜像“

Ubuntu开发入门之“制作Ubuntu rootfs根文件系统镜像” 问题描述解决方法1.首先从官网下载最基础的ubuntu base核心文件,ubuntu core.2.接下来就是制作一个基础功能的根文件系统3.修改可用源4.接下来就是挂载根文件系统,进行模拟安装应用5.根文件系统安装常用的工具和配置用户…...

基于FPGA的SystemVerilog练习

文章目录 一、认识SystemVerilogSystemVerilog的语言特性SystemVerilog的应用领域SystemVerilog的优势SystemVerilog的未来发展方向 二、流水灯代码流水灯部分testbench仿真文件 三、用systemVerilog实现超声波测距计时器测距部分led部分数码管部分采样部分顶层文件引脚绑定效果…...

【数据结构】详解堆的基本结构及其实现

文章目录 前言1.堆的相关概念1.1堆的概念1.2堆的分类1.2.1小根堆1.2.2大根堆 1.3堆的特点堆的实用场景 2.堆的实现2.1初始化2.2插入2.3堆的向上调整2.4删除2.5堆的向下调整2.6判空2.7获取堆顶元素2.8销毁 3.堆排序3.1实现3.2堆排序的时间复杂度问题 前言 在上一篇文章中&#…...

python无限弹窗的代码

一个简单的Python代码示例&#xff0c;用于在特定的时间间隔内显示一个简单的弹窗。这个代码使用了Python的tkinter库来创建一个简单的GUI窗口。 python import tkinter as tk import time def popup(): popup_window.deiconify() # 显示窗口 popup_window.wait_window() # 等…...

多线程新手村5--线程池

1.1 线程池是什么 线程诞生的意义是因为进程的创建/销毁开销太大&#xff0c;所以使用线程提高代码的执行效率&#xff1b;那如果想要进一步提升执行效率&#xff0c;该怎么办呢&#xff1f;有一个方法是使用线程池。 首先&#xff0c;什么是线程池&#xff1a;池就是池子&am…...

数据库 mysql 的彻底卸载

MySQL卸载步骤如下&#xff1a; &#xff08;1&#xff09;按 winr 快捷键&#xff0c;在弹出的窗口输入 services.msc&#xff0c;打开服务列表。 &#xff08;2&#xff09;在服务列表中&#xff0c; 找到 mysql 开头的所有服务&#xff0c; 右键停止&#xff0c;终止对应的…...

Meterpreter工具使用

Meterpreter属于stage payload&#xff0c;在Metasploit Framework中&#xff0c;Meterpreter是一种后渗透工具&#xff0c;它 属于一种在运行过程中可通过网络进行功能扩展的动态可扩展型Payload。这种工具是基于“内存DLL注 入”理念实现的&#xff0c;它能够通过创建一个新进…...

第四讲 单片机STC89C52+RA8889代码移植范例(包含API接口)

本次介绍单片机STC89C52RA8889代码移植范例&#xff0c;该范例已将RA8889的API移植好了&#xff0c;下方提供下载地址。 硬件平台&#xff1a;89C52RA8889 采用SPI通信方式 (已测试通过&#xff09; 上一讲已经阐述RA8889移植到51单片机的基本方法&#xff0c;本讲增加了API…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...