Spring WebFlux 使用 R2DBC 訪問 MySQL

一届搁、介紹

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)勢敦间。

兩者的異同如下:


WebFlux.jpeg

MySQL 支持

直到 Spring Boot 2.3.0.RELEASE,才正式支持基于 r2dbc 的 MySQL 驅(qū)動(dòng)束铭。

本文使用的框架廓块、環(huán)境和工具如下:

二契沫、創(chuàng)建工程

本文基于 IntelliJ IDEA 來創(chuàng)建工程带猴,讀者也可以自行基于 spring initializr 來創(chuàng)建。

1. 新建工程

點(diǎn)擊菜單中【File】→【New】→ 【Project...】懈万,出現(xiàn)以下對話框拴清,選擇 Spring initializr:

1.png

說明

  • JDK: 選擇 JDK 8 及以上,建議使用 OpenJDK会通,以避免法律風(fēng)險(xiǎn)
  • 從 2020.1 開始口予, IntelliJ IDEA 已經(jīng)自帶有 JDK 下載功能,不需要再獨(dú)立下載和安裝
    在 Project SDK 欄中下拉涕侈,選擇 Download JDK...沪停,出現(xiàn)下面的對話框,選擇合適的版本下載即可裳涛。


    2.png

2. 填寫項(xiàng)目信息

點(diǎn)擊 Next木张,進(jìn)入填寫項(xiàng)目信息界面。

3.png

說明

  • Group 和 Artifact:根據(jù)需要修改
  • Type:保持 Maven Project

3. 選擇特性

點(diǎn)擊 Next端三,進(jìn)入選擇特性界面舷礼。


4.png

說明
選擇以下組件:

  • 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.png

5. 生成項(xiàng)目

點(diǎn)擊 Finish,生成項(xiàng)目虚婿,進(jìn)入 IDE旋奢,典型的 Spring Boot 項(xiàng)目結(jié)構(gòu)。

6.png

三然痊、準(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 圖如下:


7.png

基礎(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 早先是隨著 RubyRuby 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)問題

\color{red}{發(fā)現(xiàn)返回?cái)?shù)據(jù)中奈附,createAt全度、createdBy、updatedAt斥滤、updatedBy 四個(gè)字段沒有返回值将鸵。}查詢資料后勉盅,發(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 提供的各種功能戳粒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市虫啥,隨后出現(xiàn)的幾起案子蔚约,更是在濱河造成了極大的恐慌,老刑警劉巖涂籽,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苹祟,死亡現(xiàn)場離奇詭異,居然都是意外死亡评雌,警方通過查閱死者的電腦和手機(jī)树枫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來景东,“玉大人砂轻,你說我怎么就攤上這事〗锿拢” “怎么了搔涝?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長和措。 經(jīng)常有香客問我庄呈,道長,這世上最難降的妖魔是什么臼婆? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任抒痒,我火速辦了婚禮,結(jié)果婚禮上颁褂,老公的妹妹穿的比我還像新娘故响。我一直安慰自己傀广,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布彩届。 她就那樣靜靜地躺著伪冰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪樟蠕。 梳的紋絲不亂的頭發(fā)上贮聂,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機(jī)與錄音寨辩,去河邊找鬼吓懈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛靡狞,可吹牛的內(nèi)容都是我干的耻警。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼甸怕,長吁一口氣:“原來是場噩夢啊……” “哼甘穿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起梢杭,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤温兼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后武契,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體募判,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年咒唆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兰伤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,745評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钧排,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出均澳,到底是詐尸還是另有隱情恨溜,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布找前,位于F島的核電站糟袁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏躺盛。R本人自食惡果不足惜项戴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望槽惫。 院中可真熱鬧周叮,春花似錦辩撑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至项贺,卻和暖如春君躺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背开缎。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工棕叫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奕删。 一個(gè)月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓俺泣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親急侥。 傳聞我的和親對象是個(gè)殘疾皇子砌滞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評論 2 354