【应用】SpringBoot -- Webflux + R2DBC 操作 MySQL
创始人
2024-05-13 11:11:27

SpringBoot -- Webflux + R2DBC 操作 MySQL

  • Webflux 概述
  • Webflux 基本使用
  • Webflux + R2DBC 操作 MySQL
    • Controller + Service
    • Route + Handler
  • 参考文章

Webflux 概述

简单来说,Webflux 是响应式编程的框架,与其对等的概念是 SpringMVC。两者的不同之处在于 Webflux 框架是异步非阻塞的,其可以通过较少的线程处理高并发请求。

Webflux 的框架底层采用了 Reactor 响应式编程框架以及 Netty,关于这两部分内容可以参看我之前的学习笔记:

  1. 【基础】Netty 的基础概念及使用

  2. 【基础】Reactor 响应式编程

作为一个异步框架来说,必须保证整个程序链中的每一步都是异步操作,如果在某一步出现了同步阻塞(如等待数据库 IO),则整个程序还是回出现阻塞的问题。因此本文主要介绍 Webflux 框架的基本使用,并通过异步数据库驱动 R2DBC 实现了对 MySQL 数据库的异步操作。

注意,单纯使用 Webflux 框架并不一定会提高接口的响应速度,其作用是提高系统的吞吐量。具体接口的响应速度还要看我们本身的业务逻辑。

Webflux 基本使用

首先创建 maven 项目,在项目的 pom 文件中引入相应的依赖

    org.springframework.bootspring-boot-starter-parent2.7.3 org.springframework.bootspring-boot-starter-data-r2dbcorg.springframework.bootspring-boot-starter-webfluxmysqlmysql-connector-javaruntimedev.mikur2dbc-mysql0.8.2.RELEASEorg.projectlomboklombok1.18.24

创建项目的启动类

@SpringBootApplication
public class WebfluxDemoApplication {public static void main(String[] args) {SpringApplication.run(WebfluxDemoApplication.class);}}

此时,我们就可以编写一个简单的 Controller 来感受一下 Webflux 框架异步相应的概念

@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/hello")public String hello() {long start = System.currentTimeMillis();String helloStr = getHelloStr();System.out.println("普通接口耗时:" + (System.currentTimeMillis() - start));return helloStr;}@GetMapping("/helloWebFlux")public Mono hello0() {long start = System.currentTimeMillis();Mono hello0 = Mono.fromSupplier(this::getHelloStr);System.out.println("WebFlux 接口耗时:" + (System.currentTimeMillis() - start));return hello0;}private String getHelloStr() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}return "hello";}}

上述代码中,我们定义了一个普通的接口和一个异步响应的接口,启动程序调用相应接口,观察两个接口的耗时可以发现,异步相应接口在处理任务时不会阻塞,而是直接向下运行,当业务产生结果后,再将结果通过“预留的通道”反向推送到请求者;而普通接口的整个过过程都是同步的。

在这里插入图片描述

同时,观察 Postman 调用接口的接口响应时间,我们可以发现,无论是普通接口还是异步接口,其接口相应时间均为 2s 多。这也印证了 Webflux 框架并不一定会提高接口的响应时间,起主要作用是提高系统的吞吐量。

Webflux + R2DBC 操作 MySQL

R2DBC 是一个异步操作数据库的驱动,区别于传统的同步数据库驱动 JDBC,R2DBC 与数据库的各种操作也是异步的,这将大量节省高并发系统的线程数量。

首先,创建一个 User 实体类用于测试,同时在 MySQL 中创建相应的数据库以及表结构

@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("webflux_user")
public class User {@Idprivate int id;private String username;private String password;}

编写数据仓库层,使用 Spring-data 封装好的简单 CRUD 接口(用法类似 JPA)

public interface UserRepository extends ReactiveCrudRepository {}

此时就可以调用封装好的 CRUD 方法进行简单的增删改查操作了。在 Webflux 框架中,我们可以使用 SpringMVC 中 Controller + Service 的模式进行开发,也可以使用 Webflux 中 route + handler 的模式进行开发。

Controller + Service

编写 Service 调用 UserRepository

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;public Mono addUser(User user) {return userRepository.save(user);}public Mono> delUser(int id) {return userRepository.findById(id).flatMap(user -> userRepository.delete(user).then(Mono.just(new ResponseEntity(HttpStatus.OK)))).defaultIfEmpty(new ResponseEntity(HttpStatus.NOT_FOUND));}public Mono> updateUser(User user) {return userRepository.findById(user.getId()).flatMap(user0 -> userRepository.save(user)).map(user0 -> new ResponseEntity(user0, HttpStatus.OK)).defaultIfEmpty(new ResponseEntity(HttpStatus.NOT_FOUND));}public Flux getAllUser() {return userRepository.findAll();}}

编写 Controller 进行测试

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@PostMappingpublic Mono addUser(@RequestBody User user) {return userService.addUser(user);}@DeleteMapping("/{id}")public Mono> delUser(@PathVariable int id) {return userService.delUser(id);}@PutMappingpublic Mono> updateUser(@RequestBody User user) {return userService.updateUser(user);}@GetMappingpublic Flux getAllUser() {return userService.getAllUser();}}

Route + Handler

handler 就相当于定义很多处理器,其中不同的方法负责处理不同路由的请求,其对应的是传统的 Service 层

@Component
public class UserHandler {@Autowiredprivate UserRepository userRepository;public Mono addUser(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userRepository.saveAll(request.bodyToMono(User.class)), User.class);}public Mono delUser(ServerRequest request) {return userRepository.findById(Integer.parseInt(request.pathVariable("id"))).flatMap(user -> userRepository.delete(user).then(ServerResponse.ok().build())).switchIfEmpty(ServerResponse.notFound().build());}public Mono updateUser(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userRepository.saveAll(request.bodyToMono(User.class)), User.class);}public Mono getAllUser(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userRepository.findAll(), User.class);}public Mono getAllUserStream(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(userRepository.findAll(), User.class);}}

route 就是路由配置,其规定路由的分发规则,将不同的请求路由分发给相应的 handler 进行业务逻辑的处理,其对应的就是传统的 Controller 层

@Configuration
public class RouteConfig {@BeanRouterFunction userRoute(UserHandler userHandler) {return RouterFunctions.nest(RequestPredicates.path("/userRoute"),RouterFunctions.route(RequestPredicates.POST(""), userHandler::addUser).andRoute(RequestPredicates.DELETE("/{id}"), userHandler::delUser).andRoute(RequestPredicates.PUT(""), userHandler::updateUser).andRoute(RequestPredicates.GET(""), userHandler::getAllUser).andRoute(RequestPredicates.GET("/stream"), userHandler::getAllUserStream));}}

参考文章

  1. 探究WebFlux之WebFlux 的秘密
  2. 用 WebFlux 写个 CURD 是什么体验?
  3. WebFlux 中的请求地址路由怎么玩?
  4. WebFlux 详解

相关内容

热门资讯

demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...