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

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

  • 相关阅读:
    【BZOJ 4631】4631: 踩气球 (线段树)
    【BZOJ 4148】 4148: [AMPPZ2014]Pillars (乱搞)
    【LYOI 212】「雅礼集训 2017 Day8」价(二分匹配+最大权闭合子图)
    【BZOJ 4104】 4104: [Thu Summer Camp 2015]解密运算 (智商)
    【BZOJ 4103】 4103: [Thu Summer Camp 2015]异或运算 (可持久化Trie)
    【BZOJ 3747】 3747: [POI2015]Kinoman (线段树)
    【BZOJ 3997】 3997: [TJOI2015]组合数学 (DP| 最小链覆盖=最大点独立集)
    【BZOJ 3727】 3727: PA2014 Final Zadanie (递推)
    【BZOJ 3442】 3442: 学习小组 (最大费用流)
    【BZOJ 3218】 3218: a + b Problem(最小割+可持久化线段树)
  • 原文地址:https://www.cnblogs.com/binecy/p/15004375.html
Copyright © 2011-2022 走看看