响应式关系数据库处理R2DBC
简介
之前我们提到过,对于底层的数据源来说,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。
具体的依赖如下:
<!-- R2DBC H2 Driver -->
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<version>${r2dbc-h2.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
创建ConnectionFactory
ConnectionFactory是数据库连接的一个具体实现,通过ConnectionFactory我们可以创建到数据库的连接。
先看一下数据库的配置文件,为了方便起见,这里我们使用的是内存数据库H2 :
r2dbc.url=r2dbc:h2:mem://./r2dbc
r2dbc.user=sa
r2dbc.password=password
第一个url指定的是数据库的连接方式,下面两个是数据库的用户名和密码。
接下来我们看一下,怎么通过这些属性来创建ConnectionFactory:
@Bean
public ConnectionFactory connectionFactory() {
ConnectionFactoryOptions baseOptions = ConnectionFactoryOptions.parse(url);
ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions);
if (!StringUtil.isNullOrEmpty(user)) {
ob = ob.option(USER, user);
}
if (!StringUtil.isNullOrEmpty(password)) {
ob = ob.option(PASSWORD, password);
}
return ConnectionFactories.get(ob.build());
}
通过url可以parse得到ConnectionFactoryOptions。然后通过ConnectionFactories的get方法创建ConnectionFactory。
如果我们设置了USER或者PASSWORD,还可以加上这两个配置。
创建Entity Bean
这里,我们创建一个简单的User对象:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Users {
private Long id;
private String firstname;
private String lastname;
}
初始化数据库
虽然H5有很多更加简单的方式来初始化数据库,比如直接读取SQL文件,这里为了说明R2DBC的使用,我们使用手动的方式来创建:
@Bean
public CommandLineRunner initDatabase(ConnectionFactory cf) {
return (args) ->
Flux.from(cf.create())
.flatMap(c ->
Flux.from(c.createBatch()
.add("drop table if exists Users")
.add("create table Users(" +
"id IDENTITY(1,1)," +
"firstname varchar(80) not null," +
"lastname varchar(80) not null)")
.add("insert into Users(firstname,lastname)" +
"values('flydean','ma')")
.add("insert into Users(firstname,lastname)" +
"values('jacken','yu')")
.execute())
.doFinally((st) -> c.close())
)
.log()
.blockLast();
}
上面的代码中,我们使用c.createBatch()来向数据库插入一些数据。
除了createBatch,还可以使用create来创建单个的执行语句。
获取所有的用户
在Dao中,我们提供了一个findAll的方法:
public Flux<Users> findAll() {
return Mono.from(connectionFactory.create())
.flatMap((c) -> Mono.from(c.createStatement("select id,firstname,lastname from users")
.execute())
.doFinally((st) -> close(c)))
.flatMapMany(result -> Flux.from(result.map((row, meta) -> {
Users acc = new Users();
acc.setId(row.get("id", Long.class));
acc.setFirstname(row.get("firstname", String.class));
acc.setLastname(row.get("lastname", String.class));
return acc;
})));
}
简单解释一下上面的使用。
因为是一个findAll方法,我们需要找出所有的用户信息。所以我们返回的是一个Flux而不是一个Mono。
怎么从Mono转换成为一个Flux呢?
这里我们使用的是flatMapMany,将select出来的结果,分成一行一行的,最后转换成为Flux。
Prepare Statement
为了防止SQL注入,我们需要在SQL中使用Prepare statement:
public Mono<Users> findById(long id) {
return Mono.from(connectionFactory.create())
.flatMap(c -> Mono.from(c.createStatement("select id,firstname,lastname from Users where id = $1")
.bind("$1", id)
.execute())
.doFinally((st) -> close(c)))
.map(result -> result.map((row, meta) ->
new Users(row.get("id", Long.class),
row.get("firstname", String.class),
row.get("lastname", String.class))))
.flatMap( p -> Mono.from(p));
}
看下我们是怎么在R2DBC中使用prepare statement的。
事务处理
接下来我们看一下怎么在R2DBC中使用事务:
public Mono<Users> createAccount(Users account) {
return Mono.from(connectionFactory.create())
.flatMap(c -> Mono.from(c.beginTransaction())
.then(Mono.from(c.createStatement("insert into Users(firstname,lastname) values($1,$2)")
.bind("$1", account.getFirstname())
.bind("$2", account.getLastname())
.returnGeneratedValues("id")
.execute()))
.map(result -> result.map((row, meta) ->
new Users(row.get("id", Long.class),
account.getFirstname(),
account.getLastname())))
.flatMap(pub -> Mono.from(pub))
.delayUntil(r -> c.commitTransaction())
.doFinally((st) -> c.close()));
}
上面的代码中,我们使用了事务,具体的代码有两部分:
c -> Mono.from(c.beginTransaction())
.delayUntil(r -> c.commitTransaction())
开启是的时候需要使用beginTransaction,后面提交就需要调用commitTransaction。
WebFlux使用
最后,我们需要创建WebFlux应用来对外提供服务:
@GetMapping("/users/{id}")
public Mono<ResponseEntity<Users>> getUsers(@PathVariable("id") Long id) {
return usersDao.findById(id)
.map(acc -> new ResponseEntity<>(acc, HttpStatus.OK))
.switchIfEmpty(Mono.just(new ResponseEntity<>(null, HttpStatus.NOT_FOUND)));
}
@GetMapping("/users")
public Flux<Users> getAllAccounts() {
return usersDao.findAll();
}
@PostMapping("/createUser")
public Mono<ResponseEntity<Users>> createUser(@RequestBody Users user) {
return usersDao.createAccount(user)
.map(acc -> new ResponseEntity<>(acc, HttpStatus.CREATED))
.log();
}
执行效果
最后,我们运行一下代码,执行下users:
curl "localhost:8080/users"
[{"id":1,"firstname":"flydean","lastname":"ma"},{"id":2,"firstname":"jacken","lastname":"yu"}]%
完美,实验成功。
本文的代码:webflux-with-r2dbc
本文作者:flydean程序那些事
本文链接:http://www.flydean.com/r2dbc-introduce/
本文来源:flydean的博客
欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
响应式关系数据库处理R2DBC的更多相关文章
- Spring Data R2DBC响应式操作MySQL
1. 前言 在使用R2DBC操作MySQL数据库 一文中初步介绍了r2dbc-mysql的使用.由于借助DatabaseClient操作MySQL,过于初级和底层,不利于开发.今天就利用Spring ...
- Reactive Spring实战 -- 响应式MySql交互
本文与大家探讨Spring中如何实现MySql响应式交互. Spring Data R2DBC项目是Spring提供的数据库响应式编程框架. R2DBC是Reactive Relational Dat ...
- 一步步开发自己的博客 .NET版 剧终篇(6、响应式布局 和 自定义样式)
前言 这次开发的博客主要功能或特点: 第一:可以兼容各终端,特别是手机端. 第二:到时会用到大量html5,炫啊. 第三:导入博客园的精华文章,并做分类.(不要封我) 第四:做 ...
- 网站就必须用响应式布局吗?MVC视图展现模式之移动布局
本文先引入给读者一个自己研究的机会,下次深入说明一下: 废话不多说,直接上图 新建一个mvc的项目 在视图里面添加一个移动端视图 正常访问一下 Bootstrap自带的响应式的方式(页面代码并没有改变 ...
- 响应式图片菜单式轮播,兼容手机,平板,PC
昨天在给自己用bootstrap写一个响应式主业模版时想用一个图片轮播js,看到了bootstrap里面的unslider.js,只有1.7k,很小,很兴奋,但使用到最后发现不兼容手机,当分辨率变化的 ...
- 模拟Bootstrap响应式网格系统
Bootstrap响应式(适应于不同的终端设备).Bootstrap栅格系统是利用百分比把视口等分为12个,然后利用媒体查询,设置float属性使之并列显示 一.媒体查询 媒体查询包含一个可选的媒体类 ...
- iOS开发--Swift RAC响应式编程初探
时间不是很充足, 先少说点, RAC的好处是响应式编程, 不需要自己去设置代理委托, target, 而是主要以信息流(signal), block为主, 看到这里激动吧, 它可以帮你监听你的事件, ...
- JuCheap V2.0响应式后台管理系统模板正式发布beta版本
JuCheap V1.* 查看地址: http://blog.csdn.net/allenwdj/article/details/49155339 经过半年的努力,JuCheap后台通用响应式管理后台 ...
- 响应式布局-Rem的用法
前言: 文章较为系统地介绍了rem这个新的文字大小单位,绝对干货,绝对好文.转载时略有改动. 先来看看一些基本理念,比如: 响应式网页不仅仅是响应不同类型的设备,而且需要响应不同的用户需求.响应式 ...
随机推荐
- JVM系列【2】Class文件结构
JVM系列笔记目录 虚拟机的基础概念 class文件结构 class文件加载过程 jvm内存模型 JVM常用指令 GC与调优 如何查看class字节码文件 在idea中可以通过插件BinEd来查看二进 ...
- 编程体系结构(08):Spring.Mvc.Boot框架
本文源码:GitHub·点这里 || GitEE·点这里 一.Spring框架 1.框架概述 Spring是一个开源框架,框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 ...
- 【数论】HDU 4143 A Simple Problem
题目内容 给出一个正整数\(n\),找到最小的正整数\(x\),使之能找到一个整数\(y\),满足\(y^2=n+x^2\). 输入格式 第一行是数据组数\(T\),每组数据有一个整数\(n\). 输 ...
- 初识 MongoDB 和 .NET Core 入门
昨天搭建完毕 MongoDB 集群 后,开始计划了解 MongoDB ,并引入使用场景,这里介绍一下学习过程中的一些笔记,帮助读者快速了解 MongoDB 并使用 C# 对其进行编码. 浅入 Mong ...
- selenium基础--登录简单的网站
import time from selenium import webdriver from lxml import etree from selenium.webdriver import Act ...
- 【Azure Redis 缓存 Azure Cache For Redis】Redis出现 java.net.SocketTimeoutException: Read timed out 异常
问题描述 在使用Azure Redis时,遇见Read Timed out异常, Redis的客户端使用的时jedis.问题发生时,执行redis部分指令出错,大部分get指令,set指令能正常执行. ...
- 安装Linux注意事项
网络配置NAT Worstation 生成虚拟网卡,编辑虚拟网络中子网IP地址为10网段内部地址,避免冲突. Linux命令 查看主机IP地址 [root@C8 ~]# hostname -I 19 ...
- gethub网址链接
https://github.com/fraser-in-school/suduku 之前这个网址是手动提交的,现在从新更新下,这是用Visio studio提交的,应该不会缺少并不必要的文件 htt ...
- h5 返回上一页面方法
//以下方法仅供参考1.返回上一页,不刷新history.html window.history.go(-1); javascript:window.history.go(-1) 2.返回上一页并刷 ...
- H5页面字体设置
H5页面不支持 MicrosoftYaHei(微软雅黑)别傻傻的设置微软雅黑字体了 如果一定要微软雅黑操作如下 @font-face 定义为微软雅黑字体并存放到 web 服务器上,在需要使用时被自动下 ...