本文与大家探讨Spring中如何实现MySql响应式交互。

Spring Data R2DBC项目是Spring提供的数据库响应式编程框架。

R2DBC是Reactive Relational Database Connectivity的首字母缩写词。 R2DBC是一个API规范倡议,它声明了一个响应式API,由驱动程序供应商实现,并以响应式编程的方式访问他们的关系数据库。

实现数据库的响应式编程并不是容易的,传统的JDBC协议是一个完全阻塞的 API,所以响应式编程对JDBC协议可以说是一种“颠覆”了。

这里再强调一次响应式编程,响应式编程是一种非阻塞异步的编程模式,而Spring响应式编程提供了一种友好、直观、易于理解的编码模式处理异步结果(可参考前面的文章)。

也就是说,应用发送SQL给数据库后,应用线程不需要阻塞等待数据库返回结果,而是直接返回处理其他任务,等到数据库SQL处理完成后,再由Spring调用线程处理结果。

到目前,Spring Data R2DBC项目支持以下数据库:

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)

下面基于MySql,介绍一下Spring Data R2DBC使用方式。

引入依赖

    <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency> <dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
<version>0.8.2.RELEASE</version>
</dependency>

配置文件

spring.r2dbc.url=r2dbcs:mysql://127.0.0.1:3306/bin-springreactive?useSSL=false
spring.r2dbc.username=...
spring.r2dbc.password=...

Spring Data R2DBC可以与Spring Data JPA结合使用,其实R2DBC与原来的JPA使用方式差别不大,使用非常简单。

只是Spring Data JPA中方法返回的是真实的值,而R2DBC中,返回的是数据流Mono,Flux。

简单介绍一个Spring Data JPA。Spring Data JPA是Spring基于ORM框架、JPA规范的基础上封装的一套 JPA (Java Persistence API) 应用框架,简单说,就是类似Mybatis,Hibernate的框架(Spring Data JPA底层通过Hibernate操作数据库)。

Repository是Spring Data R2DBC中的重要概念,封装了对一个实体的操作,相当于一个dao(Data Access Object,数据访问对象)。

假如应用中有一个实体DeliveryCompany,对应表delivery_company。

实体定义如下:

public class DeliveryCompany {
@Id
private long id;
private String name;
private String label;
private Integer level;
...
}

@Id注解标志了id属性。

下面我们定义一个DeliveryCompanyRepository接口,继承与R2dbcRepository。

@Repository
public interface DeliveryCompanyRepository extends R2dbcRepository<DeliveryCompany,Long> {
...
}

R2dbcRepository是Spring实现的接口,该接口继承与ReactiveCrudRepository,ReactiveCrudRepository接口提供了增删改查的模板方法。

public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> Mono<S> save(S var1); <S extends T> Flux<S> saveAll(Iterable<S> var1); <S extends T> Flux<S> saveAll(Publisher<S> var1); Mono<T> findById(ID var1); Mono<T> findById(Publisher<ID> var1); ...
}

注意这里的返回结果,是Mono、Flux等异步结果,这就是响应式交互与非响应式交互的最大区别。

如果要自定义操作,有以下方式

(1) 通过方法名定义

只要我们按规则定义方法名,Spring就会为我们生成SQL。

// 按名称查找
Flux<DeliveryCompany> findByName(String name); // 查找给定范围内的
Flux<DeliveryCompany> findByIdGreaterThan(Long startId); // 查找大于给定id的数据
Flux<DeliveryCompany> findByIdGreaterThan(Long startId); // 查询名称以给定字符串开头的数据
Flux<DeliveryCompany> findByNameStartingWith(String start); // 分页
Flux<DeliveryCompany> findByIdGreaterThanEqual(Long startId, Pageable pageable);

注意,上面方法名需要按规范定义

findByName -> findBy<fieldName>
findByIdGreaterThan -> findBy<fieldName>GreaterThan

Spring会为我们生成对应的SQL,非常方便。这种方法可以满足多数简单的查询。

对应的还有删除操作

Mono<Integer> deleteByName(String name);

详细的方法命名规则,则参考官方文档。

(2)手动编写SQL

对于复杂的SQL,开发人员也可以手写SQL,

@Query("select  id,name from delivery_company where id in  (:ids)")
Flux<DeliveryCompany> findByIds2(List<Long> ids); @Query("select id,name from delivery_company where name = :name")
Flux<DeliveryCompany> findByName2(String name); @Modifying
@Query("update delivery_company set name = :name where id = :id")
Mono<DeliveryCompany> update2(@Param("id") long id, @Param("name") String name);

