一届搁、介紹
Reactive Programming
響應(yīng)式編程指的是數(shù)據(jù)驅(qū)動(dòng)的、異步和并發(fā)的編程范式窍育。簡而言之卡睦,異步數(shù)據(jù)流編程。對于數(shù)據(jù)流進(jìn)行創(chuàng)建漱抓、組合表锻、過濾、轉(zhuǎn)換等操作乞娄,最終得到所需要的處理和結(jié)果瞬逊。典型的框架有 RxJava显歧、Reactor 等。
WebFlux
WebFlux 是 Spring Framework 提供的新一代 Web 開發(fā)框架码耐,區(qū)別于 Spring MVC追迟,WebFlux 提供了非阻塞的、基于 Reactive 技術(shù)棧的開發(fā)框架骚腥,以發(fā)揮出多核編程的優(yōu)勢敦间。
兩者的異同如下:
MySQL 支持
直到 Spring Boot 2.3.0.RELEASE,才正式支持基于 r2dbc 的 MySQL 驅(qū)動(dòng)束铭。
本文使用的框架廓块、環(huán)境和工具如下:
- OpenJDK 14.0.1 (技術(shù)研究建議選用最新版本,項(xiàng)目開發(fā)則選用穩(wěn)定版本)
- IntelliJ IDEA 2020.1.1
- (Lombok)[https://projectlombok.org/]
- Spring Boot 2.3.0.RELEASE
- WebFlux
- Spring Data R2DBC
- Netty (Spring Boot WebFlux 的內(nèi)建容器)
- Reactor (WebFlux 的 Reactive 基礎(chǔ)庫)
二契沫、創(chuàng)建工程
本文基于 IntelliJ IDEA 來創(chuàng)建工程带猴,讀者也可以自行基于 spring initializr 來創(chuàng)建。
1. 新建工程
點(diǎn)擊菜單中【File】→【New】→ 【Project...】懈万,出現(xiàn)以下對話框拴清,選擇 Spring initializr:
說明
- JDK: 選擇 JDK 8 及以上,建議使用 OpenJDK会通,以避免法律風(fēng)險(xiǎn)
-
從 2020.1 開始口予, IntelliJ IDEA 已經(jīng)自帶有 JDK 下載功能,不需要再獨(dú)立下載和安裝
在 Project SDK 欄中下拉涕侈,選擇 Download JDK...沪停,出現(xiàn)下面的對話框,選擇合適的版本下載即可裳涛。
2. 填寫項(xiàng)目信息
點(diǎn)擊 Next木张,進(jìn)入填寫項(xiàng)目信息界面。
說明
- Group 和 Artifact:根據(jù)需要修改
- Type:保持 Maven Project
3. 選擇特性
點(diǎn)擊 Next端三,進(jìn)入選擇特性界面舷礼。
說明
選擇以下組件:
- Developer Tools
- Lombok
- Web
- Spring Reactive Web
- SQL
- Spring Data R2DBC
- MySQL Driver
4. 設(shè)置項(xiàng)目名稱和目錄界面
點(diǎn)擊 Next,進(jìn)入設(shè)置項(xiàng)目名稱和目錄界面技肩,保持默認(rèn)即可且轨。
5. 生成項(xiàng)目
點(diǎn)擊 Finish,生成項(xiàng)目虚婿,進(jìn)入 IDE旋奢,典型的 Spring Boot 項(xiàng)目結(jié)構(gòu)。
三然痊、準(zhǔn)備 MySQL 數(shù)據(jù)
在開始寫代碼之前至朗,先準(zhǔn)備好測試用的數(shù)據(jù)環(huán)境。本文使用的是 MySQL 8.0.19剧浸。MySQL 社區(qū)版的官方下載地址是 MySQL Community Server锹引。
安裝 MySQL
如果有現(xiàn)成的 MySQL 環(huán)境矗钟,請?zhí)^此步驟。
- Windows 用戶:直接下載 MySQL Installer for Windows嫌变,安裝即可吨艇。
- Mac 用戶: 使用 homebrew 安裝更為便捷,命令如下:
brew install mysql
創(chuàng)建 database
首先腾啥,創(chuàng)建 database spring_r2dbc_samples东涡,并創(chuàng)建用戶 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;
創(chuàng)建表
本文所使用的表的 ER 圖如下:
基礎(chǔ)字段
字段代碼 | 字段名稱 | 說明 |
---|---|---|
id | 編號 | 主鍵倘待,自增 |
remark | 備注 | 可選 |
active | 有效標(biāo)志 | 缺省為 1 |
createdAt | 創(chuàng)建時(shí)間 | 默認(rèn)為 CURRENT_TIMESTAMP |
createdBy | 創(chuàng)建人 | |
updatedAt | 更新時(shí)間 | 默認(rèn)為 CURRENT_TIMESTAMP疮跑,記錄更新時(shí)自動(dòng)更新 |
updatedBy | 更新人 |
學(xué)生表
除了基礎(chǔ)字段,還包含以下主要字段:
字段代碼 | 字段名稱 | 說明 |
---|---|---|
code | 學(xué)號 | |
name | 姓名 | |
gender | 性別 | M: 男 F: 女 |
birthday | 生日 | |
address | 家庭地址 |
表創(chuàng)建腳本
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`)
);
準(zhǔn)備測試數(shù)據(jù)
-- 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')
;
四凸舵、訪問數(shù)據(jù)
配置數(shù)據(jù)源
首選祖娘,把 /src/main/resources/application.properties 改名為 application.yml,即 YAML 格式啊奄。這個(gè)純屬個(gè)人喜好渐苏,覺得配置起來結(jié)構(gòu)更清晰點(diǎn)。YAML 早先是隨著 Ruby 和 Ruby on Rails 的流行而流行起來的菇夸,簡單直接整以,比起 json 少了括號和雙引號,作為配置文件峻仇,還是非常不錯(cuò)的。
修改配置文件 application.yml邑商,加入以下配置:
spring:
r2dbc:
url: r2dbcs:mysql://localhost:3306/spring_r2dbc_samples?sslMode=DISABLED
username: spring_r2dbc_samples_user
password: B55!3Ufhj
創(chuàng)建實(shí)體類
- 創(chuàng)建子 package entity
- 創(chuàng)建實(shí)體類 Student
- 使用 Lombok 的 @Data 來使得 Student 類可訪問
- @ReadOnlyProperty 的作用是防止代碼修改創(chuàng)建時(shí)間和更新時(shí)間摄咆,這個(gè)會(huì)由 MySQL 自動(dòng)完成
- 目前 R2DBC 尚不支持 Audit 功能,所以 createdBy 和 updatedBy 還不能自動(dòng)設(shè)置
代碼如下:
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;
}
創(chuàng)建倉庫類
倉庫(repository) 類似于原先的 dao 的角色人断,主要提供各種底層數(shù)據(jù)訪問功能吭从。Spring Data JPA 中首選推出了 repository 的概念, Spring Data R2DBC 也基本沿用恶迈,但是功能上沒有 JPA 那么強(qiáng)大涩金。
- 創(chuàng)建子 package repository
- 創(chuàng)建倉庫類 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> {
}
創(chuàng)建控制器
- 創(chuàng)建子 package controller
- 創(chuàng)建控制器 StudentController暇仲,并映射到 /api/students
- 注入 StudentRepository
- 創(chuàng)建方法步做,獲取所有學(xué)生
代碼如下:
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();
}
}
編譯和啟動(dòng)
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: 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)
訪問數(shù)據(jù)
使用 http://localhost:8080/api/students 訪問接口,返回?cái)?shù)據(jù)如下:
以下數(shù)據(jù)是使用 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
}
]
出現(xiàn)問題
查詢資料后勉盅,發(fā)現(xiàn)缺省情況下,createAt 會(huì)轉(zhuǎn)換成數(shù)據(jù)庫字段 created_at顶掉,所以沒有成功映射草娜。
解決問題
定位問題后,可以添加一個(gè)配置痒筒,自定義命名轉(zhuǎn)換規(guī)則宰闰。
- 創(chuàng)建子 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();
}
};
}
}
重新運(yùn)行和驗(yàn)證
重新運(yùn)行凸克,訪問接口议蟆,得到數(shù)據(jù)如下,問題解決萎战!
[
{
"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"
}
]
五咐容、參考
后續(xù)
本文引領(lǐng)大家進(jìn)入 R2DBC的世界,搭建出了一個(gè)可運(yùn)行的最小項(xiàng)目蚂维,后續(xù)將著重介紹 R2DBC 提供的各種功能戳粒。