本文与大家探讨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. shell基础之for循环语句

    For语句 格式:for name [ [ in [ word ... ] ] ; ] do list ; done for 变量名 in 取值列表; do 命令 done 或者 for 变量名 in ...

  2. Linux服务之cobbler批量部署篇

    一.Cobbler简介:Cobbler通过将设置和管理一个安装服务器所涉及的任务集中在一起,从而简化了系统配置.相当于Cobbler封装了DHCP.TFTP.XINTED等服务,结合了PXE.kick ...

  3. 90%的人都不知道的Node.js 依赖关系管理(上)

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://dzone.com/articles/nodejs-dependency-mana ...

  4. nginx反向代理网站镜像

    某些公司会墙特定网站,如果你有一个可访问的域名和服务器,就可以通过nginx反向代理来来解决这些问题.比如现在我们用mirror.example.com镜像www.baidu.com,以下是详细操作. ...

  5. easyui datagrid 自定义单元格单击与双击事件(Day_38)

    $(function(){ $('#tableId').datagrid({//单击事件   onClickRow:function(rowIndex,rowData){  alert("单 ...

  6. MySQL之数据查询语言(DQL)

    数据查询语言DQL基本结构是由SELECT子句,FROM子句,WHERE子句组成的查询块: SELECT <字段> FROM <表名> WHERE <查询条件> - ...

  7. JVM-垃圾收集算法基础

    目录 目录 前言 手动释放内存导致的问题 垃圾判定方法 哪些对象是垃圾? 引用计数算法 可达性分析法 垃圾收集算法 标记-清除 优点 缺点 优化 标记-复制 优点 缺点 优化 标记-整理 优点 缺点 ...

  8. Could not get JDBC Connection排查

    最近在维护的一个比较旧的项目,发现总是隔一段时间JDBC就报错: Could not get JDBC Connection; nested exception is org.apache.commo ...

  9. GO语言复合类型05---递归

    package main import ( "fmt" "time" ) /* ·递归就是自己调自己 ·递归一定要有终止条件(否则就是无限死循环) */ /*使 ...

  10. Go基础结构与类型01---常量变量表达式

    // 包名(main包下的main函数是程序的入口) package main // 导入sdk(software developing kit)中的fmt包 import "fmt&quo ...