可以看到,编写SQL也非常简单,对于集合参数支持非常好。

目前未发现使用JPQL(Java Persistence Query Language)的方式,不过使用原生的SQL是没有问题的。

如果大家使用过Mybatis,应该会用过以下判断参数非空的做法

<select id="findByName2"
resultType="DeliveryCompany">
SELECT * FROM delivery_company
WHERE name = #{name}
<if test="label != null">
AND label like #{label}
</if>
</select>

可惜在JPA中非找到支持的方法,如果有同学知道,请不吝指教。

(3) 使用R2dbcEntityTemplate

另外,可以使用R2dbcEntityTemplate自动生成SQL

    @Autowired
private R2dbcEntityTemplate template; public Flux<DeliveryCompany> getByName3(String name) {
return template
.select(DeliveryCompany.class)
.from("delivery_company")
.matching(Query.query(Criteria.where("name").is(name))).all();
// Criteria.where("name").is(name).and
} public Mono<Integer> update3(DeliveryCompany company) {
return template
.update(DeliveryCompany.class)
.inTable("delivery_company")
.matching(Query.query(Criteria.where("id").is(company.getId())))
.apply(Update.update("name", company.getName()));
}

这种方式可以实现判断参数非空查询,不过使用起来较为繁琐(我们也可以对其进行一定的封装以方便我们使用)。

(4)Spring Data R2DBC中同样支持Querydsl,

我们定义的Repository可以继承于ReactiveQuerydslPredicateExecutor,该接口提供以下模板方法

public interface ReactiveQuerydslPredicateExecutor<T> {
Mono<T> findOne(Predicate var1); Flux<T> findAll(Predicate var1); Flux<T> findAll(Predicate var1, Sort var2); Flux<T> findAll(Predicate var1, OrderSpecifier... var2); Flux<T> findAll(OrderSpecifier... var1); Mono<Long> count(Predicate var1); Mono<Boolean> exists(Predicate var1);
}

Spring Data R2DBC中同样支持@QuerydslPredicate注解,这里不再深入。

Spring Data R2DBC支持事务,使用方法很简单,在业务方法添加@Transactional即可

    @Transactional
public Flux<DeliveryCompany> save(List<DeliveryCompany> companyList) {
Flux<DeliveryCompany> result = Flux.just();
for (DeliveryCompany deliveryCompany : companyList) {
result = result.concat(result, repository.save(deliveryCompany));
}
return result;
}

为了展示事务的使用,这里没有调用Repository的saveAll方法,而是循环插入数据并返回最后的结果。

注意,最后的结果Flux、Mono一定要作为方法返回值,因为响应式编程的异常信息保存在这些结果中(而不是在方法调用时抛出),所以这些结果必须作为方法返回值,否则Spring无法知道方法是否报错,也就无法回退事务。

Spring Data R2DBC基本与Spring Data JPA的使用相同,所以本篇文章主要还是对Spring Data JPA使用方式的介绍。

我之前并没有使用过Spring Data JPA,本篇文章主要还是入门介绍,还有很多东西没有涉及,如id生成,多表查询等,这里不再一一介绍。

官方文档:https://docs.spring.io/spring-data/r2dbc/docs/1.3.2/reference/html/

文章完整代码:https://gitee.com/binecy/bin-springreactive/tree/master/delivery-service

如果您觉得本文不错,欢迎关注我的微信公众号,系列文章持续更新中。您的关注是我坚持的动力!

