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

相关文章
相关标签/搜索