Spring Data R2DBC響應式操做MySQL

1. 前言

使用R2DBC操做MySQL數據庫 一文中初步介紹了r2dbc-mysql的使用。因爲藉助DatabaseClient操做MySQL,過於初級和底層,不利於開發。今天就利用Spring Data R2DBC來演示Spring 數據存儲抽象(Spring Data Repository)風格的R2DBC數據庫操做。html

請注意:目前 Spring Data R2DBC雖然已經迭代了多個正式版,可是仍然處於初級階段,還不足以運用到生產中。不過將來可期,值得研究學習。

2. Spring Data R2DBC

Spring Data R2DBC提供了基於R2DBC反應式關係數據庫驅動程序的流行的Repository抽象。可是這並非一個ORM框架,你能夠把它看作一個數據庫訪問的抽象層或者R2DBC的客戶端程序。它不提供ORM框架具備的緩存、懶加載等諸多特性,但它抽象了數據庫和對象的抽象映射關係,具備輕量級、易用性的特色。java

2.1 版本對應關係

胖哥總結了截至目前Spring Data R2DBCSpring Framework的版本對應關係:mysql

Spring Data R2DBC Spring Framework
1.0.0.RELEASE 5.2.2.RELEASE
1.1.0.RELEASE 5.2.6.RELEASE
1.1.1.RELEASE 5.2.7.RELEASE
1.1.2.RELEASE 5.2.8.RELEASE

必定要注意版本對應關係,避免不兼容的狀況。react

3. 基礎依賴

上次我沒有引用R2DBC鏈接池,此次我將嘗試使用它。主要依賴以下 ,這裏我還集成了Spring Webflux:web

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<!--  r2dbc 鏈接池 -->
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-pool</artifactId>
</dependency>
<!--r2dbc mysql 庫-->
<dependency>
    <groupId>dev.miku</groupId>
    <artifactId>r2dbc-mysql</artifactId>
</dependency>
<!--自動配置須要引入的一個嵌入式數據庫類型對象-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<!-- 反應式web框架 webflux-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
這裏我採用的是 Spring Boot 2.3.2.RELEASE

4. 配置

上次咱們採用的是JavaConfig風格的配置,只須要向Spring IoC注入一個ConnectionFactory。這一次我將嘗試在application.yaml中配置R2DBC的必要參數。spring

spring:
  r2dbc:
    url: r2dbcs:mysql://127.0.0.1:3306/r2dbc
    username: root
    password: 123456

以上就是R2DBC的主要配置。特別注意的是spring.r2dbc.url的格式,根據數據庫的不一樣寫法是不一樣的,要看驅動的定義,這一點很是重要。鏈接池這裏使用默認配置便可,不用顯式定義。sql

5. 編寫業務代碼

接下來就是編寫業務代碼了。這裏我還嘗試使用DatabaseClient來執行了DDL語句建立了client_user表,感受還不錯。數據庫

@Autowired
DatabaseClient databaseClient;

@Test
void doDDL() {

    List<String> ddl = Collections.unmodifiableList(Arrays.asList("drop table if exists client_user;", "create table client_user(user_id varchar(64) not null primary key,nick_name varchar(32),phone_number varchar(16),gender tinyint default 0) charset = utf8mb4;"));
    ddl.forEach(sql -> databaseClient.execute(sql)
            .fetch()
            .rowsUpdated()
            .as(StepVerifier::create)
            .expectNextCount(1)
            .verifyComplete());
}

5.1 聲明數據庫實體

熟悉Spring Data JPA的同窗應該很輕車熟路了。api

/**
 *  the client user type
 *
 * @author felord.cn
 */
@Data
@Table
public class ClientUser implements Serializable {
    private static final long serialVersionUID = -558043294043707772L;
    @Id
    private String userId;
    private String nickName;
    private String phoneNumber;
    private Integer gender;
}

5.2 聲明CRUD接口

上面實體類中的@Table註解是有說法的,當咱們的操做接口繼承的是ReactiveCrudRepository<T, ID> 或者ReactiveSortingRepository<T, ID>時,須要在實體類上使用@Table註解,這也是推薦的用法。緩存

public interface ReactiveClientUserSortingRepository extends ReactiveSortingRepository<ClientUser,String> {
    
}

固然實體類不使用@Table註解標記時,咱們還能夠繼承R2dbcRepository<T, ID>接口。而後ReactiveClientUserSortingRepository將提供一些操做數據庫的方法。

Repository提供的一些默認操做數據庫的方法

