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:\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>
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); } }
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; }
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> { }
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(); } }
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不存在直接新增】,没有就是新增 */ }
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();
解坑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来返回数据 */ }
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)); }
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)); }
·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(); } }
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(); }
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); }
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(); }
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(); }
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(); }
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(); } }
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; }
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> { }
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)); } }
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 + " \n " + s2); } }
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; } }
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 + " \n " + s2); } }
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); } ); } }
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)); } }
6 SpringBootWebflux 整合 MongoDB 实现CRUD参考代码