Spring in Action 5 Self-learning notes 11 - Reactive 存取数据

Spring in Action 5 Self-learning notes 11 - Reactive 存取数据

上一篇的当务之急就是还没有响应式的数据库,JPA无法直接返回Flux或者Mono类型。 目前Spring 响应式支持的数据库有Cassandra,MongoDB,Couchbase和Redis。 非响应式数据库 非响应式数据库的主要支持来自于Spring Data JPA,遗憾的是,这些关系型数据库

上一篇的当务之急就是还没有响应式的数据库,JPA无法直接返回Flux或者Mono类型。 目前Spring 响应式支持的数据库有Cassandra,MongoDB,Couchbase和Redis。

非响应式数据库

非响应式数据库的主要支持来自于Spring Data JPA,遗憾的是,这些关系型数据库都不是响应式的。 如果一定要使用,可以在查询到数据后将其转换成响应式,或者响应式数据到达订阅者的时候,将其转换成非响应式数据:
List<Order> orders = repo.findByUser(someUser);
Flux<Order> orderFlux = Flux.fromIterable(orders);
Order order repo.findById(Long id);
Mono<Order> orderMono = Mono.just(order);
响应式数据转换成非响应式数据:
Taco taco = tacoMono.block();
tacoRepo.save(taco);

Iterable<Taco> tacos = tacoFlux.toIterable();
tacoRepo.saveAll(tacos);
一般也可以采用订阅的形式:
tacoFlux.subscribe(taco -> {
    tacoRepo.save(taco);
});
注意,这些语句,在查询数据库,保存数据和转换成非响应式数据的时候,都是阻塞的。 如果一个Web应用想要是响应式的,必须从控制器到数据库全部是响应式的,所以要发挥响应式的真正作用,还是赶快来看看真正的响应式吧。 SIA5里提供了Cassandra和MongoDB的例子。不过我打算学习MongoDB和Redis,这里先按照书把MongoDB学一下并运用,之后的Redis要自学补上这一章,毕竟Redis太好用了。

使用MongoDB

MongoDB是一个文档数据库,实际上MongoDB存储的是二进制JSON文件,也被称为BSON,可以像SQL数据库一样查询取出来。 Spring Initializr提供了两个依赖,注意选择Reactive的那个:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>
        spring-boot-starter-data-mongodb-reactive
    </artifactId>
</dependency>
注意,和其他的响应式数据库一样,这是JPA的替代品,所以需要把JPA,H2数据库等一并从依赖里去掉,或者单独创建一个新项目来试验一下。 导入依赖后的默认MongoDB会运行在本机的27017端口,为了方便也可以使用集成的MongoDB,可以在Spring Initializr中勾选,也可以单独添加依赖:
<dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
与H2一样,所有数据在应用停止之后会全部消失。 MongoDB的配置与其他数据库很相似:
  1. spring.data.mongodb.host,主机地址,默认localhost
  2. spring.data.mongodb.port,端口,默认27017
  3. spring.data.mongodb.username,用户名
  4. spring.data.mongodb.password,密码
  5. spring.data.mongodb.database,数据库名称,默认是test

Java类与MongoDB数据库的映射关系

Spring Data MongoDB提供了很多注解,不过只有三个是最常用的:
  1. @Id,标记文档ID
  2. @Document,标记一个文档
  3. @Field,标记一个域(可定义顺序),就是个键名,存入文档中。
对于每一个映射类,@Id@Document是一定要有的,不标记@Field的属性会默认有同名属性。 把Student类改成MongoDB的类:
package cc.conyli.webflux.domain;

import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;

@Data
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
@RequiredArgsConstructor
@Document
public class Student implements Serializable {


    @Id
    private String id;

    private String firstName;

    private String lastName;

    private Integer courseId;

}
现在因为不使用JPA了,所以@Entity@Table之类的注解都没有了,换成了@Document注解,表示这个类被映射为一个文档。 默认文档的名称是第一个字母小写的类名,不过也可以自定义:@Document(collection="studentds")@Id可以用在任意可以序列化的域变量上,包括字符串和Long类型。这个id的名称无所谓,只要是字符串类型,在Mongo内就必定会生成_id键,如果不指定@Id,会自动生成。这个可以看Spring Data MongoDB - Reference Documentation 把Course做一下修改:
package cc.conyli.webflux.domain;

import lombok.Data;

import lombok.NoArgsConstructor;
import lombok.NonNull;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;

@Data
@NoArgsConstructor(force = true)
@Document
public class Course {

    @Id
    private String id;
    
    @NonNull
    @Size(min=5,message = "At least 5 characters long")
    private String courseName;

    private Date createAt = new Date();
    
    private List<Student> students;
    
}
比想象的要简单,@Id不像JPA一样只能是INT或者LONG,也不像Cassandra是UUID,可以是任意可序列化的类型。实际上如果选择String类型作为ID,Mongo自动分配一个值存进去(如果是null的话),实际上是获得了一个数据库管理的ID,无需再手动设置。 注意List<Student>,这个不会单独在Mongo中再另外存一个collection,而是会直接把这个列表进行存储。 原书还展示了Order对象和User对象的存储,一样的简单,除了标识ID之外,几乎不用做任何调整。 当然也会有其他高级和不经常使用的映射方法,不过目前先了解这些足够了。

编写MongoDB的神奇接口

有两个接口可以选择:
  1. ReactiveCrudRepository,使用.save()方法保存新老数据
  2. ReactiveMongoRepository,提供了一些insert()方法,写入的时候更高效
对于不需要经常变动的文档,可以选择前者。需要经常写入的,推荐选择后者p。 写一个简单的学生接口试试:
package cc.conyli.webflux.repository;

import cc.conyli.webflux.domain.Student;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.web.bind.annotation.CrossOrigin;

@CrossOrigin(origins = "*")
public interface StudentRepo extends ReactiveCrudRepository<Student, Integer> {

}
由于这个接口是响应式,注意了,返回的是Flux或者Mono对象,findAll方法会返回Flux<Student>,findById则会返回Mono<Student>。 再来编写一个使用insert的接口:
package cc.conyli.webflux.repository;

import cc.conyli.webflux.domain.Course;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.web.bind.annotation.CrossOrigin;
import reactor.core.publisher.Flux;

@CrossOrigin(origins = "*")
public interface CourseRepo extends ReactiveMongoRepository<Course, Integer> {
    Flux<Course> findByOrderByCreateAtDesc();
}
ReactiveMongoRepository相比ReactiveCrudRepository唯一的缺点是仅能用于MongoDB,无法移植。 增和查全部的会了,按照id查找的话,需要重写一下神奇接口中的方法,因为默认的方法是按照Int类型查:
@CrossOrigin(origins = "*")
public interface StudentRepo extends ReactiveCrudRepository<Student, Integer> {
    Mono<Student> findById(String id);
}    
@GetMapping("/students/{id}")
public Mono<Student> getSingleStudent(@PathVariable("id") String id) {
    return studentRepo.findById(id);
}
而修改对于MongoDB来说,就是直接找到对应的id然后更改对象。由于是一个流,可以取出来之后使用流操作,比较方便。 这里后续找到了一篇文章,说的比SIA5要完整详细一些响应式Spring的道法术器(Spring WebFlux 快速上手 + 全面介绍)。 准备跟着这个来做一遍了。
LICENSED UNDER CC BY-NC-SA 4.0
Comment