WebFlux04 SpringBootWebFlux集成MongoDB之Windows版本

Posted 寻渝记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebFlux04 SpringBootWebFlux集成MongoDB之Windows版本相关的知识,希望对你有一定的参考价值。

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:\\tool\\mongoDB\\bin\\mongod --dbpath C:\\tool\\data --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.*以上是关于WebFlux04 SpringBootWebFlux集成MongoDB之Windows版本的主要内容,如果未能解决你的问题,请参考以下文章

在 WebFlux 中使用聚合

Spring Security WebFlux 注销

Spring Security WebFlux IP 白名单

Spring webflux请求回调不起作用

如何在 Spring boot 2 + Webflux + Thymeleaf 中配置 i18n?

使用Webflux和Spring Cloud时如何用netty替换tomcat?