而後Spring Data JPA怎麼寫,這裏也差很少怎麼寫,可是有些功能如今尚未獲得支持,好比上面提到的分頁,還有主鍵策略等。

相似 PagingAndSortingRepository<T,ID>的反應式分頁功能接口目前尚未實裝,會在將來的版本集成進來。

5.3 實際操做

接下來咱們就要經過R2DBC實際操做MySQL數據庫了。按照咱們傳統的邏輯寫了以下的新增邏輯:

ClientUser clientUser = new ClientUser();

clientUser.setGender(2);
clientUser.setNickName("r2dbc");
clientUser.setPhoneNumber("9527");
clientUser.setUserId("snowflake");

Mono<ClientUser> save = reactiveClientUserSortingRepository.save(clientUser);

結果數據庫並無寫入數據。這時由於r2dbc-mysql不能被直接使用,只能由客戶端去實現並委託給客戶端去操做。

這也是 R2DBC的設計原則,R2DBC的目標是最小化SPI平面,目的是消除數據庫之間的差別部分,並使得整個數據庫徹底具備反應式和背壓。它主要用做客戶端庫使用的驅動程序SPI,而不打算直接在應用程序代碼中使用。

因此這裏咱們能夠藉助於reactor-test測試庫去執行一下,改寫爲:

reactiveClientUserSortingRepository.save(clientUser)
        .log()
        .as(StepVerifier::create)
        .expectNextCount(1)
        .verifyComplete();

可是依然不能執行成功,提示update table [client_user]. Row with Id [snowflake] does not exist ,也就是說指望執行的是新增可是實際執行的是更新,因爲數據庫找不到主鍵爲snowflake的記錄就報了錯。這裏爲何是更新呢?

這時由於實體類在進行新增時會判斷主鍵是否填充,若是沒有填充就認爲是新數據,採起真正的新增操做,主鍵須要數據庫來自動填充;若是主鍵存在值則認爲是舊數據則調用更新操做。胖哥同Spring Data R2DBC的項目組溝通後並無獲得友好的解決方案,不過我已經找到了方法,這裏先留個坑。

那麼該如何新增一條數據呢?咱們只能藉助於@Query註解來編寫一條SQL寫入了:

@Modifying
@Query("insert into client_user (user_id,nick_name,phone_number,gender) values (:userId,:nickName,:phoneNumber,:gender)")
Mono<Integer> addClientUser(String userId, String nickName, String phoneNumber, Integer gender);

當添加了@Modifying後,返回值能夠從Mono<ClientUser>Mono<Boolean>或者Mono<Integer>任意一種選擇。

reactiveClientUserSortingRepository
        .addClientUser("snowflake",
                "r2dbc",
                "132****155",
                0)
        .as(StepVerifier::create)
        .expectNextCount(1)
        .verifyComplete();

r2dbc 寫入成功log

這樣就證實寫成功了一條數據。

5.4 搭配Webflux使用

可是實際中該如何應用呢?目前可以想到的就是結合反應式框架Spring Webflux了,就像Spring Data JPA配合Spring MVC同樣。

咱們編寫一個Webflux接口:

@RestController
@RequestMapping("/user")
public class ReactiveClientUserController {

    @Autowired
    private ReactiveClientUserSortingRepository reactiveClientUserSortingRepository;


    /**
     * 這裏爲了檢驗默認api 就不分層了
     *
     * @param userId the user id
     * @return the mono
     */
    @GetMapping("/{userId}")
    public Mono<ClientUser> findUserById(@PathVariable String userId) {
        return reactiveClientUserSortingRepository.findById(userId);
    }

}

webflux 經過r2dbc查詢mysql數據庫

5.5 一些測試數據參考

在低併發時,Spring MVC + JDBC表現最佳,但在高併發下,WebFlux + R2DBC使用每一個已處理請求的內存最少。

併發下的CPU佔用

在高併發下,Spring MVC + JDBC的響應時間開始降低。顯然,R2DBC在更高的併發性下提供了更好的響應時間。Spring WebFlux也比使用Spring MVC的相似實現更好。

吞吐量對比

6. 總結

今天對Spring Data R2DBC進一步演示,相信你可以從中學到一些東西。因爲R2DBC仍是比較新,還存在一些須要改進和補充的東西。目前社區很是活躍,發展十分迅速。好了今天的文章就到這裏,原創不易多多關注:碼農小胖哥 若是你以爲本文頗有用,請點贊、轉發、再看。

關注公衆號:Felordcn 獲取更多資訊

我的博客:https://felord.cn

相關文章
相關標籤/搜索