【应用】SpringBoot -- Webflux + R2DBC 操作 MySQL

news2024/9/27 19:20:16

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 文件中引入相应的依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-r2dbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>dev.miku</groupId>
            <artifactId>r2dbc-mysql</artifactId>
            <version>0.8.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
    </dependencies>

创建项目的启动类

@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<String> hello0() {
        long start = System.currentTimeMillis();
        Mono<String> 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 {

    @Id
    private int id;

    private String username;

    private String password;

}

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

public interface UserRepository extends ReactiveCrudRepository<User, Integer> {

}

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

Controller + Service

编写 Service 调用 UserRepository

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public Mono<User> addUser(User user) {
        return userRepository.save(user);
    }

    public Mono<ResponseEntity<Void>> delUser(int id) {
        return userRepository.findById(id)
                .flatMap(user -> userRepository.delete(user).then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK))))
                .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
    }

    public Mono<ResponseEntity<User>> updateUser(User user) {
        return userRepository.findById(user.getId())
                .flatMap(user0 -> userRepository.save(user))
                .map(user0 -> new ResponseEntity<User>(user0, HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity<User>(HttpStatus.NOT_FOUND));
    }

    public Flux<User> getAllUser() {
        return userRepository.findAll();
    }

}

编写 Controller 进行测试

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public Mono<User> addUser(@RequestBody User user) {
        return userService.addUser(user);
    }

    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> delUser(@PathVariable int id) {
        return userService.delUser(id);
    }

    @PutMapping
    public Mono<ResponseEntity<User>> updateUser(@RequestBody User user) {
        return userService.updateUser(user);
    }

    @GetMapping
    public Flux<User> getAllUser() {
        return userService.getAllUser();
    }

}

Route + Handler

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

@Component
public class UserHandler {

    @Autowired
    private UserRepository userRepository;

    public Mono<ServerResponse> addUser(ServerRequest request) {
        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(userRepository.saveAll(request.bodyToMono(User.class)), User.class);
    }

    public Mono<ServerResponse> 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<ServerResponse> updateUser(ServerRequest request) {
        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(userRepository.saveAll(request.bodyToMono(User.class)), User.class);
    }

    public Mono<ServerResponse> getAllUser(ServerRequest request) {
        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(userRepository.findAll(), User.class);
    }

    public Mono<ServerResponse> getAllUserStream(ServerRequest request) {
        return ServerResponse.ok()
                .contentType(MediaType.TEXT_EVENT_STREAM)
                .body(userRepository.findAll(), User.class);
    }

}

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

@Configuration
public class RouteConfig {

