Responsive programming practice: Spring WebFlux integrates MongoDB and Swagger

1 Dependence

New project, rapid iteration,
technology selection: Spring WebFlux,
not Spring MVC,
I have never touched Spring WebFlux before, and all projects use Spring MVC.
I learned new knowledge Spring WebFlux this time and recorded it.

2 SpringMVC & Spring WebFlux

Spring products provide two parallel technical routes:

  • Servlet API route based on Spring MVC and Spring Data
  • Reactive stream route based on Spring WebFlux and Spring Data Reactive
    : https://spring.io/reactive
    Reactive system is an ideal solution for low-latency, high-throughput workloads.
    WebFlux streams everything.

Let's take a look at the comparison between the two technical routes of Spring WebFlux and Spring MVC:
Source: https://spring.io/reactive
insert image description here
As can be seen from the above figure,
Spring WebFlux supports non-relational databases,
which is still quite different from Spring MVC. Large,
if you need to use a relational database, building projects based on Spring MVC is the best choice.
Therefore, building projects based on Spring WebFlux is suitable for unstructured storage.

This article mainly explains the application, and the details of responsive programming will be shared in other articles.

3 project dependencies

Version:
SpringBoot2.4.5
Swagger: 3.0.0

Here, use spring-boot-starter-webflux as the startup container (default is 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 databases

This article chooses MongoDB as the persistence layer.
MongoDB deployment: https://blog.csdn.net/Xin_101/article/details/130902224

4.1 MongoDB configuration

spring:
  devtools:
    restart:
      enabled: true
  data:
    mongodb:
      uri: mongodb://tutorial:[email protected]:27017/tutorial?authSource=tutorial

4.2 Document configuration

MongoDB uses Document as the basic unit for storing data. In contrast, for tables in MySQL, newly created data needs to clearly specify the stored Document. Next,
create a new user document to store user data.
Among them, @Id user indicates that the data is an attribute in MongDB _idand generated by MongoDB.
However, there is no automatic data mapping when using MongoDB to operate data, and manual mapping is required. Therefore, a new static final attribute name is created under each field that needs to be mapped.
For example _id, username, used when manipulating data.

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 uses Mono or Flux as the return object.
Therefore, use Mono or Flux directly as the final result type in the Service layer
instead of encapsulating the final result in the Controller layer.
WebFlux: everything is streaming data, so the coding is very elegant and can be To achieve waterfall programming, the data flows down layer by layer, and finally the result is output.

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

The interface layer directly uses Mono to encapsulate the result.

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 Configure Swagger

Swagger uses version 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();
    }
}

Visit: http://localhost:9121/swagger-ui/index.htm

insert image description here

8 Summary

The selection of technology depends on the actual situation. It is not to introduce technology for the sake of introducing technology, but to introduce technology to solve problems.
(1) Spring WebFlux uses Mono or Flux as the return type;
(2) Everything in Spring WebFlux flows, and the data is passed layer by layer through the flow, and the programming method is very elegant: waterfall; (
3) Spring WebFlux uses Swagger3. 0.0, when testing, using Swagger2.9.2 cannot generate documents;
(4) Spring WebFlux is the first choice for responsive programming, low-latency, high-throughput systems, but the choice of data layer is limited, such as only supporting limited non-relational Database: MongoDB, Redis.

Guess you like

Origin blog.csdn.net/Xin_101/article/details/130918666