响应式编程实战:Spring WebFlux集成MongoDB和Swagger

news/2024/12/4 3:34:10/

1 缘起

新的项目,快速迭代,
技术选型:Spring WebFlux,
非Spring MVC,
之前没有接触过Spring WebFlux,项目中都是使用Spring MVC,
这次学到了新的知识Spring WebFlux,记录下。

2 SpringMVC & Spring WebFlux

Spring产品提供了两个并行的技术路线:

  • 基于Spring MVC和Spring Data构造的Servlet API路线
  • 基于Spring WebFlux和Spring Data的响应流路线
    响应式:https://spring.io/reactive
    响应式系统是低延迟、高吞吐工作负载的理想方案
    WebFlux一切皆流。
    下面看下Spring WebFlux和Spring MVC这两个技术路线的对比:
    在这里插入图片描述
    由上图可知,
    Spring WebFlux支持非关系型数据库,
    这一点与Spring MVC的差别还是很大的,
    如果需要使用关系型数据库,基于Spring MVC构建项目是最有选择,
    因此,基于Spring WebFlux构建项目,适用于非结构化存储。

本篇主要讲解应用,响应式编程的细节会在其他文章中分享。

3 项目依赖

版本:
SpringBoot2.4.5
Swagger:3.0.0

这里,使用spring-boot-starter-webflux作为启动容器(默认为Netty)。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.5</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.monkey</groupId><artifactId>spring-boot-template</artifactId><version>0.0.1-SNAPSHOT</version><name>spring-boot-template</name><description>Template project for Spring Boot</description><properties><java.version>1.8</java.version><io.springfox.version>3.0.0</io.springfox.version><io.swagger.version>1.5.22</io.swagger.version><fastjson.version>1.2.60</fastjson.version><logback.version>1.2.3</logback.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><!--接口管理工具--><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>${io.springfox.version}</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${io.springfox.version}</version><exclusions><exclusion><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId></exclusion><exclusion><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId></exclusion></exclusions></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${io.springfox.version}</version></dependency><dependency><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId><version>${io.swagger.version}</version></dependency><dependency><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId><version>${io.swagger.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><!--日志工具--><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>${logback.version}</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version></dependency><!--AOP--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb-reactive</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

4 数据库

本文选择MongoDB作为持久层。
MongoDB的部署:https://blog.csdn.net/Xin_101/article/details/130902224

4.1 MongoDB配置

spring:devtools:restart:enabled: truedata:mongodb:uri: mongodb://tutorial:admin123456@172.22.75.234:27017/tutorial?authSource=tutorial

4.2 文档配置

MongoDB使用Document作为存储数据的基本单位,对比而言,MySQL中的表,新建的数据需要明确指定存储的Document,
下面新建一个user文档,存储用户数据。
其中,@Id用户表示该数据为MongDB中的_id属性,由MongoDB生成。
而,使用MongoDB操作数据没有自动的数据映射,需要手动映射,因此,在每个需要映射的字段下新建了一个static final的属性名称。
_id,username,操作数据时使用。

package com.monkey.springboottemplate.modules.user.entity;import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;/*** 用户信息.** @author xindaqi* @since 2023-05-28 11:38*/
@Document("user")
public class UserEntity {/*** 用户主键id,MongoDB自动生成*/@Idprivate String id;public final static String _id = "_id";/*** 用户id*/private String uid;/*** 用户姓名*/private String username;public static final String _username = "username";/*** 用户性别*/private String sex;public static final String _sex = "sex";/*** 创建时间*/private Long createdTime;/*** 更新时间*/private Long updatedTime;public static final String _updatedTime = "updatedTime";public String getId() {return id;}public void setId(String id) {this.id = id;}public String getUid() {return uid;}public void setUid(String uid) {this.uid = uid;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public Long getCreatedTime() {return createdTime;}public void setCreatedTime(Long createdTime) {this.createdTime = createdTime;}public Long getUpdatedTime() {return updatedTime;}public void setUpdatedTime(Long updatedTime) {this.updatedTime = updatedTime;}@Overridepublic String toString() {return "UserEntity{" +"id='" + id + '\'' +", uid='" + uid + '\'' +", username='" + username + '\'' +", sex='" + sex + '\'' +", createdTime='" + createdTime + '\'' +", updatedTime='" + updatedTime + '\'' +'}';}
}

5 Service

WebFlux中使用Mono或者Flux作为返回对象,
因此,在Service层直接使用Mono或Flux作为最终的结果类型,
而不是在Controller层中封装最终结果,
WebFlux:一切皆流数据,因此,编码很优雅,可以做到瀑布式编程,数据一层一层地流下去,最终输出结果。

package com.monkey.springboottemplate.modules.user.service.impl;import com.monkey.springboottemplate.common.response.Response;
import com.monkey.springboottemplate.modules.user.convert.UserConvert;
import com.monkey.springboottemplate.modules.user.dao.UserDAO;
import com.monkey.springboottemplate.modules.user.entity.UserEntity;
import com.monkey.springboottemplate.modules.user.service.IUserService;
import com.monkey.springboottemplate.modules.user.vo.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;/*** 用户服务实现类.** @author xindaqi* @date 2021-05-08 16:01*/
@Service
public class UserServiceImpl implements IUserService {private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);@Autowiredprivate ReactiveMongoTemplate mongoTemplate;@Overridepublic Mono<Response<String>> addUser(UserAddInputVO params) {UserEntity userEntity = new UserEntity();userEntity.setUid(params.getUid());userEntity.setUsername(params.getUsername());userEntity.setSex(params.getSex());userEntity.setCreatedTime(System.currentTimeMillis());return mongoTemplate.insert(userEntity).flatMap(user -> Mono.just(Response.success(user.getId())));}@Overridepublic Mono<Response<String>> deleteUser(String id) {Query query = Query.query(Criteria.where(UserEntity._id).is(id));return mongoTemplate.findAndRemove(query, UserEntity.class).flatMap(user ->Mono.just(Response.success(id)));}@Overridepublic Mono<Response<String>> editUser(UserEditInputVO params) {Query query = Query.query(Criteria.where(UserEntity._id).is(params.getId()));Update update = new Update().set(UserEntity._sex, params.getSex()).set(UserEntity._username, params.getUsername()).set(UserEntity._updatedTime, System.currentTimeMillis());return mongoTemplate.updateFirst(query, update, UserEntity.class).flatMap(user -> Mono.just(Response.success(params.getId())));}@Overridepublic Mono<Response<UserInfoVO>> queryUserById(String id) {Query query = Query.query(Criteria.where(UserEntity._id).is(id));return mongoTemplate.findOne(query, UserEntity.class).flatMap(user -> {UserInfoVO userInfo = new UserInfoVO();userInfo.setUid(user.getUid());userInfo.setId(user.getId());userInfo.setSex(user.getSex());userInfo.setUsername(user.getUsername());return Mono.just(Response.success(userInfo));});}@Overridepublic Mono<Response<List<UserInfoVO>>> queryUserByPage(UserPageInputVO params) {Response<List<UserInfoVO>> resp = Response.success();Query query = new Query();return mongoTemplate.count(query, UserEntity.class).flatMap(count -> {if (Objects.isNull(count)) {resp.setTotal(0);}resp.setTotal(count);query.skip((long) (params.getPageStart() - 1) * params.getPageSize()).limit(params.getPageSize());return mongoTemplate.find(query, UserEntity.class).collectList();}).map(userLi -> {List<UserInfoVO> li = userLi.stream().map(UserConvert::convert).collect(Collectors.toList());resp.setData(li);return resp;});}
}

6 Controller

接口层,直接使用Mono封装结果。

package com.monkey.springboottemplate.api;import com.monkey.springboottemplate.common.response.Response;
import com.monkey.springboottemplate.modules.user.service.IUserService;
import com.monkey.springboottemplate.modules.user.vo.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;import javax.annotation.Resource;import java.util.List;import static com.monkey.springboottemplate.common.constant.DigitalConstant.ONE;/*** 用户增删改查接口.** @author xindaqi* @date 2021-05-08 16:09*/
@RestController
@RequestMapping("/api/v1/user")
@Api(tags = "人员配置")
public class UserApi {@ResourceIUserService userService;@PostMapping("/add")@ApiOperation("添加用户")public Mono<Response<String>> addUser(@RequestBody UserAddInputVO params) {return userService.addUser(params);}@GetMapping("/delete")@ApiOperation("删除用户")public Mono<Response<String>> deleteUser(@Param("id") String id) {return userService.deleteUser(id);}@PostMapping("/edit")@ApiOperation("编辑/修改用户")public Mono<Response<String>> editUser(@RequestBody UserEditInputVO params) {return userService.editUser(params);}@GetMapping("/query")@ApiOperation("根据ID查询用户")public Mono<Response<UserInfoVO>> queryUserById(@Param("id") String id) {return userService.queryUserById(id);}@PostMapping("/query/page")@ApiOperation("分页查询用户")public Mono<Response<List<UserInfoVO>>> queryUserByPage(@RequestBody UserPageInputVO params) {return userService.queryUserByPage(params);}
}

7 配置Swagger

Swagger使用3.0.0版本。

package com.monkey.springboottemplate.common.config;import springfox.documentation.service.Contact;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import org.springframework.core.env.Profiles;
import org.springframework.core.env.Environment;
import springfox.documentation.oas.annotations.EnableOpenApi;/*** Swagger配置.** @author xindaqi* @since 2023-05-27 22:20*/
@Configuration
@EnableSwagger2
@EnableOpenApi
public class SwaggerConfig {@Beanpublic Docket createRestApi(Environment environment){// 配置Swagger显示,仅dev和test环境显示Profiles profiles = Profiles.of("dev");boolean b = environment.acceptsProfiles(profiles);return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(b).select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build();}private ApiInfo apiInfo(){return new ApiInfoBuilder().title("接口文档").contact(new Contact("xindaqi", "xxx@qq.com", "xxx@qq.com")).version("1.0").description("个人信息").termsOfServiceUrl("http://localhost:9121/api/v1").build();}
}

访问:http://localhost:9121/swagger-ui/index.htm

在这里插入图片描述

8 小结

技术选型依实际情况而定,不是为了引入技术而引入技术,是为了解决问题,引入技术。
(1)Spring WebFlux使用Mono或Flux作为返回类型;
(2)Spring WebFlux一切皆流,通过流将数据一层一层地传递下去,编程方式非常优雅:瀑布式;
(3)Spring WebFlux使用Swagger3.0.0,测试时,使用了Swagger2.9.2无法生成文档;
(4)Spring WebFlux是响应式编程,低延迟、高吞吐系统的首选,但是,数据层的选择受限,如仅支持有限的非关系型数据库:MongoDB、Redis。


http://www.ppmy.cn/news/100107.html

相关文章

Beautiful Soup应用示例

Beautiful Soup应用示例 简介 Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库&#xff0c;它能够通过你喜欢的转换器实现惯用的文档导航&#xff0c;查找&#xff0c;修改文档的方式。 快速入门 from bs4 import BeautifulSoup broken_html <ul classcoun…

Zabbix4.0 自动发现TCP端口并监控

java端口很多&#xff0c;每台机器上端口不固定&#xff0c;考虑给机器配置组不同的组挂载模版&#xff0c;相对繁琐。直接使用同一个脚本自动获取机器上java相关的端口&#xff0c;推送到zabbix-server。有服务端口挂了自动推送告警 一、zabbix-agent配置过程 1、用户自定义参…

ROS:ROS的一些基本命令行

目录 一、打开小海龟1.1终端&#xff0c;启动ROS Master&#xff1a;1.2终端2&#xff0c;启动小海龟仿真器&#xff1a;1.3终端3&#xff0c;启动海龟控制节点&#xff1a; 二、查看系统中的计算图三、节点命令3.1查看节点下的命令rosnode3.2显示节点列表rosnode list3.3查看节…

【PyQt5】指示灯显示

【PyQt5】指示灯显示 1、背景2、代码示例3、QtDesigner绘制 1、背景 利用Qt5写工业控制软件交互界面的时候&#xff0c;经常需要在界面上有指示灯功能。 例如下面的明暗表示串行端口的连接和断开。 我们本质是用Qt5的label文本标签来实现的&#xff0c;即通过设置标签的样式表…

Homebrew安装及换源

Homebrew安装 官方源安装 直接在Terminal下执行命令&#x1f451; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 安装命令可直接在&#x1f37a;Homebrew官网查找&#xff1a; The Missing Package Manag…

Java的IO

1. Java中有几种类型的流 按照流的方向&#xff1a;输入流&#xff08;inputStream&#xff09;和输出流&#xff08;outputStream&#xff09;。 按照实现功能分&#xff1a;节点流&#xff08;可以从或向一个特定的地方&#xff08;节点&#xff09;读写数据。如 FileReade…

ERP系统介绍

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ERP系统概述&#xff1f;1.什么是ERP2.主流ERP系统介绍3.用友ERP4.部署用友ERP畅捷通T6软件系统环境要求4.用友ERP畅捷通T6软件用户管理4.用友ERP畅捷通T6软…

POI入门级操作excel文档的代码示例

本文介绍了使用Apache POI库操作文档的所有方法和代码示例。读者可以学习如何创建Excel文档、Sheet、行、单元格,以及如何设置单元格的值和样式,最后将Excel文档保存到磁盘上。使用POI操作Excel文件非常方便,本文详细介绍了这个过程中需要使用的类和方法,供读者参考使用。 …