    @Bean
    RouterFunction<ServerResponse> 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 详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/170224.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

贪心策略(四)合并区间合集

目录 435. 无重叠区间移除元素的最小个数 无重叠区间 区间组合结果 合并区间_牛客题霸_牛客网 435. 无重叠区间 移除元素的最小个数 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重…

【Linux调试器-gdb使用】

目录 1. 背景 2. 使用 3 命令总结 1. 背景 通过c语言的学习我们知道程序的发布方式有两种&#xff0c;debug模式和release模式&#xff0c;debug模式就是我们所说的调试模式。我们已经熟悉了在Windows平台下VS系列的调试&#xff0c;接下来我们一起在无图形化界面的Linux下来…

2023-01-18 flink 11.6 时间水印 和 窗口周期的关系计算方法

forBoundedOutOfOrderness 和 TumblingEventTimeWindowsforBoundedOutOfOrderness&#xff08;M&#xff09;TumblingEventTimeWindows&#xff08;N&#xff09;第一条数据的时间TS1第一个窗口期公式&#xff1a;窗口开始时间&#xff1a;win_start ((TS1-M)/N) * N窗口结束时…

Docker上部署goweb项目

文章目录一、安装go语言环境①下载go语言环境②解压go语言环境到指定目录③验证是否成功④配置镜像加速二、go语言项目配置第一种&#xff1a;先编译后打包&#xff08;分步部署&#xff0c;靠谱&#xff09;第二种&#xff1a;直接打包法三、成功运行一、安装go语言环境 ①下…

Zabbix 监控 Linux操作系统的监控指标

一、Zabbix 监控 Linux操作系统的监控指标 (仅供参考) Zabbi x默认使用Zabbix agent监控操作系统,其内置的监控项可以满足系统大部分的指标监控,因此,在完成Zabbix agent的安装后,只需在前端页面配置并关联相应的系统监控模板就可以了。 如果内置监控项不能满足监控需求…

走向开放世界强化学习、IJCAI2022论文精选、机器人 RL 工具、强化学习招聘、《强化学习周刊》第73期...

No.73智源社区强化学习组强化学习周刊订阅《强化学习周刊》已经开启“订阅功能”&#xff0c;扫描下面二维码&#xff0c;进入主页&#xff0c;选择“关注TA”&#xff0c;我们会向您自动推送最新版的《强化学习周刊》。本期贡献者&#xff1a;&#xff08;李明&#xff0c;刘青…

【Kotlin】类的继承 ① ( 使用 open 关键字开启类的继承 | 使用 open 关键字开启方法重写 )

文章目录一、使用 open 关键字开启类的继承二、使用 open 关键字开启方法重写一、使用 open 关键字开启类的继承 Kotlin 中的类 默认都是 封闭的 , 无法被继承 , 如果要想类被继承 , 需要在定义类时 使用 open 关键字 ; 定义一个普通的 Kotlin 类 : class Person(val name: S…

分析GC日志来进行JVM调优

不同的垃圾收集器产生的GC日志大致遵循了同一个规则&#xff0c;只是有些许不同&#xff0c;不过对于G1收集器的GC日志和其他垃圾收集器有较大差别&#xff0c;话不多说&#xff0c;正式进入正文。。。 什么时候会发生垃圾收集 首先我们来看一个问题&#xff0c;那就是什么时…

SpringBoot集成Elasticsearch7.4 实战(三)

本篇文章主要讲的是:在Springboot环境下&#xff0c;管理数据1. 数据管理1.1. 新增数据1.1.1. 单实例新增http方式提交数据&#xff0c;案例中我将数据格式做了规范&#xff0c;提交过程中需要指定索引名&#xff0c;以及JSON格式数据&#xff0c;这样尽可能在实际过程中做到通…

图论算法:普里姆算法(C++实现+图解)

文章目录最小生成树普里姆算法实现过程代码实现最小生成树 什么是最小生成树? 对于如图所示的带权无向连通图来说&#xff1a;从图中任意顶点出发&#xff0c;进行dfs或者bfs便可以访问到图中的所有顶点&#xff0c;因此连通图中一次遍历所经过的边的集合以及图中所有顶点的…

libvirt零知识学习2 —— libvirt源码下载

1. libvirt官网主页 libvirt的官网地址为&#xff1a; https://libvirt.org/ 主页如下图所示&#xff1a; 2. libvirt官网下载主页 libvirt的官网下载页地址为&#xff08;在主页中点击“Download”按钮即可跳转到&#xff09;&#xff1a; https://libvirt.org/downloads…

KaiwuDB 首席解决方案专家 金宁:1.0 时序数据库核心功能解读

以下是实录文章精简版欢迎大家点赞、收藏、关注&#xff01;大家好&#xff0c;今天介绍将分为 3 部分&#xff1a;首先从物联网蓬勃发展的时代背景出发&#xff0c;我们一起来看看数据库究竟将面临怎样的挑战与机遇&#xff1b;接着我将为大家详细 KaiwuDB 1.0 时序数据库的核…

(Java高级教程)第四章必备前端基础知识-第一节:HTML

文章目录一&#xff1a;HTML概述&#xff08;1&#xff09;概述&#xff08;2&#xff09;标签&#xff08;3&#xff09;HTML基本结构二&#xff1a;常用标签介绍&#xff08;1&#xff09;注释&#xff08;2&#xff09;标题&#xff08;3&#xff09;段落&#xff08;4&…

React Fragment

首先 我们编写这样一个例子 我们在创建一个react项目 在src的目录下创建components目录 components下创建一个子组件 我这里的名字叫 subset.jsx import React from "react";export default class subset extends React.Component{constructor(props){super(prop…

阿B百大名单公布,有你喜欢的up吗?

阿B在1月13日中午19点30分公布了2022百大UP主名单&#xff0c;那么今年的某站年度UP主都是谁呢&#xff1f;你喜欢的up入选了吗&#xff1f; 咱就来自己查一下都有谁入选了吧~ 我们是用python自动获取名单的哦。 环境使用 python 3.9 pycharm 模块使用 selenium 谷歌驱动 …

Java基础之《netty(26)—netty其他常用编解码器》

一、解码器-ReplayingDecoder 1、函数声明 public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder 2、ReplayingDecoder扩展了ByteToMessageDecoder类&#xff0c;使用这个类&#xff0c;我们不必调用readableBytes()方法。参数S指定了用户状态管理…

【Linux】版本管理工具 Git

目录 一、什么是 Git 二、如何使用 Git 1、创建远程仓库 2、将远端仓库克隆到本地 3、将本地文件添加到仓库 3.1、三板斧第一招&#xff1a;文件添加 3.2、三板斧第二招&#xff1a;提交本地 3.3、三板斧第三招&#xff1a;提交远端 4、删除文件 5、删除仓库 一、什么是 Gi…

postman接口关联

有两种方法&#xff0c;使用json提取器实现接口关联&#xff0c;还有使用正则表达式提取器实现接口关联。方法一&#xff1a;使用json提取器实现接口关联第一个接口&#xff1a;//使用json提取器提取contractID、documentID//把返回的字符串格式的数据转换成对象的形式var resu…

SAP FICO 理解成本中心会计

成本中心会计 一、成本要素 管理会计&#xff08;CO&#xff09;的数据均来源于FI损益类科目&#xff0c;也就是说只有损益类科目才可以创建成本要素&#xff08;必须先创建损益类科目&#xff0c;后创建成本要素&#xff09;&#xff0c; 但是不一定所有的损益类科目都需要…

gma 气象气候函数包的简要介绍及运算过程主要问题说明(内存不足、出现 nan 等)及解决方法

0 概述 0.1 明确气候与气象的概念 气候(Climate)&#xff1a;是指一个地区大气物理特征的长期平均状态&#xff0c;具有一定的稳定性&#xff0c;且周期长。根据世界气象组织&#xff08;WMO&#xff09;的规定&#xff0c;一个标准气候计算时间为 30 年。 气象(Meteorology)&…