• WebFlux04 SpringBootWebFlux集成MongoDB之Windows版本、WebFlux实现CRUD、WebFlux实现JPA、参数校验


    1 下载并安装MongoDB

      1.1 MongoDB官网

        

      1.2 下载

        solutions -> download center

        

      1.3 安装

        双击进入安装即可

        1.3.1 安装时常见bug01

          

        1.3.2 bug01解决办法

          

      1.4 启动mongodb

        技巧01:需要在同安装目录同一级别创建一个data目录来存放数据

        技巧02:将下下面的命令存储成一个 bat 文件,下次启动时双击即可

    C:	oolmongoDBinmongod --dbpath C:	ooldata --smallfiles

      1.5 mongodb正常启动后的控制台信息

        

      1.6 启动MongoDB客户端

        双击mongoDB安装目录下 -> bin -> mongo.exe

        

        1.6.1 常用命令

          show databases -> 显示数据库

          use 数据库名称 -> 更换当前数据库

          show tables -> 查看当前数据库中的数据表

          db.表名.find() -> 查看某个表中的所有数据

          db.表名.find().pretty() -> 查看某个表中的所有数据并进行格式化输出

          

    2 SpringBootWebFlux集成MongoDB

      2.1 创建一个项目

        引入相关依赖:webflux、mongodb、devtool、lombok

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>cn.xiangxu</groupId>
        <artifactId>webflux_demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>webflux_demo</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.3.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.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-devtools</artifactId>
                <!--<scope>runtime</scope>-->
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-mongodb-reactive -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
            </dependency>
    
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>io.projectreactor</groupId>
                <artifactId>reactor-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-api</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <fork>true</fork>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    
    </project>
    pom.xml

      2.2 启动类

        在启动类上添加 @EnableReactiveMongoRepositories 注解来开启mongodb相关的配置

    package cn.xiangxu.webflux_demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
    
    @SpringBootApplication
    @EnableReactiveMongoRepositories
    public class WebfluxDemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(WebfluxDemoApplication.class, args);
        }
    }
    View Code

      2.3 实体类

        @Document(collection = "user") 目的时定义在mongodb中的表名,相当于JPA中的@Table注解

        技巧01:在mongodb中的主键一般都是String类型的

    package cn.xiangxu.webflux_demo.domain;
    
    import lombok.Data;
    import org.springframework.data.annotation.Id;
    import org.springframework.data.mongodb.core.mapping.Document;
    
    /**
     * @author 王杨帅
     * @create 2018-06-27 8:42
     * @desc
     **/
    @Document(collection = "user")
    @Data
    public class User {
        @Id
        private String id;
    
        private String name;
    
        private int age;
    }
    User.java

      2.4 持久层

        只需要继承 ReactiveMongoRepository 接口就行啦,和JPA差不多

    package cn.xiangxu.webflux_demo.repository;
    
    import cn.xiangxu.webflux_demo.domain.User;
    import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
    import org.springframework.stereotype.Repository;
    
    /**
     * @author 王杨帅
     * @create 2018-06-27 8:44
     * @desc
     **/
    @Repository
    public interface UserRepository extends ReactiveMongoRepository<User, String> {
    }
    UserRepository.java

      2.5 控制层

        技巧01:有两种返回方式,一种是把所有数据一次性返回,另一种是像流一样的进行返回

    package cn.xiangxu.webflux_demo.web;
    
    import cn.xiangxu.webflux_demo.domain.User;
    import cn.xiangxu.webflux_demo.repository.UserRepository;
    import org.springframework.http.MediaType;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import reactor.core.publisher.Flux;
    
    /**
     * @author 王杨帅
     * @create 2018-06-27 8:45
     * @desc
     **/
    @RestController
    @RequestMapping(value = "/user")
    public class UserController {
    
        private final UserRepository userRepository;
    
        /**
         * 利用构造器注入持久层对象
         * @param userRepository
         */
        public UserController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        /**
         * 一次性返回
         * @return
         */
        @GetMapping(value = "/")
        public Flux<User> getAll() {
            return userRepository.findAll();
        }
    
        /**
         * 流式返回
         * @return
         */
        @GetMapping(value = "/stream/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<User> streamGetAll() {
            return userRepository.findAll();
        }
    
    
    }
    UserController.java

      2.6 配置文件

        前提:安装好mongodb并启动

    spring.data.mongodb.uri=mongodb://localhost:27017/webflux

      2.7 启动并测试

        技巧01:利用postman进行测试

        

    3 WebFlux实现CRUD

      准备:SpringBootWebFlux项目搭建以及SpringBootWebFlux集成MongoDB请参见上面的

      3.1 新增

        ReactiveCrudRepository 接口中的 save 方法可以实现更新和新增操作

        技巧01:利用save方法进行更新操作时,如果接收到的ID在mongodb中没有对应的记录就会执行新增操作,而且新增数据的ID就是传过来的ID信息

        技巧02:利用save方法进行新增操作时,不需要前端传ID信息,mongodb会自动根据实体类生成ID信息;如果传了ID信息就会变成更新操作了

    <S extends T> Mono<S> save(S var1);
        /**
         * 新增用户
         * @param user
         * @return
         */
        @PostMapping
        public Mono<User> createUser(@RequestBody User user
        ) {
            return userRepository.save(user);
    
            /**
             * Note
             *  1 save 可以修改和新增,如果有ID就是修改【该ID存在,如果该ID不存在直接新增】,没有就是新增
             */
        }
    View Code

        

      3.2 删除

        需求:根据前端传过来的ID信息删除用户信息,如果删除成功就返回200状态码,删除失败就返回404状态码

        坑01:ReactiveCrudRepository接口提供的一系列delete方法都没有返回自,所以不确定是否已经删除成功

        Mono<Void> deleteById(ID var1);
    
        Mono<Void> deleteById(Publisher<ID> var1);
    
        Mono<Void> delete(T var1);
    
        Mono<Void> deleteAll(Iterable<? extends T> var1);
    
        Mono<Void> deleteAll(Publisher<? extends T> var1);
    
        Mono<Void> deleteAll();
    View Code

        解坑01:根据前端I传过来的ID查询信息 -> 查到就进行删除操作 -> 返回200状态码

                           -> 查不到就直接返回404状态码

        技巧01:map和flatMap的使用时机

          当要操作数据并返回Mono时使用flatMap; 如果不操作数据,仅仅转换数据时使用Map

        技巧02:如果一个stream没有返回值但方法有要求有返回值时,可以利用then来返回数据

        /**
         * 删除用户
         *  需求:删除成功后返回200状态码,删除失败就返回404状态码
         * @param id
         * @return
         */
        @DeleteMapping(value = "/{id}")
        public Mono<ResponseEntity<Void>> deleteUser(@PathVariable("id") String id) {
    //        userRepository.deleteById(id); // deleteById 方法没有返回值,我们无法知道是否删除成功
            return userRepository.findById(id)
                    .flatMap( // 根据ID找到数据进行删除操作,并返回200状态码
                            user -> userRepository.delete(user)
                                    .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
                    )
                    .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND)); // 如果没有查找到数据就返回404状态码
    
            /**
             * Note
             *  1 map和flatMap的使用时机
             *      当要操作数据并返回Mono时使用flatMap
             *      如果不操作数据,仅仅转换数据时使用Map
             *  2 如果一个stream没有返回值但方法有要求有返回值时,可以利用then来返回数据
             */
    
        }
    View Code

      3.3 更新

        需求:根据前端传过来的ID和更新数据类进行更新操作;更新成功后返回200状态码和更新后的数据,更新失败后就返回404状态码

        坑01:直接利用save方法进行更新操作时容易产生歧义,因为save方法可以进行更新和删除操作;当接收到对象没有id信息时就进行新增操作,如果有ID信息而且数据库有该ID对应的数据时就进行更新操作,如果有ID信息但是数据库中没有改ID对应的数据时也会进行新增操作

        解坑01: 根据前端传过来的ID查询数据 -> 查到数据就进行更新操作 -> 返回200状态码和更新过后的数据

                           -> 没查到数据就返回404状态码

        /**
         * 修改数据
         *      修改成功返回200和修改成功后的数据,不存在时返回404
         * @param id 要修改的用户ID
         * @param user 修改数据
         * @return
         */
        @PutMapping(value = "/{id}")
        public Mono<ResponseEntity<User>> updateUser(
                @PathVariable("id") String id,
                @RequestBody User user
        ) {
            return userRepository.findById(id)
                    .flatMap( // 操作数据
                            u -> {
                                u.setAge(user.getAge());
                                u.setName(user.getName());
                                return userRepository.save(u);
                            }
                    )
                    .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                    .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
        }
    View Code

      3.4 查询

        需求:根据前端传过来的ID查询数据,如果查到就直接返回200状态码和查到的数据,如果不存在该ID对应的数据就直接返回404状态码

        /**
         * 根据ID查找用户
         *      存在时返回200和查到的数据,不存在时就返回404
         * @param id 用户ID
         * @return
         */
        @GetMapping(value = "/{id}")
        public Mono<ResponseEntity<User>> getById(@PathVariable("id") String id) {
    
            return userRepository.findById(id)
                    .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                    .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    
        }
    View Code

      ·3.5 代码汇总

    package cn.xiangxu.webflux_demo.web;
    
    import cn.xiangxu.webflux_demo.domain.User;
    import cn.xiangxu.webflux_demo.repository.UserRepository;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    /**
     * @author 王杨帅
     * @create 2018-06-27 8:45
     * @desc
     **/
    @RestController
    @RequestMapping(value = "/user")
    public class UserController {
    
        private final UserRepository userRepository;
    
        /**
         * 利用构造器注入持久层对象
         * @param userRepository
         */
        public UserController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        /**
         * 以数组形式一次性返回
         * @return
         */
        @GetMapping(value = "/")
        public Flux<User> getAll() {
            return userRepository.findAll();
        }
    
        /**
         * 以SSE形式流式返回
         * @return
         */
        @GetMapping(value = "/stream/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<User> streamGetAll() {
            return userRepository.findAll();
        }
    
        /**
         * 新增用户
         * @param user
         * @return
         */
        @PostMapping
        public Mono<User> createUser(@RequestBody User user
        ) {
            return userRepository.save(user);
    
            /**
             * Note
             *  1 save 可以修改和新增,如果有ID就是修改【该ID存在,如果该ID不存在直接新增】,没有就是新增
             */
        }
    
        /**
         * 删除用户
         *  需求:删除成功后返回200状态码,删除失败就返回404状态码
         * @param id
         * @return
         */
        @DeleteMapping(value = "/{id}")
        public Mono<ResponseEntity<Void>> deleteUser(@PathVariable("id") String id) {
    //        userRepository.deleteById(id); // deleteById 方法没有返回值,我们无法知道是否删除成功
            return userRepository.findById(id)
                    .flatMap( // 根据ID找到数据进行删除操作,并返回200状态码
                            user -> userRepository.delete(user)
                                    .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
                    )
                    .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND)); // 如果没有查找到数据就返回404状态码
    
            /**
             * Note
             *  1 map和flatMap的使用时机
             *      当要操作数据并返回Mono时使用flatMap
             *      如果不操作数据,仅仅转换数据时使用Map
             *  2 如果一个stream没有返回值但方法有要求有返回值时,可以利用then来返回数据
             */
    
        }
    
        /**
         * 修改数据
         *      修改成功返回200和修改成功后的数据,不存在时返回404
         * @param id 要修改的用户ID
         * @param user 修改数据
         * @return
         */
        @PutMapping(value = "/{id}")
        public Mono<ResponseEntity<User>> updateUser(
                @PathVariable("id") String id,
                @RequestBody User user
        ) {
            return userRepository.findById(id)
                    .flatMap( // 操作数据
                            u -> {
                                u.setAge(user.getAge());
                                u.setName(user.getName());
                                return userRepository.save(u);
                            }
                    )
                    .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                    .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
        }
    
        /**
         * 根据ID查找用户
         *      存在时返回200和查到的数据,不存在时就返回404
         * @param id 用户ID
         * @return
         */
        @GetMapping(value = "/{id}")
        public Mono<ResponseEntity<User>> getById(@PathVariable("id") String id) {
    
            return userRepository.findById(id)
                    .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                    .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    
        }
    
        /**
         * 根据年龄段查询:数组形式返回
         * @param start 最小年龄
         * @param end 最大年龄
         * @return
         */
        @GetMapping(value = "/age/{start}/{end}")
        public Flux<User> findByAge(
                @PathVariable("start") Integer start,
                @PathVariable("end") Integer end
        ) {
            return userRepository.findByAgeBetween(start, end);
        }
    
        /**
         * 根据年龄段查询:流式返回
         * @param start
         * @param end
         * @return
         */
        @GetMapping(value = "/stream/age/{start}/{end}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<User> streamFindByAge(
                @PathVariable("start") Integer start,
                @PathVariable("end") Integer end
        ) {
            return userRepository.findByAgeBetween(start, end);
        }
    
    
        /**
         * 查询年龄在20-30的用户
         * @return
         */
        @GetMapping(value = "/age/oldUser")
        public Flux<User> oldUser() {
            return userRepository.oldUser();
        }
    
    
        /**
         * 查询年龄在20-30的用户
         * @return
         */
        @GetMapping(value = "/stream/age/oldUser", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<User> streamFindByAge() {
            return userRepository.oldUser();
        }
    
    
    
    }
    View Code

      

    4 WebFlux实现JPA

      ReactiveCrudRepository接口除了提供简单的CRUD操作外,还可以进行自定义数据操作方法,但是自定义方法的方法名有一定的要求;如果定义的方法名不满足 SpringData JPA 也可以利用 @Query 注解使用原生的注解进行实现

      4.1 符合JPA规范的写法

        4.1.1 持久层

        /**
         * 根据年龄段查询用户【PS: 不包括端点值】
         * @param start
         * @param end
         * @return
         */
        Flux<User> findByAgeBetween(Integer start, Integer end);
    package cn.xiangxu.webflux_demo.repository;
    
    import cn.xiangxu.webflux_demo.domain.User;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.data.mongodb.repository.Query;
    import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
    import org.springframework.stereotype.Repository;
    import reactor.core.publisher.Flux;
    
    /**
     * @author 王杨帅
     * @create 2018-06-27 8:44
     * @desc
     **/
    @Repository
    public interface UserRepository extends ReactiveMongoRepository<User, String> {
    
        /**
         * 根据年龄段查询用户【PS: 不包括端点值】
         * @param start
         * @param end
         * @return
         */
        Flux<User> findByAgeBetween(Integer start, Integer end);
    
        /**
         * 查询年龄在20-30的用户【PS: 包括端点值】
         *      利用MongoDB的 SQL 语句实现
         * @return
         */
        @Query("{'age':{'$gte':20, '$lte':30}}")
        Flux<User> oldUser();
    
    }
    View Code

        4.1.2 控制层

        /**
         * 根据年龄段查询:数组形式返回
         * @param start 最小年龄
         * @param end 最大年龄
         * @return
         */
        @GetMapping(value = "/age/{start}/{end}")
        public Flux<User> findByAge(
                @PathVariable("start") Integer start,
                @PathVariable("end") Integer end
        ) {
            return userRepository.findByAgeBetween(start, end);
        }
    
        /**
         * 根据年龄段查询:流式返回
         * @param start
         * @param end
         * @return
         */
        @GetMapping(value = "/stream/age/{start}/{end}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<User> streamFindByAge(
                @PathVariable("start") Integer start,
                @PathVariable("end") Integer end
        ) {
            return userRepository.findByAgeBetween(start, end);
        }
    View Code

      4.2 @Query的写法

        4.2.1 持久层

        /**
         * 查询年龄在20-30的用户【PS: 包括端点值】
         *      利用MongoDB的 SQL 语句实现
         * @return
         */
        @Query("{'age':{'$gte':20, '$lte':30}}")
        Flux<User> oldUser();
    package cn.xiangxu.webflux_demo.repository;
    
    import cn.xiangxu.webflux_demo.domain.User;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.data.mongodb.repository.Query;
    import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
    import org.springframework.stereotype.Repository;
    import reactor.core.publisher.Flux;
    
    /**
     * @author 王杨帅
     * @create 2018-06-27 8:44
     * @desc
     **/
    @Repository
    public interface UserRepository extends ReactiveMongoRepository<User, String> {
    
        /**
         * 根据年龄段查询用户【PS: 不包括端点值】
         * @param start
         * @param end
         * @return
         */
        Flux<User> findByAgeBetween(Integer start, Integer end);
    
        /**
         * 查询年龄在20-30的用户【PS: 包括端点值】
         *      利用MongoDB的 SQL 语句实现
         * @return
         */
        @Query("{'age':{'$gte':20, '$lte':30}}")
        Flux<User> oldUser();
    
    }
    View Code

        4.2.2 控制层

        /**
         * 查询年龄在20-30的用户
         * @return
         */
        @GetMapping(value = "/age/oldUser")
        public Flux<User> oldUser() {
            return userRepository.oldUser();
        }
    
    
        /**
         * 查询年龄在20-30的用户
         * @return
         */
        @GetMapping(value = "/stream/age/oldUser", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<User> streamFindByAge() {
            return userRepository.oldUser();
        }
    View Code

      4.3 代码汇总

        4.3.1 持久层

    package cn.xiangxu.webflux_demo.repository;
    
    import cn.xiangxu.webflux_demo.domain.User;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.data.mongodb.repository.Query;
    import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
    import org.springframework.stereotype.Repository;
    import reactor.core.publisher.Flux;
    
    /**
     * @author 王杨帅
     * @create 2018-06-27 8:44
     * @desc
     **/
    @Repository
    public interface UserRepository extends ReactiveMongoRepository<User, String> {
    
        /**
         * 根据年龄段查询用户【PS: 不包括端点值】
         * @param start
         * @param end
         * @return
         */
        Flux<User> findByAgeBetween(Integer start, Integer end);
    
        /**
         * 查询年龄在20-30的用户【PS: 包括端点值】
         *      利用MongoDB的 SQL 语句实现
         * @return
         */
        @Query("{'age':{'$gte':20, '$lte':30}}")
        Flux<User> oldUser();
    
    }
    View Code

        4.3.2 控制层

    package cn.xiangxu.webflux_demo.web;
    
    import cn.xiangxu.webflux_demo.domain.User;
    import cn.xiangxu.webflux_demo.repository.UserRepository;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    /**
     * @author 王杨帅
     * @create 2018-06-27 8:45
     * @desc
     **/
    @RestController
    @RequestMapping(value = "/user")
    public class UserController {
    
        private final UserRepository userRepository;
    
        /**
         * 利用构造器注入持久层对象
         * @param userRepository
         */
        public UserController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        /**
         * 以数组形式一次性返回
         * @return
         */
        @GetMapping(value = "/")
        public Flux<User> getAll() {
            return userRepository.findAll();
        }
    
        /**
         * 以SSE形式流式返回
         * @return
         */
        @GetMapping(value = "/stream/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<User> streamGetAll() {
            return userRepository.findAll();
        }
    
        /**
         * 新增用户
         * @param user
         * @return
         */
        @PostMapping
        public Mono<User> createUser(@RequestBody User user
        ) {
            return userRepository.save(user);
    
            /**
             * Note
             *  1 save 可以修改和新增,如果有ID就是修改【该ID存在,如果该ID不存在直接新增】,没有就是新增
             */
        }
    
        /**
         * 删除用户
         *  需求:删除成功后返回200状态码,删除失败就返回404状态码
         * @param id
         * @return
         */
        @DeleteMapping(value = "/{id}")
        public Mono<ResponseEntity<Void>> deleteUser(@PathVariable("id") String id) {
    //        userRepository.deleteById(id); // deleteById 方法没有返回值,我们无法知道是否删除成功
            return userRepository.findById(id)
                    .flatMap( // 根据ID找到数据进行删除操作,并返回200状态码
                            user -> userRepository.delete(user)
                                    .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
                    )
                    .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND)); // 如果没有查找到数据就返回404状态码
    
            /**
             * Note
             *  1 map和flatMap的使用时机
             *      当要操作数据并返回Mono时使用flatMap
             *      如果不操作数据,仅仅转换数据时使用Map
             *  2 如果一个stream没有返回值但方法有要求有返回值时,可以利用then来返回数据
             */
    
        }
    
        /**
         * 修改数据
         *      修改成功返回200和修改成功后的数据,不存在时返回404
         * @param id 要修改的用户ID
         * @param user 修改数据
         * @return
         */
        @PutMapping(value = "/{id}")
        public Mono<ResponseEntity<User>> updateUser(
                @PathVariable("id") String id,
                @RequestBody User user
        ) {
            return userRepository.findById(id)
                    .flatMap( // 操作数据
                            u -> {
                                u.setAge(user.getAge());
                                u.setName(user.getName());
                                return userRepository.save(u);
                            }
                    )
                    .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                    .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
        }
    
        /**
         * 根据ID查找用户
         *      存在时返回200和查到的数据,不存在时就返回404
         * @param id 用户ID
         * @return
         */
        @GetMapping(value = "/{id}")
        public Mono<ResponseEntity<User>> getById(@PathVariable("id") String id) {
    
            return userRepository.findById(id)
                    .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                    .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    
        }
    
        /**
         * 根据年龄段查询:数组形式返回
         * @param start 最小年龄
         * @param end 最大年龄
         * @return
         */
        @GetMapping(value = "/age/{start}/{end}")
        public Flux<User> findByAge(
                @PathVariable("start") Integer start,
                @PathVariable("end") Integer end
        ) {
            return userRepository.findByAgeBetween(start, end);
        }
    
        /**
         * 根据年龄段查询:流式返回
         * @param start
         * @param end
         * @return
         */
        @GetMapping(value = "/stream/age/{start}/{end}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<User> streamFindByAge(
                @PathVariable("start") Integer start,
                @PathVariable("end") Integer end
        ) {
            return userRepository.findByAgeBetween(start, end);
        }
    
    
        /**
         * 查询年龄在20-30的用户
         * @return
         */
        @GetMapping(value = "/age/oldUser")
        public Flux<User> oldUser() {
            return userRepository.oldUser();
        }
    
    
        /**
         * 查询年龄在20-30的用户
         * @return
         */
        @GetMapping(value = "/stream/age/oldUser", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<User> streamFindByAge() {
            return userRepository.oldUser();
        }
    
    
    
    }
    View Code

    5 参数校验

      技巧01:参数校验和MVC模式相同,只需要在实体类的成员属性上添加相应的注解即可;然后在请求控制方法的参数上加上@Valid即可

      5.1 实体类

    package cn.xiangxu.webflux_test.domain.domain_do;
    
    import lombok.Data;
    import org.hibernate.validator.constraints.Range;
    import org.springframework.data.annotation.Id;
    import org.springframework.data.mongodb.core.mapping.Document;
    
    import javax.validation.constraints.NotBlank;
    
    /**
     * @author 王杨帅
     * @create 2018-08-02 15:30
     * @desc 学生实体类
     **/
    @Document(collection = "student")
    @Data
    public class StudentDO {
        @Id
        private String id;
    
        @NotBlank
        private String name;
        private String address;
        @Range(min = 12, max = 50)
        private Integer age;
    }
    StudentDO.java

      5.2 持久层

    package cn.xiangxu.webflux_test.reposigory;
    
    import cn.xiangxu.webflux_test.domain.domain_do.StudentDO;
    import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
    import org.springframework.stereotype.Repository;
    
    /**
     * @author 王杨帅
     * @create 2018-08-02 15:31
     * @desc 学生持久层
     **/
    @Repository
    public interface StudentRepository extends ReactiveMongoRepository<StudentDO, String> {
    }
    StudentRepository.java

      5.3 控制层

        坑01:在MVC模式时可以在控制方法上使用  BindingResult ,但是在 WebFlux 模式下不可以使用;只能通过创建切面进行异常捕获

        技巧02:添加了@Valid注解后如果有参数不合法时抛出的异常是 WebExchangeBindException

    package cn.xiangxu.webflux_test.controller;
    
    import cn.xiangxu.webflux_test.domain.domain_do.StudentDO;
    import cn.xiangxu.webflux_test.reposigory.StudentRepository;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import javax.validation.Valid;
    
    import static cn.xiangxu.webflux_test.util.CheckUtil.checkeName;
    
    /**
     * @author 王杨帅
     * @create 2018-08-02 15:44
     * @desc 学生控制层
     **/
    @RestController
    @RequestMapping(value = "/stu")
    @Slf4j
    public class StudentController {
    
        @Autowired
        private StudentRepository studentRepository;
    
        @GetMapping
        public Flux<StudentDO> findList() {
            return studentRepository.findAll();
        }
    
        @PostMapping
        public Mono<StudentDO> create(
                @Valid @RequestBody StudentDO studentDO) {
            System.out.println(studentDO);
            checkeName(studentDO.getName());
            return studentRepository.save(studentDO);
        }
    
        @PutMapping()
        public Mono<ResponseEntity<String>> update(
                @Valid @RequestBody StudentDO studentDO
        ) {
            log.info("前端传过来的数据为:" + studentDO);
            checkeName(studentDO.getName());
            return studentRepository.findById(studentDO.getId())
                    .flatMap(student -> studentRepository.save(studentDO))
                    .map(student -> new ResponseEntity<String>("更新成功", HttpStatus.OK))
                    .defaultIfEmpty(new ResponseEntity<String>("ID不合法", HttpStatus.BAD_REQUEST));
        }
    
        @DeleteMapping(value = "/{id}")
        public Mono<ResponseEntity<Void>> delete(
                @PathVariable(value = "id") String id
        ) {
            log.info("从前端获取到的ID信息为:" + id);
            return studentRepository.findById(id)
                    .flatMap(
                            student -> {
                                return studentRepository.deleteById(student.getId())
                                        .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)));
                            }
                    )
                    .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
        }
    
        @GetMapping(value = "/{id}")
        public Mono<ResponseEntity<StudentDO>> findById(
                @PathVariable("id") String id
        ) {
            log.info("从前端获取到的参数信息为:" + id);
            return studentRepository.findById(id)
                    .map(student -> new ResponseEntity<StudentDO>(student, HttpStatus.OK))
                    .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
        }
    
    }
    StudentController.java

      5.4 编写异常处理切面

    package cn.xiangxu.webflux_test.exception.handler;
    
    import cn.xiangxu.webflux_test.exception.CheckException;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.support.WebExchangeBindException;
    
    import java.util.Optional;
    
    /**
     * @author 王杨帅
     * @create 2018-08-02 16:31
     * @desc
     **/
    @ControllerAdvice
    @Slf4j
    public class CheckAdvice {
        @ExceptionHandler(WebExchangeBindException.class)
        public ResponseEntity handleWebExchangeBindException(WebExchangeBindException e) {
            log.error(e.getMessage());
    
            return new ResponseEntity<String>(toStr(e), HttpStatus.BAD_REQUEST);
        }
    
        @ExceptionHandler(CheckException.class)
        public ResponseEntity handleCheckException(CheckException e) {
            log.error(e.getMessage());
            return new ResponseEntity<String>(toStr(e), HttpStatus.BAD_REQUEST);
        }
    
        private String toStr(CheckException e) {
            return e.getFiledName() + " : " + e.getFiledValue();
        }
    
        /**
         * 把校验异常转化成字符串
         * @param e
         * @return
         */
        private String toStr(WebExchangeBindException e) {
            return e.getFieldErrors().stream()
                    .map(error -> error.getField() + " : " + error.getDefaultMessage())
                    .reduce("", (s1, s2) -> s1 + " 
     " + s2);
        }
    }
    CheckAdvice.java

      5.5 自定义校验方法

        自定义校验方法有两种方式,一种自定义一个参数校验注解,另外一种是自定义一个参数校验方法【PS: 本博文基于后者】

         5.5.1 自定义异常

    package cn.xiangxu.webflux_test.exception;
    
    import lombok.Data;
    
    /**
     * @author 王杨帅
     * @create 2018-08-02 17:01
     * @desc 检查异常
     **/
    @Data
    public class CheckException extends RuntimeException  {
    
        /**
         * 出错字段
         */
        private String filedName;
    
        /**
         * 出错值
         */
        private String filedValue;
    
        public CheckException(String message, String filedName, String filedValue) {
            super(message);
            this.filedName = filedName;
            this.filedValue = filedValue;
        }
    
        public CheckException(String filedName, String filedValue) {
            this.filedName = filedName;
            this.filedValue = filedValue;
        }
    }
    CheckException.java

        5.5.2 编写捕获自定义异常的切面

    package cn.xiangxu.webflux_test.exception.handler;
    
    import cn.xiangxu.webflux_test.exception.CheckException;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.support.WebExchangeBindException;
    
    import java.util.Optional;
    
    /**
     * @author 王杨帅
     * @create 2018-08-02 16:31
     * @desc
     **/
    @ControllerAdvice
    @Slf4j
    public class CheckAdvice {
        @ExceptionHandler(WebExchangeBindException.class)
        public ResponseEntity handleWebExchangeBindException(WebExchangeBindException e) {
            log.error(e.getMessage());
    
            return new ResponseEntity<String>(toStr(e), HttpStatus.BAD_REQUEST);
        }
    
        @ExceptionHandler(CheckException.class)
        public ResponseEntity handleCheckException(CheckException e) {
            log.error(e.getMessage());
            return new ResponseEntity<String>(toStr(e), HttpStatus.BAD_REQUEST);
        }
    
        private String toStr(CheckException e) {
            return e.getFiledName() + " : " + e.getFiledValue();
        }
    
        /**
         * 把校验异常转化成字符串
         * @param e
         * @return
         */
        private String toStr(WebExchangeBindException e) {
            return e.getFieldErrors().stream()
                    .map(error -> error.getField() + " : " + error.getDefaultMessage())
                    .reduce("", (s1, s2) -> s1 + " 
     " + s2);
        }
    }
    CheckAdvice.java

        5 .5.3 自定义校验方法

    package cn.xiangxu.webflux_test.util;
    
    import cn.xiangxu.webflux_test.exception.CheckException;
    
    import java.util.Arrays;
    import java.util.stream.Stream;
    
    /**
     * @author 王杨帅
     * @create 2018-08-02 17:00
     * @desc 检查工具类
     **/
    public class CheckUtil {
    
        private static final String[] INVALID_NAMES = {"admin", "fury"};
    
        /**
         * 校验名字:不成功时抛出自定义异常
         * @param value
         */
        public static void checkeName(String value) {
            Stream.of(INVALID_NAMES)
                    .filter(name -> name.equalsIgnoreCase(value))
                    .findAny()
                    .ifPresent(
                            name -> {
                                throw new CheckException("name", value);
                            }
                    );
        }
    
    }
    CheckUtil.java

        5.5.4 使用自定义参数校验方法

    package cn.xiangxu.webflux_test.controller;
    
    import cn.xiangxu.webflux_test.domain.domain_do.StudentDO;
    import cn.xiangxu.webflux_test.reposigory.StudentRepository;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.*;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import javax.validation.Valid;
    
    import static cn.xiangxu.webflux_test.util.CheckUtil.checkeName;
    
    /**
     * @author 王杨帅
     * @create 2018-08-02 15:44
     * @desc 学生控制层
     **/
    @RestController
    @RequestMapping(value = "/stu")
    @Slf4j
    public class StudentController {
    
        @Autowired
        private StudentRepository studentRepository;
    
        @GetMapping
        public Flux<StudentDO> findList() {
            return studentRepository.findAll();
        }
    
        @PostMapping
        public Mono<StudentDO> create(
                @Valid @RequestBody StudentDO studentDO) {
            System.out.println(studentDO);
            checkeName(studentDO.getName());
            return studentRepository.save(studentDO);
        }
    
        @PutMapping()
        public Mono<ResponseEntity<String>> update(
                @Valid @RequestBody StudentDO studentDO
    
        ) {
            log.info("前端传过来的数据为:" + studentDO);
            checkeName(studentDO.getName());
            return studentRepository.findById(studentDO.getId())
                    .flatMap(student -> studentRepository.save(studentDO))
                    .map(student -> new ResponseEntity<String>("更新成功", HttpStatus.OK))
                    .defaultIfEmpty(new ResponseEntity<String>("ID不合法", HttpStatus.BAD_REQUEST));
        }
    
        @DeleteMapping(value = "/{id}")
        public Mono<ResponseEntity<Void>> delete(
                @PathVariable(value = "id") String id
        ) {
            log.info("从前端获取到的ID信息为:" + id);
            return studentRepository.findById(id)
                    .flatMap(
                            student -> {
                                return studentRepository.deleteById(student.getId())
                                        .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)));
                            }
                    )
                    .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
        }
    
        @GetMapping(value = "/{id}")
        public Mono<ResponseEntity<StudentDO>> findById(
                @PathVariable("id") String id
        ) {
            log.info("从前端获取到的参数信息为:" + id);
            return studentRepository.findById(id)
                    .map(student -> new ResponseEntity<StudentDO>(student, HttpStatus.OK))
                    .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
        }
    
    }
    StudentController.java

    6 SpringBootWebflux 整合 MongoDB 实现CRUD参考代码

      

  • 相关阅读:
    weblogic的ssrf漏洞
    web服务器、Web中间件和Web容器的区别
    linux C判断文件是否存在
    Linux 文件锁flock 实现两个进程相互监听存活状态
    Linux 进程间通信之管道(pipe),(fifo)
    Linux 进程间通信系列之 信号
    android Binder机制(一)架构设计
    Linux 系统 文件锁 fcntl函数详解
    execlp启动android进程命令
    Linux环境编程--waitpid与fork与execlp
  • 原文地址:https://www.cnblogs.com/NeverCtrl-C/p/9241138.html
Copyright © 2020-2023  润新知