简介

之前我们提到过,对于底层的数据源来说,MongoDB, Redis, 和 Cassandra 可以直接以reactive的方式支持Spring Data。而其他很多关系型数据库比如Postgres, Microsoft SQL Server, MySQL, H2 和 Google Spanner 则可以通过使用R2DBC 来实现对reactive的支持。

今天我们就来具体讲解一下R2DBC的使用。

R2DBC介绍

之前我们介绍了Reactor还有基于其之上的Spring WebFlux框架。包括vert.x,rxjava等等reactive技术。我们实际上在应用层已经有很多优秀的响应式处理框架。

但是有一个问题就是所有的框架都需要获取底层的数据,而基本上关系型数据库的底层读写都还是同步的。

为了解决这个问题,出现了两个标准,一个是oracle提出的 ADBC (Asynchronous Database Access API),另一个就是Pivotal提出的R2DBC (Reactive Relational Database Connectivity)。

R2DBC是基于Reactive Streams标准来设计的。通过使用R2DBC,你可以使用reactive API来操作数据。

同时R2DBC只是一个开放的标准,而各个具体的数据库连接实现,需要实现这个标准。

今天我们以r2dbc-h2为例,讲解一下r2dbc在Spring webFlux中的使用。

项目依赖

我们需要引入r2dbc-spi和r2dbc-h2两个库,其中r2dbc-spi是接口,而r2dbc-h2是具体的实现。

同时我们使用了Spring webflux,所以还需要引入spring-boot-starter-webflux。

具体的依赖如下:

  1. <!-- R2DBC H2 Driver -->
  2. <dependency>
  3. <groupId>io.r2dbc</groupId>
  4. <artifactId>r2dbc-h2</artifactId>
  5. <version>${r2dbc-h2.version}</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-webflux</artifactId>
  10. </dependency>

创建ConnectionFactory

ConnectionFactory是数据库连接的一个具体实现,通过ConnectionFactory我们可以创建到数据库的连接。

先看一下数据库的配置文件,为了方便起见,这里我们使用的是内存数据库H2 :

  1. r2dbc.url=r2dbc:h2:mem://./r2dbc
  2. r2dbc.user=sa
  3. r2dbc.password=password

第一个url指定的是数据库的连接方式,下面两个是数据库的用户名和密码。

接下来我们看一下,怎么通过这些属性来创建ConnectionFactory:

  1. @Bean
  2. public ConnectionFactory connectionFactory() {
  3. ConnectionFactoryOptions baseOptions = ConnectionFactoryOptions.parse(url);
  4. ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions);
  5. if (!StringUtil.isNullOrEmpty(user)) {
  6. ob = ob.option(USER, user);
  7. }
  8. if (!StringUtil.isNullOrEmpty(password)) {
  9. ob = ob.option(PASSWORD, password);
  10. }
  11. return ConnectionFactories.get(ob.build());
  12. }

通过url可以parse得到ConnectionFactoryOptions。然后通过ConnectionFactories的get方法创建ConnectionFactory。

如果我们设置了USER或者PASSWORD,还可以加上这两个配置。

创建Entity Bean

这里,我们创建一个简单的User对象:

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class Users {
  5. private Long id;
  6. private String firstname;
  7. private String lastname;
  8. }

初始化数据库

虽然H5有很多更加简单的方式来初始化数据库,比如直接读取SQL文件,这里为了说明R2DBC的使用,我们使用手动的方式来创建:

  1. @Bean
  2. public CommandLineRunner initDatabase(ConnectionFactory cf) {
  3. return (args) ->
  4. Flux.from(cf.create())
  5. .flatMap(c ->
  6. Flux.from(c.createBatch()
  7. .add("drop table if exists Users")
  8. .add("create table Users(" +
  9. "id IDENTITY(1,1)," +
  10. "firstname varchar(80) not null," +
  11. "lastname varchar(80) not null)")
  12. .add("insert into Users(firstname,lastname)" +
  13. "values('flydean','ma')")
  14. .add("insert into Users(firstname,lastname)" +
  15. "values('jacken','yu')")
  16. .execute())
  17. .doFinally((st) -> c.close())
  18. )
  19. .log()
  20. .blockLast();
  21. }

上面的代码中,我们使用c.createBatch()来向数据库插入一些数据。

除了createBatch,还可以使用create来创建单个的执行语句。

获取所有的用户

