Spring WebFlux 使用 R2DBC 访问 MySQL

一、介绍

Reactive Programming

响应式编程指的是数据驱动的、异步和并发的编程范式。简而言之,异步数据流编程。对于数据流进行创建、组合、过滤、转换等操作,最终得到所需要的处理和结果。典型的框架有 RxJava、Reactor 等。

WebFlux

WebFlux 是 Spring Framework 提供的新一代 Web 开发框架,区别于 Spring MVC,WebFlux 提供了非阻塞的、基于 Reactive 技术栈的开发框架,以发挥出多核编程的优势。

两者的异同如下:


WebFlux.jpeg

MySQL 支持

直到 Spring Boot 2.3.0.RELEASE,才正式支持基于 r2dbc 的 MySQL 驱动。

本文使用的框架、环境和工具如下:

  • OpenJDK 14.0.1 (技术研究建议选用最新版本,项目开发则选用稳定版本)
  • IntelliJ IDEA 2020.1.1
  • (Lombok)[https://projectlombok.org/]
  • Spring Boot 2.3.0.RELEASE
  • WebFlux
  • Spring Data R2DBC
  • Netty (Spring Boot WebFlux 的内建容器)
  • Reactor (WebFlux 的 Reactive 基础库)

二、创建工程

本文基于 IntelliJ IDEA 来创建工程,读者也可以自行基于 spring initializr 来创建。

1. 新建工程

点击菜单中【File】→【New】→ 【Project...】,出现以下对话框,选择 Spring initializr:

1.png

说明

  • JDK: 选择 JDK 8 及以上,建议使用 OpenJDK,以避免法律风险
  • 从 2020.1 开始, IntelliJ IDEA 已经自带有 JDK 下载功能,不需要再独立下载和安装
    在 Project SDK 栏中下拉,选择 Download JDK...,出现下面的对话框,选择合适的版本下载即可。


    2.png

2. 填写项目信息

点击 Next,进入填写项目信息界面。

3.png

说明

  • Group 和 Artifact:根据需要修改
  • Type:保持 Maven Project

3. 选择特性

点击 Next,进入选择特性界面。


4.png

说明
选择以下组件:

  • Developer Tools
    • Lombok
  • Web
    • Spring Reactive Web
  • SQL
    • Spring Data R2DBC
    • MySQL Driver

4. 设置项目名称和目录界面

点击 Next,进入设置项目名称和目录界面,保持默认即可。

5.png

5. 生成项目

点击 Finish,生成项目,进入 IDE,典型的 Spring Boot 项目结构。

6.png

三、准备 MySQL 数据

在开始写代码之前,先准备好测试用的数据环境。本文使用的是 MySQL 8.0.19。MySQL 社区版的官方下载地址是 MySQL Community Server。

安装 MySQL

如果有现成的 MySQL 环境,请跳过此步骤。

  • Windows 用户:直接下载 MySQL Installer for Windows,安装即可。
  • Mac 用户: 使用 homebrew 安装更为便捷,命令如下:
brew install mysql

创建 database

首先,创建 database spring_r2dbc_samples,并创建用户 spring_r2dbc_samples_user,脚本如下:

CREATE DATABASE `spring_r2dbc_samples` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';

CREATE USER `spring_r2dbc_samples_user`@`%` IDENTIFIED WITH mysql_native_password BY 'B55!3Ufhj';
CREATE USER `spring_r2dbc_samples_user`@`localhost` IDENTIFIED WITH mysql_native_password BY 'B55!3Ufhj';

GRANT all privileges ON `spring_r2dbc_samples`.* TO `spring_r2dbc_samples_user`@`%`;
GRANT all privileges ON `spring_r2dbc_samples`.* TO `spring_r2dbc_samples_user`@`localhost`;

flush privileges;

创建表

本文所使用的表的 ER 图如下:


7.png

基础字段

字段代码 字段名称 说明
id 编号 主键,自增
remark 备注 可选
active 有效标志 缺省为 1
createdAt 创建时间 默认为 CURRENT_TIMESTAMP
createdBy 创建人
updatedAt 更新时间 默认为 CURRENT_TIMESTAMP,记录更新时自动更新
updatedBy 更新人

学生表

除了基础字段,还包含以下主要字段:

字段代码 字段名称 说明
code 学号
name 姓名
gender 性别 M: 男 F: 女
birthday 生日
address 家庭地址

表创建脚本


ALTER TABLE `spring_r2dbc_samples`.`Student` DROP INDEX `idx_main`;

DROP TABLE `spring_r2dbc_samples`.`Student`;

CREATE TABLE `spring_r2dbc_samples`.`Student`  (
    `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    `code` varchar(50) NOT NULL,
    `name` varchar(50) NOT NULL,
    `gender` char(1) NOT NULL,
    `birthday` date NOT NULL,
    `address` varchar(300) NULL,
    `remark` varchar(1000) NULL,
    `active` tinyint NOT NULL DEFAULT 1,
    `createdAt` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
    `createdBy` varchar(50) NOT NULL,
    `updatedAt` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
    `updatedBy` varchar(50) NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE INDEX `idx_main`(`code`)
);

准备测试数据

-- Student

delete from Student;

insert into Student(code, name, gender, birthday, address, createdBy, updatedBy)
values
      ('S0001', 'Tom', 'M', '2001-03-05', null, 'TEST', 'TEST')
    , ('S0002', 'Ted', 'M', '2001-06-12', null, 'TEST', 'TEST')
    , ('S0003', 'Mary', 'F', '2001--9-12', 'Chicago', 'TEST', 'TEST')
;

四、访问数据

配置数据源

首选,把 /src/main/resources/application.properties 改名为 application.yml,即 YAML 格式。这个纯属个人喜好,觉得配置起来结构更清晰点。YAML 早先是随着 Ruby 和 Ruby on Rails 的流行而流行起来的,简单直接,比起 json 少了括号和双引号,作为配置文件,还是非常不错的。

修改配置文件 application.yml,加入以下配置:

spring:
  r2dbc:
    url: r2dbcs:mysql://localhost:3306/spring_r2dbc_samples?sslMode=DISABLED
    username: spring_r2dbc_samples_user
    password: B55!3Ufhj

创建实体类

  • 创建子 package entity
  • 创建实体类 Student
  • 使用 Lombok 的 @Data 来使得 Student 类可访问
  • @ReadOnlyProperty 的作用是防止代码修改创建时间和更新时间,这个会由 MySQL 自动完成
  • 目前 R2DBC 尚不支持 Audit 功能,所以 createdBy 和 updatedBy 还不能自动设置

代码如下:

package com.example.webfluxmysqldemo.entity;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
public class Student {
    @Id
    private Long id;

    private String code;
    private String name;
    private String gender;
    private LocalDate birthday;
    private String address;

    private String remark;
    private boolean active;

    @ReadOnlyProperty
    private LocalDateTime createdAt;
    private String createdBy;

    @ReadOnlyProperty
    private LocalDateTime updatedAt;
    private String updatedBy;
}

创建仓库类

仓库(repository) 类似于原先的 dao 的角色,主要提供各种底层数据访问功能。Spring Data JPA 中首选推出了 repository 的概念, Spring Data R2DBC 也基本沿用,但是功能上没有 JPA 那么强大。

  • 创建子 package repository
  • 创建仓库类 StudentRepository,继承自 ReactiveCrudRepository

代码如下:

package com.example.webfluxmysqldemo.repository;

import com.example.webfluxmysqldemo.entity.Student;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;

public interface StudentRepository extends ReactiveCrudRepository {
}

创建控制器

  • 创建子 package controller
  • 创建控制器 StudentController,并映射到 /api/students
  • 注入 StudentRepository
  • 创建方法,获取所有学生

代码如下:

package com.example.webfluxmysqldemo.controller;

import com.example.webfluxmysqldemo.entity.Student;
import com.example.webfluxmysqldemo.repository.StudentRepository;
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;

@RestController
@RequestMapping("/api/students")
public class StudentController {
    private final StudentRepository studentRepository;

    public StudentController(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

        @GetMapping("")
    public Flux index() {
        return studentRepository.findAll();
    }
}

编译和启动


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

2020-06-07 16:22:27.000  INFO 65817 --- [           main] c.e.w.WebfluxMysqlDemoApplication        : Starting WebfluxMysqlDemoApplication on Arthur-MacBook-Pro.local with PID 65817 (/Users/arthur/github/arthurlee/webflux-mysql-demo/target/classes started by arthur in /Users/arthur/github/arthurlee/webflux-mysql-demo)
2020-06-07 16:22:27.002  INFO 65817 --- [           main] c.e.w.WebfluxMysqlDemoApplication        : No active profile set, falling back to default profiles: default
2020-06-07 16:22:27.648  INFO 65817 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2020-06-07 16:22:27.737  INFO 65817 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 82ms. Found 1 R2DBC repository interfaces.
2020-06-07 16:22:28.692  INFO 65817 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2020-06-07 16:22:28.701  INFO 65817 --- [           main] c.e.w.WebfluxMysqlDemoApplication        : Started WebfluxMysqlDemoApplication in 2.167 seconds (JVM running for 2.934)

访问数据

使用 http://localhost:8080/api/students 访问接口,返回数据如下:
以下数据是使用 Postman 来

[
    {
        "id": 4,
        "code": "S0001",
        "name": "Tom",
        "gender": "M",
        "birthday": "2001-03-05",
        "address": null,
        "remark": null,
        "active": true,
        "createdAt": null,
        "createdBy": null,
        "updatedAt": null,
        "updatedBy": null
    },
    {
        "id": 5,
        "code": "S0002",
        "name": "Ted",
        "gender": "M",
        "birthday": "2001-06-12",
        "address": null,
        "remark": null,
        "active": true,
        "createdAt": null,
        "createdBy": null,
        "updatedAt": null,
        "updatedBy": null
    },
    {
        "id": 6,
        "code": "S0003",
        "name": "Mary",
        "gender": "F",
        "birthday": "2001-09-12",
        "address": "Chicago",
        "remark": null,
        "active": true,
        "createdAt": null,
        "createdBy": null,
        "updatedAt": null,
        "updatedBy": null
    }
]

出现问题

查询资料后,发现缺省情况下,createAt 会转换成数据库字段 created_at,所以没有成功映射。

解决问题

定位问题后,可以添加一个配置,自定义命名转换规则。

  • 创建子 package config
  • 添加配置类 R2dbcConfig (名称随意)
  • 添加 Bean,返回 NamingStrategy

代码如下:

package com.example.webfluxmysqldemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;

@Configuration
public class R2dbcConfig {
    @Bean
    public NamingStrategy namingStrategy() {
        return new NamingStrategy() {
            @Override
            public String getColumnName(RelationalPersistentProperty property) {
                return property.getName();
            }
        };
    }
}

重新运行和验证

重新运行,访问接口,得到数据如下,问题解决

[
    {
        "id": 4,
        "code": "S0001",
        "name": "Tom",
        "gender": "M",
        "birthday": "2001-03-05",
        "address": null,
        "remark": null,
        "active": true,
        "createdAt": "2020-06-07T15:47:07",
        "createdBy": "TEST",
        "updatedAt": "2020-06-07T15:47:07",
        "updatedBy": "TEST"
    },
    {
        "id": 5,
        "code": "S0002",
        "name": "Ted",
        "gender": "M",
        "birthday": "2001-06-12",
        "address": null,
        "remark": null,
        "active": true,
        "createdAt": "2020-06-07T15:47:07",
        "createdBy": "TEST",
        "updatedAt": "2020-06-07T15:47:07",
        "updatedBy": "TEST"
    },
    {
        "id": 6,
        "code": "S0003",
        "name": "Mary",
        "gender": "F",
        "birthday": "2001-09-12",
        "address": "Chicago",
        "remark": null,
        "active": true,
        "createdAt": "2020-06-07T15:47:07",
        "createdBy": "TEST",
        "updatedAt": "2020-06-07T15:47:07",
        "updatedBy": "TEST"
    }
]

五、参考

  • 本文参考代码
  • Building a Reactive RESTful Web Service
  • Accessing data with R2DBC

后续

本文引领大家进入 R2DBC的世界,搭建出了一个可运行的最小项目,后续将着重介绍 R2DBC 提供的各种功能。

你可能感兴趣的:(Spring WebFlux 使用 R2DBC 访问 MySQL)