一、介绍
Reactive Programming
响应式编程指的是数据驱动的、异步和并发的编程范式。简而言之,异步数据流编程。对于数据流进行创建、组合、过滤、转换等操作,最终得到所需要的处理和结果。典型的框架有 RxJava、Reactor 等。
WebFlux
WebFlux 是 Spring Framework 提供的新一代 Web 开发框架,区别于 Spring MVC,WebFlux 提供了非阻塞的、基于 Reactive 技术栈的开发框架,以发挥出多核编程的优势。
两者的异同如下:
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:
说明
- JDK: 选择 JDK 8 及以上,建议使用 OpenJDK,以避免法律风险
-
从 2020.1 开始, IntelliJ IDEA 已经自带有 JDK 下载功能,不需要再独立下载和安装
在 Project SDK 栏中下拉,选择 Download JDK...,出现下面的对话框,选择合适的版本下载即可。
2. 填写项目信息
点击 Next,进入填写项目信息界面。
说明
- Group 和 Artifact:根据需要修改
- Type:保持 Maven Project
3. 选择特性
点击 Next,进入选择特性界面。
说明
选择以下组件:
- Developer Tools
- Lombok
- Web
- Spring Reactive Web
- SQL
- Spring Data R2DBC
- MySQL Driver
4. 设置项目名称和目录界面
点击 Next,进入设置项目名称和目录界面,保持默认即可。
5. 生成项目
点击 Finish,生成项目,进入 IDE,典型的 Spring Boot 项目结构。
三、准备 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 图如下:
基础字段
字段代码 | 字段名称 | 说明 |
---|---|---|
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<Student, Long> {
}
创建控制器
- 创建子 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<Student> 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"
}
]
五、参考
后续
本文引领大家进入 R2DBC的世界,搭建出了一个可运行的最小项目,后续将着重介绍 R2DBC 提供的各种功能。