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

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集成MongoDB和Swagger_第1张图片
    由上图可知,
    Spring WebFlux支持非关系型数据库,
    这一点与Spring MVC的差别还是很大的,
    如果需要使用关系型数据库,基于Spring MVC构建项目是最有选择,
    因此,基于Spring WebFlux构建项目,适用于非结构化存储。

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

3 项目依赖

版本:
SpringBoot2.4.5
Swagger:3.0.0

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


<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.0modelVersion>
	<parent>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-parentartifactId>
		<version>2.4.5version>
		<relativePath/> 
	parent>
	<groupId>com.monkeygroupId>
	<artifactId>spring-boot-templateartifactId>
	<version>0.0.1-SNAPSHOTversion>
	<name>spring-boot-templatename>
	<description>Template project for Spring Bootdescription>
	<properties>
		<java.version>1.8java.version>
		<io.springfox.version>3.0.0io.springfox.version>
		<io.swagger.version>1.5.22io.swagger.version>
		<fastjson.version>1.2.60fastjson.version>
		<logback.version>1.2.3logback.version>
	properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webfluxartifactId>
		dependency>

		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-testartifactId>
			<scope>testscope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintagegroupId>
					<artifactId>junit-vintage-engineartifactId>
				exclusion>
			exclusions>
		dependency>
		
		<dependency>
			<groupId>io.springfoxgroupId>
			<artifactId>springfox-boot-starterartifactId>
			<version>${io.springfox.version}version>
		dependency>
		<dependency>
			<groupId>io.springfoxgroupId>
			<artifactId>springfox-swagger2artifactId>
			<version>${io.springfox.version}version>
			<exclusions>
				<exclusion>
					<groupId>io.swaggergroupId>
					<artifactId>swagger-annotationsartifactId>
				exclusion>
				<exclusion>
					<groupId>io.swaggergroupId>
					<artifactId>swagger-modelsartifactId>
				exclusion>
			exclusions>
		dependency>
		<dependency>
			<groupId>io.springfoxgroupId>
			<artifactId>springfox-swagger-uiartifactId>
			<version>${io.springfox.version}version>
		dependency>
		<dependency>
			<groupId>io.swaggergroupId>
			<artifactId>swagger-annotationsartifactId>
			<version>${io.swagger.version}version>
		dependency>
		<dependency>
			<groupId>io.swaggergroupId>
			<artifactId>swagger-modelsartifactId>
			<version>${io.swagger.version}version>
		dependency>
		<dependency>
			<groupId>com.alibabagroupId>
			<artifactId>fastjsonartifactId>
			<version>${fastjson.version}version>
		dependency>
		
		<dependency>
			<groupId>ch.qos.logbackgroupId>
			<artifactId>logback-coreartifactId>
			<version>${logback.version}version>
		dependency>
		<dependency>
			<groupId>ch.qos.logbackgroupId>
			<artifactId>logback-classicartifactId>
			<version>${logback.version}version>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-aopartifactId>
		dependency>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-data-mongodb-reactiveartifactId>
		dependency>
	dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.bootgroupId>
				<artifactId>spring-boot-maven-pluginartifactId>
			plugin>
		plugins>
	build>
project>

4 数据库

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

4.1 MongoDB配置

spring:
  devtools:
    restart:
      enabled: true
  data:
    mongodb:
      uri: mongodb://tutorial:[email protected]: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自动生成
     */
    @Id
    private 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;
    }

    @Override
    public 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);

    @Autowired
    private ReactiveMongoTemplate mongoTemplate;

    @Override
    public 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())));
    }

    @Override
    public 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))
        );
    }

    @Override
    public 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())));
    }

    @Override
    public 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));
        });
    }

    @Override
    public 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 {

    @Resource
    IUserService 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 {

    @Bean
    public 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", "[email protected]", "[email protected]"))
                .version("1.0")
                .description("个人信息")
                .termsOfServiceUrl("http://localhost:9121/api/v1")
                .build();
    }
}

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

响应式编程实战:Spring WebFlux集成MongoDB和Swagger_第2张图片

8 小结

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

你可能感兴趣的:(#,MongoDB,#,Spring,webflux,mongodb,swagger,响应式)