SpringWebflux的简单Demo

文章目录

  • 什么是webflux?
  • 简单CRUD例子
    • 引入Jar包
    • 创建表结构
    • 创建Model对象
    • 创建Dao接口及其mapper文件
    • Service
    • Controller
    • 其他有关Mybatis的配置
    • 实战
      • 调用查询接口
  • Webflux和WebMvc性能对比
    • 准备
    • 测试webflux性能
    • 测试webmvc性能
    • 结果对比
  • 目前没解决的问题
  • 参考

什么是webflux?

spring-webflux是spring在5.0版本后提供的一套响应式编程风格的web开发框架
这个框架包含了spring-framework和spring mvc,它可以运行在Netty、Undertow以及3.1版本以上的Serlvet容器上。
你可以在项目中同时使用spring-webmvc和spring-webflux,或者只用其中一个来开发web应用。

简单CRUD例子

引入Jar包

如果你同时添加了spring-boot-starter-web和spring-boot-starter-webflux依赖,那么Spring Boot会自动配置Spring MVC,而不是WebFlux。你当然可以强制指定应用类型,通过SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE) 。

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webfluxartifactId>
dependency>

<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.1.4version>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <scope>runtimescope>
dependency>

创建表结构

CREATE TABLE `ts_person` (
     `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
     `age` tinyint(4) NOT NULL COMMENT '年龄',
     `name` varchar(20) NOT NULL COMMENT '名称',
     `ctime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     `mtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
     PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户表-webflux测试';

创建Model对象

@Data
public class Person {
    /**
     * id
     */
    private Integer id;
    /**
     * 名称
     */
    private String name;

    private Integer age;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime ctime;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime mtime;
}

创建Dao接口及其mapper文件

dao接口,这里仅测试一个接口

@Mapper
public interface PersonDao {
    Person findOne(@Param("id") Integer id);

    int savePerson(@Param("person") Person person);

    List<Person> findAll();

    int deletePerson(@Param("id") Integer id);

    int updatePerson(@Param("person") Person person);
}

mapper文件


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zby.dao.PersonDao">
    <insert id="savePerson">
        INSERT INTO mybatis.ts_person(`name`, age)
        VALUES (#{person.name}, #{person.age})
    insert>
    <update id="updatePerson">
        update mybatis.ts_person
        set `name` = #{person.name},
            age    = #{person.age}
        where id = #{person.id}
    update>
    <delete id="deletePerson">
        delete
        from mybatis.ts_person
        where id = #{id}
    delete>
    <select id="findOne" resultType="com.zby.model.Person">
        SELECT tp.*
        FROM mybatis.ts_person tp
        WHERE tp.id = #{id}
    select>
    <select id="findAll" resultType="com.zby.model.Person">
        SELECT tp.*
        FROM mybatis.ts_person tp
    select>
mapper>

Service

在webflux里面,Handler相当于mvc的service层

@Component
@RequiredArgsConstructor
public class PersonHandler {
    private final PersonDao personDao;

    public Mono<Person> getPerson(Integer id) {
        return Mono.justOrEmpty(personDao.findOne(id));
    }

    public Mono<Integer> deletePerson(Integer id) {
        return Mono.create(monoSink -> {
            monoSink.success(personDao.deletePerson(id));
        });
    }

    public Mono<Integer> savePerson(SavePersonRequest request) {
        Person person = BeanUtil.toBean(request, Person.class);
        return Mono.create(monoSink -> {
            //Mono.create方法里面是异步的,差不多就是一个Future
            //可以加以下代码验证异步的特点,虽然下面设置了30s睡眠,但是接口立马就返回了
                /*try {
                    Thread.sleep(30000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            monoSink.success(personDao.savePerson(person));
        });
    }

    public Flux<Person> findAll() {
        return Flux.fromIterable(personDao.findAll());
    }

    public Mono<Integer> updatePerson(ModifyPersonRequest request) {
        Person person = BeanUtil.toBean(request, Person.class);
        return Mono.create(monoSink -> {
            getPersonIfExt(request);
            monoSink.success(personDao.updatePerson(person));
        });
    }

    private void getPersonIfExt(ModifyPersonRequest request) {
        Person one = personDao.findOne(request.getId());
        if (Objects.isNull(one)) {
            throw new BizException("对象不存在");
        }
    }
}

Controller

在Webflux,编写Controller有两种方式,一种是Router,一种就是沿用MVC的Controller的写法,个人建议使用Controller的写法,因为Router的写法很麻烦,不是使用注解。
下面是Controller的写法

@RestController
@RequestMapping(value = "/person")
@RequiredArgsConstructor
public class PersonController {
    private final PersonHandler personHandler;

    @GetMapping(value = "/{id}")
    public Mono<Person> getPerson(@PathVariable("id") Integer id) {
        Mono<Person> person = personHandler.getPerson(id);
        return personHandler.getPerson(id);
    }

    @PostMapping()
    public Mono<Integer> savePerson(@RequestBody SavePersonRequest request) {
        return personHandler.savePerson(request);
    }

    @GetMapping()
    public Flux<Person> findAll() {
        return personHandler.findAll();
    }

    @DeleteMapping(value = "/{id}")
    public Mono<Integer> deletePerson(@PathVariable("id") Integer id) {
        return personHandler.deletePerson(id);
    }

    @PutMapping()
    public Mono<Integer> updatePerson(@RequestBody ModifyPersonRequest request) {
        return personHandler.updatePerson(request);
    }

}

其他入参:
修改对象的入参

// 修改对象的入参
@Data
public class ModifyPersonRequest {
    private Integer id;
    /**
     * 名称
     */
    private String name;

    private Integer age;

}

新增对象的入参

@Data
public class SavePersonRequest {
    /**
     * 名称
     */
    private String name;

    private Integer age;

}

其他有关Mybatis的配置

application.yml

server:
  #servlet:
  #  context-path: /spring-webflux-demo  # 对webflux无用,不知道是不是spring版本的问题
  port: 8084
spring:
  application: # 应用名称(也是 Nacos 中的服务名)
    name: spring-webflux-demo
  profiles:
    active: dev
  webflux:
    base-path: /spring-webflux-demo #没用,调用接口依然是没有项目路由
    format:
      date: yyyy-MM-dd #没用,返回的时间是个数组,我只能用@JsonFormat去解决
      date-time: yyyy-MM-dd HH:mm:ss #没用,返回的时间是个数组,我只能用@JsonFormat去解决
  datasource:
    url: jdbc:mysql://localhost:3306?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml

实战

往ts_person插入一条记录

INSERT INTO mybatis.ts_person
(id, age, name, ctime, mtime)
VALUES(1, 15, '破损', '2023-03-06 08:22:12', '2023-03-06 08:22:12');

调用查询接口

注:工具postman
SpringWebflux的简单Demo_第1张图片

Webflux和WebMvc性能对比

准备

apache-jmeter-5.3工具

测试webflux性能

以单个person查询的测试接口作为目标
新建测试计划testWebfluxPerson.jmx,这里jmeter希望我们用命令去执行:

	jmeter -n -t testplan/webflux/testWebfluxPerson.jmx -l testplan/webflux/result/result.txt -e -o testplan/webflux/webreport

testplan/webflux/testWebfluxPerson.jmx 测试计划路径
testplan/webflux/result/result.txt 为测试结果文件路径
testplan/webflux/webreport 为web报告保存路径

在jmeter的bin目录下打开cmd,运行上面的命令,生成结果:
SpringWebflux的简单Demo_第2张图片

测试webmvc性能

我用spring mvc搭建类似上面的webflux对应的CRUD接口,以单个person查询的测试接口作为目标
新建测试计划testConsumerPerson.jmx,这里jmeter希望我们用命令去执行:

	jmeter -n -t testplan/consumer/testConsumerPerson.jmx -l testplan/consumer/result/result.txt -e -o testplan/consumer/webreport

testplan/consumer/testConsumerPerson.jmx 测试计划路径
testplan/consumer/result/result.txt 为测试结果文件路径
testplan/consumer/webreport 为web报告保存路径

在jmeter的bin目录下打开cmd,运行上面的命令,生成结果:
SpringWebflux的简单Demo_第3张图片

结果对比

Throughput:就是吞吐量的意思,指定时间内能够处理的请求数
从上面的压测来看,webmvc的吞吐量是167.88/s ,webflux的吞吐量是388.15/s ,可以看出,webflux的吞吐量明显比webmvc优秀。
但是WebFlux 并不能使接口的响应时间缩短,它只是提高了程序的吞吐量而已,使用接口工具访问Webflux的接口跟访问WebMVC接口所消耗的时间是相差无几的。

目前没解决的问题

  1. 项目没有集成swagger,因为swagger集成到webflux时报错了
  2. 没使用统一返回体,因为考虑到要使用Mono和Flux,后续会想办法使用同一个响应体
  3. 没有使用mybatisplus,目前基本的CRUD都是自己写的,不太方便
  4. 指定不了项目路由路径,server.servlet.context-path和spring.webflux.base-path都用过,不起作用

参考

https://mp.weixin.qq.com/s/I4bqSpruoFxwk6pINNtVDQ
https://blog.csdn.net/zhangyingchengqi/article/details/110285664
https://www.zhihu.com/question/19732473
https://blog.csdn.net/qq_38515961/article/details/124989725
https://juejin.cn/post/7026166895866806308
https://blog.csdn.net/crazymakercircle/article/details/112977951

你可能感兴趣的:(mybatis,spring,java)