原文地址
前言
目前spring
提供的web
框架有两种servlet
以及reactive
,框架相关不是本文关键,可以参考Servlet vs Reactive Stacks in Five Use Cases以及Reactor 3 Reference Guide,之后会单独出文章聊关于反应式web
框架
实现
spring reactive
目前官方和数据库交互引擎还不支持mysql
,我们需要引用第三方引擎来支持
引包
这个我们本来用的是r2dbc-mysql
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
<version>0.8.2.RELEASE</version>
</dependency>
但是由于太长时间没有维护,所以这里我们换成jasync-r2dbc-mysql
<dependency>
<groupId>com.github.jasync-sql</groupId>
<artifactId>jasync-r2dbc-mysql</artifactId>
<version>2.1.24</version>
</dependency>
当然基本的r2dbc
也是要引用的
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
<version>3.0.6</version>
</dependency>
配置
和普通的servlet
配置差不多
spring:
r2dbc:
url: r2dbc:mysql://domin.baidu.com:9527/schema
username: root
password: root
Mapper
这里一般命名为XxxxRepository
,我这里统一起见,还是使用Mapper
作为后缀
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface VideoMapper extends ReactiveCrudRepository<Video, String> {
}
数据库操作
本文只介绍下我觉得常用的方法和功能,肯定不能涵盖所有的数据库操作方式。如果想了解更详细的内容以及其他方式请参考Spring Data R2DBC - Reference Documentation,根据url
可以调整当前版本文档定位到你需要的版本,这里给的链接是最新文档
查询
查询这里我们说稍微细一点,其他数据库操作也是同理,查询的方法一般有四种
-
使用
ReactiveCrudRepository
提供默认封装方法,这个是适合简单的增删改查,大家可以自行进入源码查看,虽然不是很多就是了,注意和mybatis
的封装包一样,如果需要使用主键查询,需要在实体类中使用@Id
标识主键@NoRepositoryBean public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> { //... Mono<T> findById(ID id); Mono<T> findById(Publisher<ID> id); Flux<T> findAll(); Flux<T> findAllById(Iterable<ID> ids); Flux<T> findAllById(Publisher<ID> idStream); //.. }
-
方法名解析查询,这个很有意思,在你继承
ReactiveCrudRepository
中写入查询方法时候,编辑器会有提示,如下如果你正确填写查询要求作为方法名称,比如
getFirstByVideoId
就是获取首个视频id
为第一个的参数的数据,那么就可以正确查询到数据,非常的人性。就是需要注意参数和方法名称的对应关系,不过这里编辑器会有提示,一般不会写错。合适稍微带些许条件且条件稳定的查询 -
手写查询,手写查询就是
mybatis
查询的简易版本,适合条件较多且稳定的查询,这个给个简单示例public interface VideoMapper extends ReactiveCrudRepository<Video, String> { @Query("select * from video where auth_id is null and video_collection_id = :collectionId order by video_num") Flux<Video> getVideosNoAuth(String collectionId); }
-
构造查询,小伙伴们可能有个疑问,什么是稳定查询,这里的意思就是查询条件不会变的查询。比如根据视频名称查询,无论什么情况只要输入视频名称即可,但是如果是不稳定的查询,可能是如果情况1就根据视频名称查询,如果条件2就根据视频上传人查询。这样上面三种就都不合适了,我们需要自己构造查询条件,这个给大家一个简单的示例,当然如果小伙伴们能自己封装更方便的方法调用那是最好了,但是反应式框架不作为我的主要使用框架,我这里就不封装了
@Override public Flux<VideoCollection> personVideo(VideoCollectionPersonParam param) { Criteria stepByUser = ObjectUtils.isEmpty(param.getUserId()) ? Criteria.where("auth_id").isNull() : Criteria.where("auth_id").is(param.getUserId()); Criteria stepByNameLike = ObjectUtils.isEmpty(param.getVideoColName()) ? stepByUser : stepByUser.and(Criteria.where("collection_name") .like(String.join(param.getVideoColName(), "%", "%"))); return template.select(VideoCollection.class).from("video_collection") .matching(Query.query(stepByNameLike).offset(param.getOffset()) .limit(param.getLimit())).all(); }
查询是最后一句,上面两个是根据参数不同分装的查询条件
存储
反应式框架是倒推的,如果你的操作没有返回结果,那么相关操作就不会被执行。所以数据存储函数,如果返回void
,那么会导致程序不会跑相关代码。如果不希望返回需要使用Mono<Void>
来代替,并连接返回和流程的关系,不能让流程线被切断
{
@Resource
private VideoBarrageRefMapper videoBarrageRefMapper;
public Mono<Void> insertSubBarrageList(FilePart file, String videoId) {
VideoBarRef ref = videoBarRefService.build(file, videoId);
return videoBarrageRefMapper.save(ref).then();
}
}
这里的then()
的作用就是如此,但如果你的代码如下
{
@Resource
private VideoBarrageRefMapper videoBarrageRefMapper;
public void insertSubBarrageList(FilePart file, String videoId) {
VideoBarRef ref = videoBarRefService.build(file, videoId);
videoBarrageRefMapper.save(ref);
}
}
从编程逻辑的角度来说似乎是没有问题的,但是反应流程线被切断,相关代码不会被执行,数据也就没有存储,编辑器也会提示Value is never used as Publisher
删除
这个就不多说了,主键删除就用默认的封装方法,如果是复杂删除就在方法名解析和构造中选择一个即可,同样注意反应流程线不要被切断
更新
简单给个示例
return needUpdateObjecMono.flatMap(entity -> videoBarrageRefMapper.getAllByVideoId(entity.getVideoId())
.collectList().flatMap(
resultList -> {
if (ObjectUtils.isEmpty(resultList)) {
return videoBarrageRefMapper.save(entity);
} else {
VideoBarrageRef oldRef = resultList.get(0);
//set update data
return videoBarrageRefMapper.save(oldRef);
}
}
)).then();
说明一下ReactiveCrudRepository
的save
方法有点奇怪,它本身是包含insert
和update
两种模式的,如果你的save
有主键重复它会自动转换为更新,否则就是插入,这个逻辑倒是没啥问题。但是我们知道在使用mybatis
时候,无论在更新还是插入的时候都在方法名中可以通过选择Selective
来选择是否尊重数据库已有值/默认值,表现在SQL
中就是,空值是否也写入SQL
当中。这里奇怪的是在insert
下,它是不写入空值的,但是update
下它会写入空值的,这点小伙伴们需要稍微注意