在Dao中,我们提供了一个findAll的方法:

  1. public Flux<Users> findAll() {
  2. return Mono.from(connectionFactory.create())
  3. .flatMap((c) -> Mono.from(c.createStatement("select id,firstname,lastname from users")
  4. .execute())
  5. .doFinally((st) -> close(c)))
  6. .flatMapMany(result -> Flux.from(result.map((row, meta) -> {
  7. Users acc = new Users();
  8. acc.setId(row.get("id", Long.class));
  9. acc.setFirstname(row.get("firstname", String.class));
  10. acc.setLastname(row.get("lastname", String.class));
  11. return acc;
  12. })));
  13. }

简单解释一下上面的使用。

因为是一个findAll方法,我们需要找出所有的用户信息。所以我们返回的是一个Flux而不是一个Mono。

怎么从Mono转换成为一个Flux呢?

这里我们使用的是flatMapMany,将select出来的结果,分成一行一行的,最后转换成为Flux。

Prepare Statement

为了防止SQL注入,我们需要在SQL中使用Prepare statement:

  1. public Mono<Users> findById(long id) {
  2. return Mono.from(connectionFactory.create())
  3. .flatMap(c -> Mono.from(c.createStatement("select id,firstname,lastname from Users where id = $1")
  4. .bind("$1", id)
  5. .execute())
  6. .doFinally((st) -> close(c)))
  7. .map(result -> result.map((row, meta) ->
  8. new Users(row.get("id", Long.class),
  9. row.get("firstname", String.class),
  10. row.get("lastname", String.class))))
  11. .flatMap( p -> Mono.from(p));
  12. }

看下我们是怎么在R2DBC中使用prepare statement的。

事务处理

接下来我们看一下怎么在R2DBC中使用事务:

  1. public Mono<Users> createAccount(Users account) {
  2. return Mono.from(connectionFactory.create())
  3. .flatMap(c -> Mono.from(c.beginTransaction())
  4. .then(Mono.from(c.createStatement("insert into Users(firstname,lastname) values($1,$2)")
  5. .bind("$1", account.getFirstname())
  6. .bind("$2", account.getLastname())
  7. .returnGeneratedValues("id")
  8. .execute()))
  9. .map(result -> result.map((row, meta) ->
  10. new Users(row.get("id", Long.class),
  11. account.getFirstname(),
  12. account.getLastname())))
  13. .flatMap(pub -> Mono.from(pub))
  14. .delayUntil(r -> c.commitTransaction())
  15. .doFinally((st) -> c.close()));
  16. }

上面的代码中,我们使用了事务,具体的代码有两部分:

  1. c -> Mono.from(c.beginTransaction())
  2. .delayUntil(r -> c.commitTransaction())

开启是的时候需要使用beginTransaction,后面提交就需要调用commitTransaction。

WebFlux使用

最后,我们需要创建WebFlux应用来对外提供服务:

  1. @GetMapping("/users/{id}")
  2. public Mono<ResponseEntity<Users>> getUsers(@PathVariable("id") Long id) {
  3. return usersDao.findById(id)
  4. .map(acc -> new ResponseEntity<>(acc, HttpStatus.OK))
  5. .switchIfEmpty(Mono.just(new ResponseEntity<>(null, HttpStatus.NOT_FOUND)));
  6. }
  7. @GetMapping("/users")
  8. public Flux<Users> getAllAccounts() {
  9. return usersDao.findAll();
  10. }
  11. @PostMapping("/createUser")
  12. public Mono<ResponseEntity<Users>> createUser(@RequestBody Users user) {
  13. return usersDao.createAccount(user)
  14. .map(acc -> new ResponseEntity<>(acc, HttpStatus.CREATED))
  15. .log();
  16. }

执行效果

最后,我们运行一下代码,执行下users:

  1. curl "localhost:8080/users"
  2. [{"id":1,"firstname":"flydean","lastname":"ma"},{"id":2,"firstname":"jacken","lastname":"yu"}]%

完美,实验成功。

本文的代码:webflux-with-r2dbc

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/r2dbc-introduce/

本文来源:flydean的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

