SpringReactive下的数据库交互

原文地址

原文链接

前言

目前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可以调整当前版本文档定位到你需要的版本,这里给的链接是最新文档

查询

查询这里我们说稍微细一点,其他数据库操作也是同理,查询的方法一般有四种

  1. 使用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);
        //..
    }
    
  2. 方法名解析查询,这个很有意思,在你继承ReactiveCrudRepository中写入查询方法时候,编辑器会有提示,如下

    如果你正确填写查询要求作为方法名称,比如getFirstByVideoId就是获取首个视频id为第一个的参数的数据,那么就可以正确查询到数据,非常的人性。就是需要注意参数和方法名称的对应关系,不过这里编辑器会有提示,一般不会写错。合适稍微带些许条件且条件稳定的查询

  3. 手写查询,手写查询就是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);
    
    }
    
    
  4. 构造查询,小伙伴们可能有个疑问,什么是稳定查询,这里的意思就是查询条件不会变的查询。比如根据视频名称查询,无论什么情况只要输入视频名称即可,但是如果是不稳定的查询,可能是如果情况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();

说明一下ReactiveCrudRepositorysave方法有点奇怪,它本身是包含insertupdate两种模式的,如果你的save有主键重复它会自动转换为更新,否则就是插入,这个逻辑倒是没啥问题。但是我们知道在使用mybatis时候,无论在更新还是插入的时候都在方法名中可以通过选择Selective来选择是否尊重数据库已有值/默认值,表现在SQL中就是,空值是否也写入SQL当中。这里奇怪的是在insert下,它是不写入空值的,但是update下它会写入空值的,这点小伙伴们需要稍微注意

原文地址

原文链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值