zoukankan      html  css  js  c++  java
  • Reactive Spring实战 -- 响应式MySql交互

    本文与大家探讨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

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

  • 相关阅读:
    leetcode 86. Partition List
    leetcode 303. Range Sum Query
    leetcode 1310. XOR Queries of a Subarray
    leetcode 1309. Decrypt String from Alphabet to Integer Mapping
    leetcode 215. Kth Largest Element in an Array
    将numpy.ndarray写入excel
    leetcode 1021 Remove Outermost Parentheses
    leetcode 1306. Jump Game III
    leetcode 1305. All Elements in Two Binary Search Trees
    ICCV2019 oral:Wavelet Domain Style Transfer for an Effective Perception-distortion Tradeoff in Single Image Super-Resolution
  • 原文地址:https://www.cnblogs.com/binecy/p/15004375.html
Copyright © 2011-2022 走看看