响应式关系数据库处理R2DBC的更多相关文章

  1. Spring Data R2DBC响应式操作MySQL

    1. 前言 在使用R2DBC操作MySQL数据库 一文中初步介绍了r2dbc-mysql的使用.由于借助DatabaseClient操作MySQL,过于初级和底层,不利于开发.今天就利用Spring ...

  2. Reactive Spring实战 -- 响应式MySql交互

    本文与大家探讨Spring中如何实现MySql响应式交互. Spring Data R2DBC项目是Spring提供的数据库响应式编程框架. R2DBC是Reactive Relational Dat ...

  3. 一步步开发自己的博客 .NET版 剧终篇(6、响应式布局 和 自定义样式)

    前言 这次开发的博客主要功能或特点:    第一:可以兼容各终端,特别是手机端.    第二:到时会用到大量html5,炫啊.    第三:导入博客园的精华文章,并做分类.(不要封我)    第四:做 ...

  4. 网站就必须用响应式布局吗?MVC视图展现模式之移动布局

    本文先引入给读者一个自己研究的机会,下次深入说明一下: 废话不多说,直接上图 新建一个mvc的项目 在视图里面添加一个移动端视图 正常访问一下 Bootstrap自带的响应式的方式(页面代码并没有改变 ...

  5. 响应式图片菜单式轮播,兼容手机,平板,PC

    昨天在给自己用bootstrap写一个响应式主业模版时想用一个图片轮播js,看到了bootstrap里面的unslider.js,只有1.7k,很小,很兴奋,但使用到最后发现不兼容手机,当分辨率变化的 ...

  6. 模拟Bootstrap响应式网格系统

    Bootstrap响应式(适应于不同的终端设备).Bootstrap栅格系统是利用百分比把视口等分为12个,然后利用媒体查询,设置float属性使之并列显示 一.媒体查询 媒体查询包含一个可选的媒体类 ...

  7. iOS开发--Swift RAC响应式编程初探

    时间不是很充足, 先少说点, RAC的好处是响应式编程, 不需要自己去设置代理委托, target, 而是主要以信息流(signal), block为主, 看到这里激动吧, 它可以帮你监听你的事件, ...

  8. JuCheap V2.0响应式后台管理系统模板正式发布beta版本

    JuCheap V1.* 查看地址: http://blog.csdn.net/allenwdj/article/details/49155339 经过半年的努力,JuCheap后台通用响应式管理后台 ...

  9. 响应式布局-Rem的用法

    前言: 文章较为系统地介绍了rem这个新的文字大小单位,绝对干货,绝对好文.转载时略有改动.   先来看看一些基本理念,比如: 响应式网页不仅仅是响应不同类型的设备,而且需要响应不同的用户需求.响应式 ...

随机推荐

  1. jvm堆内存和GC简介

    最近经常遇到jvm内存问题,觉得还是有必要整理下jvm内存的相关逻辑,这里只描述jvm堆内存,对外内存暂不阐述. jvm内存简图 jvm内存分为堆内存和非堆内存,堆内存分为年轻代.老年代,非堆内存里只 ...

  2. MeteoInfoLab脚本示例:MODIS AOD

    MODIS的气溶胶光学厚度(AOD)产品应用很广,数据可以在Giovanni上下载:http://disc.sci.gsfc.nasa.gov/giovanni/overview/index.html ...

  3. rs232转以太网转换器

    rs232转以太网转换器 rs232转网络ZLAN5103可以实现RS232/485/422和TCP/IP之间进行透明数据转发.方便地使得串口设备连接到以太网和Internet,实现串口设备的网络化升 ...

  4. C语言从1打印到100再打印到1该如何编写?我只服最后一种写法!

    我觉得这是一个送分题,奈何人才太多了,给出了各种古怪的写法,如果是做项目的话,我比骄建议一些正常的写法,就是大家都能看得懂的,不要搞什么花里胡哨,不过你要是交流的话,既然是交流,我不觉得要多正规,即使 ...

  5. 【传递闭包】HDU 2157 How many ways??

    UPD:现在才发现本题是个传递闭包 题目内容 春天到了,HDU校园里开满了花,姹紫嫣红,非常美丽. 葱头是个爱花的人,看着校花校草竞相开放,漫步校园,心情也变得舒畅. 为了多看看这迷人的校园,葱头决定 ...

  6. mysql锁 转

    参考文章:https://blog.csdn.net/puhaiyang/article/details/72284702 一.mysql锁的结构图 如上图所示,针对mysql的innodb存储引擎, ...

  7. nginx优化:配置gzip压缩页面提高访问速度(nginx1.18.0)

    一,为什么nginx要使用gzip 1,压缩的作用: 页面使用gzip压缩之后, 页面大小可以压缩到原来的1/7左右, 传输速度和页面打开时间都可以大幅度提高, 有利于用户访问页面体验的提升 2,Ng ...

  8. javascript arcgis 取区域中心点

    javascript arcgis 取区域中心点 //graphic是绘制完多边形之后返回的对象 //获得多边形的中心点坐标 var centerPoint=graphic.geometry.getE ...

  9. python 微信小程序自动化

    微信小程序自动化 https://www.cnblogs.com/yyoba/python27 - FautoTesthttps://www.cnblogs.com/yyoba/p/9973731.h ...

  10. Linux系列:快捷键、目录结构、用户目录

    一.快捷键 1.历史命令 查看历史命令:history [root@centos-master ~]# history 1 2020-10-25 21:03:39 2 2020-09-17 20:43 ...