Reactive Spring实战 -- 响应式MySql交互的更多相关文章

  1. Reactive Spring实战 -- 响应式Redis交互

    本文分享Spring中如何实现Redis响应式交互模式. 本文将模拟一个用户服务,并使用Redis作为数据存储服务器. 本文涉及两个java bean,用户与权益 public class User ...

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

    本文分享如何使用KRaft部署Kafka集群,以及Spring中如何实现Kafka响应式交互. KRaft 我们知道,Kafka使用Zookeeper负责为kafka存储broker,Consumer ...

  3. Reactive 理解 SpringBoot 响应式的核心-Reactor

    Reactive 理解 SpringBoot 响应式的核心-Reactor bestcoding 2020-02-23 17:26:43 一.前言 关于 响应式 Reactive,前面的两篇文章谈了不 ...

  4. 第二百五十一节,Bootstrap项目实战--响应式轮播图

    Bootstrap项目实战--响应式轮播图 学习要点: 1.响应式轮播图 本节课我们要在导航条的下方做一张轮播图,自动播放最新的重要动态. 一.响应式轮播图 响应式轮播图 第一步,设置轮播器区域car ...

  5. 第二百五十节,Bootstrap项目实战--响应式导航

    Bootstrap项目实战--响应式导航 学习要点: 1.响应式导航 一.响应式导航 基本导航组件+响应式 第一步,声明导航区域,设置导航默认样式,设置导航条固定在顶部navbar样式class类,写 ...

  6. Java9第四篇-Reactive Stream API响应式编程

    我计划在后续的一段时间内,写一系列关于java 9的文章,虽然java 9 不像Java 8或者Java 11那样的核心java版本,但是还是有很多的特性值得关注.期待您能关注我,我将把java 9 ...

  7. Spring 5 响应式编程

    要点 Reactor 是一个运行在 Java8 之上的响应式流框架,它提供了一组响应式风格的 API 除了个别 API 上的区别,它的原理跟 RxJava 很相似 它是第四代响应式框架,支持操作融合, ...

  8. Reactive(1) 从响应式编程到"好莱坞"

    目录 概念 面向流设计 异步化 响应式宣言 参考文档 概念 Reactive Programming(响应式编程)已经不是一个新东西了. 关于 Reactive 其实是一个泛化的概念,由于很抽象,一些 ...

  9. Reactive Spring实战 -- WebFlux使用教程

    WebFlux是Spring 5提供的响应式Web应用框架. 它是完全非阻塞的,可以在Netty,Undertow和Servlet 3.1+等非阻塞服务器上运行. 本文主要介绍WebFlux的使用. ...

随机推荐

  1. 025.Python面向对象以及对对象的操作

    一 面向对象基本概念 1.1 OOP面向对象的程序开发 用几大特征表达一类事物称为一个类,类更像是一张图纸,表达只是一个抽象概念 对象是类的具体实现,更像是由这图纸产出的具体物品,类只有一个,但是对象 ...

  2. 第一天:python学习-基础-计算机简史

    第一天:计算机简史 1.元始的计算方式 : 1.1:数手指头,结绳记事.符号记事.算筹(祖冲之-圆周率).算盘:计算效率较低. 1.2:15世纪航海活动各天文需求 计算比较繁重,出现计算尺工具,奥特雷 ...

  3. 使用vue-i18n实现中英文切换(内含动态属性的绑定)

    最近做学生管理系统,因为有国外的学生,所以要进行中英文切换,查了查Vue中使用vue-i18n插件能够实现网页的中英文切换,学习内容如下: 一.下载vue-i18n插件 npm install vue ...

  4. Nginx限制访问速率和最大并发连接数模块--limit

    Nginx限制访问速率和最大并发连接数模块--limit Tengine版本采用http_limit_req_module进行限制 具体连接请参考 http://tengine.taobao.org/ ...

  5. 书列荐书 |《黑天鹅&#183;如何应对不可预知的未来》【美】纳西姆 尼古拉斯 塔勒布 著

    你不知道的事比你知道的事更有意义,因为生活中发生了许多微小的事情,尽管出现的概率非常小,但是却以某一种巨大的力量影响我们的生活.但是由于思维习惯的问题,导致我们看问题的方式使得我们不能很快地把握事物的 ...

  6. 实时实例分割的Deep Snake:CVPR2020论文点评

    实时实例分割的Deep Snake:CVPR2020论文点评 Deep Snake for Real-Time Instance Segmentation 论文链接:https://arxiv.org ...

  7. Redis系列(五):消息队列

    消息队列已经成为现在互联网服务端的标配组件,现在比较常用的消息中间件有RabbitMQ.Kafka.RocketMQ.ActiveMQ.说出来你可能不信,Redis作为一个缓存中间件,居然也提供了消息 ...

  8. 编译原理-DFA的化简(最小化)

    对于给定的DFA    M,寻找一个状态数比M小的DFA    M'使得L(M)=L(M') 1.状态的等价性: 假设s和t为M的两个状态 ①若分别从状态s和状态t出发都能读出某个字α而停止于终态,则 ...

  9. 给STM32MP157C-DK2烧录固件

    环境: 一台PC(window/linux) STM32CubeProgrammer 我下载到的是 2.1 版本(19\07\10下载的) 里面的文件是: 里面有 3 个文件,分别window.Lin ...

  10. javascript之强制类型转换

    在javascript中,常会发生强制类型转换的情况有以下几种 字符串拼接 var a = 1; var b = a + '1'; console.log(b); //11 ==运算符 var a = ...