[Day 25] Reactive Programming - Spring WebFlux(R2DBC)

前言

在上一个范例中,是写死回传的内容,显然在现实生活中应该是不会有公司让你可以这样做的,而当我们的Controller开始Reactive了,背後的data store当然也要reactive起来。

R2DBC

一开始的时候其实Spring WebFlux并不支援关联式资料库,只能使用NoSQL或是Redis,但还是有许多的业务场景是更适合使用RDB的,这时候Spring推出 Reactive Relational Database Connectivity 简称R2DBC,跟JDBC一样是DB driver,但它并不是建立在JDBC或是其他api之上,因为JDBC是blocking的api,最後与我们熟悉的Spring Data 结合成为 Spring Data R2DBC
https://ithelp.ithome.com.tw/upload/images/20211009/20141418zFrVipEWFw.png

Support

  • H2 (io.r2dbc:r2dbc-h2)
  • MariaDB (org.mariadb:r2dbc-mariadb)
  • Microsoft SQL Server (io.r2dbc:r2dbc-mssql)
  • MySQL (dev.miku:r2dbc-mysql)
  • jasync-sql MySQL (com.github.jasync-sql:jasync-r2dbc-mysql)
  • Postgres (io.r2dbc:r2dbc-postgresql)
  • Oracle (com.oracle.database.r2dbc:oracle-r2dbc)

Example

事先准备

build.gralde 多补上两个dependencies

implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
runtimeOnly 'dev.miku:r2dbc-mysql'

这次开始的范例需要用到mySql,这边提供docker image(docker hub)的版本,
init.sql须放在与stack.yml同一层

CREATE DATABASE IF NOT EXISTS test; 
USE test;

stack.yml主要就是有一个可以init的sql执行以及有一个8081的ui介面。

# Use root/example as user/password credentials 
version: '3.1' 
services: 
  db: 
    image: mysql 
    command: --default-authentication-plugin=mysql_native_password --init-file /data/application/init.sql 
    restart: always 
    volumes: 
        - ./init.sql:/data/application/init.sql 
    environment: 
      MYSQL_ROOT_PASSWORD: example 
    ports: 
      - 3306:3306 
  adminer: 
    image: adminer 
    restart: always 
    ports: 
      - 8081:8080

application.properties将设定帐号密码同上面DB设定

spring.r2dbc.url=r2dbc:mysql://localhost:3306/test
spring.r2dbc.username=root
spring.r2dbc.password=example

Class

Greeting.java Entity,跟Spring data 相同,有@Id 就会自然被Spring视作为DB物件。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Greeting {
  @Id
  private Long id;
  private String message;

  public Greeting(String message) {
    this.message = message;
  }

}

GreetingRepository Repository 一样有很方便的CrudRepository 可以使用,用法基本上是一样的。

public interface GreetingRepository extends ReactiveCrudRepository<Greeting, Long> {
  Mono<Greeting> findById(Long id);

  Flux<Greeting> findAll();
  Flux<Greeting> findByMessage(String message);

  Mono<Void> save(Mono<Greeting> greeting);
}

\src\main\resources\schema.sql 看文件没有找到会自动产生table,可能还没支援,不过有提供ini.sql的方式,先准备schema.sql在resource目录下

CREATE TABLE IF NOT EXISTS  greeting (id bigint PRIMARY KEY AUTO_INCREMENT, message VARCHAR(255));

在任意地方放上这个initializer,建议是可以放在命名为DbConfiguration之类的地方统一管理,这边测试就直接放在WebFluxGuideApplication底下

@Bean
ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {

  ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
  initializer.setConnectionFactory(connectionFactory);
  initializer.setDatabasePopulator(
      new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));

  return initializer;
}

最後测试一下

@Bean
public CommandLineRunner demo(GreetingRepository repository) {

  return (args) -> {
    // save a few Greeting
    repository
        .saveAll(
            Arrays.asList(
                new Greeting("Hello"),
                new Greeting("Hello"),
                new Greeting("Yo"),
                new Greeting("Nice to meet you"),
                new Greeting("Hi")))
        .blockLast(Duration.ofSeconds(10));

    // fetch all Greeting
    log.info("Greeting found with findAll():");
    log.info("-------------------------------");
    repository
        .findAll()
        .doOnNext(
            greeting -> {
              log.info(greeting.toString());
            })
        .blockLast(Duration.ofSeconds(10));

    log.info("");

    // fetch an individual Greeting by ID
    repository
        .findById(1L)
        .doOnNext(
            greeting -> {
              log.info("Greeting found with findById(1L):");
              log.info("--------------------------------");
              log.info(greeting.toString());
              log.info("");
            })
        .block(Duration.ofSeconds(10));

    // fetch Greeting by last name
    log.info("Greeting found with findByMessage('Hello'):");
    log.info("--------------------------------------------");
    repository
        .findByMessage("Hello")
        .doOnNext(
            hello -> {
              log.info(hello.toString());
            })
        .blockLast(Duration.ofSeconds(10));
    log.info("");
  };
}

https://ithelp.ithome.com.tw/upload/images/20211009/20141418RRB1BCBaBQ.png

结语

今天简单的将Reactive Programming推进到了DB的世界,下一篇会补充说明。

资料来源

<<:  Day 24 深度学习与人工神经网路

>>:  Day-24 快速面试之考题大公开!(3)

Day 24:程序「动」起来

Projucer 支援另一类型的专案——Animated。与一般的 GUI 专案不同处之一是,Mai...

【Day27】建立一个 QA Bot

今天要来跟各位一起解析 QnA Maker Bot,以下简称 QA Bot。 今天是参考 官方范例程...

18. 订OKRs新手常见错误

前言 这篇跟工程师其实没那麽有关,适合给新手leader定OKRs的时候看看。 演讲总结 今天要讲...

7. STM32-结合中断来做个红绿灯吧!

既然前几篇介绍了外部中断、Timer中断与USART,那接下来就结合这三种中断来模拟红绿灯出来吧。 ...

Day 17 - 成长曲线N+1 : AIGO教练培训

图片来源 谈了好几天的AI相关议题, 其实主要也是因为之前的分享中有提到, 我是偶然间在今年七月初...