在Spring项目中如何处理R2DBC的实体关系? - sipios


反应式编程是Web开发中似乎越来越多的短语。新的工具和框架正在发展中。
在反应式编程中,调用者组件不仅将输入数据发送到工作组件,而且还订阅返回的数据流。这样,他们就不必等待输出数据了。相反,当这些数据可用时,将通知他们,然后他们将对这些数据进行处理。
 
R2DBC定义
有三点要理解:

  • R2DBC是提供接口的规范。供应商应实现它以提供对不同数据库(PostgreSQL,MySQL等)的访问。
  • 它基于Reactive Streams规范,该规范已被Java生态系统的一些主要方面(例如Vert.X,MongoDB驱动程序,项目Reactor)采用。
  • 它的目的是为现有的关系数据库驱动程序规范(例如JDBC)提供一种非阻塞的替代方法。

让我们看一下R2DBC在分层架构中位置:
  • 控制器层处理来自外部的请求,进行一些验证,然后将请求分派到服务层。
  • 服务层从控制器获取输入,然后根据业务规范应用一些计算。(banq注:复杂业务需要领域层负责业务计算)
  • 为了使这种计算成为可能,服务层必须依靠数据访问层,该层将处理对数据源的访问。

对于关系数据库,R2DBC是一种使数据访问层和数据库之间的接口具有反应性的方法。结果,每次该层调用数据库时,它将释放工作线程并等待直到数据库警告它准备好处理结果为止。
 

使用R2DBC配置项目
为了能够使用Spring Data R2DBC,我添加了以下依赖项。我决定使用PostgreSQL作为数据库,因为它是最常用的关系数据库之一。您可以在此处找到几个数据库的可用驱动程序列表。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-postgresql</artifactId>
</dependency>

并且您需要添加一个配置类,在其中声明连接工厂,spring数据将使用该连接工厂执行使用R2DBC规范的请求。此配置取决于您为项目选择的供应商驱动程序。对于PostgreSQL,下面是一个示例:

@Configuration
@EnableR2dbcRepositories
public class PostgresConfig extends AbstractR2dbcConfiguration {
    @Override
    @Bean
    public ConnectionFactory connectionFactory() {
        return new PostgresqlConnectionFactory(
           PostgresqlConnectionConfiguration.builder()
                   .host("localhost")
                   .port(5433)
                   .username(
"postgres")
                   .password(
"admin")
                   .database(
"mydb")
                   .build());
    }
}

 
处理没有关系的实体
在这一部分中,我们将专注于映射:将一个Java模型类映射到一个表。
创建一个表,其中包含以下列:

CREATE TABLE person
(
    id uuid NOT NULL DEFAULT uuid_generate_v4(),
    name character varying(255) COLLATE pg_catalog."default" NOT NULL,
    street character varying(255) COLLATE pg_catalog.
"default" NOT NULL,
    zip_code character varying(255) COLLATE pg_catalog.
"default" NOT NULL,
    city character varying(255) COLLATE pg_catalog.
"default" NOT NULL,
    CONSTRAINT person_pkey PRIMARY KEY (id)
)

映射到Spring Data对象DTO:
public class Person {
    @Id
    UUID id;
    String name;
    String street;
    String zipCode;
    String city;
}

在这种情况下,将在属性和列之间进行映射。
@Id注释是指定哪一列是主键的一种方法。如果该属性为NULL,那么Spring将在保存对象时在表中创建新行。如果该属性不为NULL,那么Spring将尝试更新现有属性。您可以在此处看到新实体和要更新的实体之间进行区分的其他方法。
您需要注意的一件事:没有像JPA @GeneratedValue这样的注释。您需要正确配置数据库,因为它将处理自动生成的主键。
为了能够访问数据库,Spring为我们提供了一个接口:R2dbcRepository。您可以创建存储库接口来扩展它:

@Repository
public interface PersonRepository extends R2dbcRepository<Person, UUID> {
}

 
处理实体之间的关系
这是R2DBC的痛点及其弹簧处理。让我们记住,Spring Data R2DBC不是ORM,因此它本身并不处理实体之间的关系。我们将必须找到解决方法并手动进行一些操作。
为了本文的好处,我问过Spring开发团队(这里是问题的链接)是否计划处理这些关系,以下是答案:
解决关系的读是最需要的主题之一。但是,由于我们不打算引入N + 1问题,因此我们需要一种适当的方法来获取单个查询中的所有关系。
因此,我们现在需要手动处理。这是我找到的一些解决方案。让我们更改用例并在数据库中创建一些表:
CREATE TABLE author
(
    id uuid NOT NULL,
    name character varying(255) COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT author_pkey PRIMARY KEY (id)
)
CREATE TABLE book
(
    id uuid NOT NULL,
    title character varying(255) COLLATE pg_catalog.
"default" NOT NULL,
    author uuid NOT NULL,
    date_of_parution timestamp without time zone NOT NULL,
    CONSTRAINT book_pkey PRIMARY KEY (id),
    CONSTRAINT
"book_to_author_FK" FOREIGN KEY (author)
    REFERENCES public.author (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE NO ACTION
)

在这里,我们将处理多对一关系,但是一对一是相同的想法。当您执行SQL连接时,结果数将与Java中的实体数相同。您可以使用Reading Converter来管理,它使Spring知道如何将数据从数据库映射到Java模型(Writing Converter:可让Spring知道如何将数据从Java模型映射到数据库)。例如:
@ReadingConverter
public class BookReadConverter implements Converter<Row, Book> {
    @Override
    public Book convert(Row source) {
        Author author = Author.builder()
                           .name(source.get("authorName", String.class))
                           .id(source.get(
"authorId", UUID.class))
                           .build();
        return Book.builder()
                  .id(source.get(
"id", UUID.class))
                  .author(author)
                  .title(source.get(
"title", String.class))
                  .dateOfParution(source.get(
"date_of_parution", LocalDate.class))
                   .build();
    }
}

不要忘记在配置类上声明它,然后您可以通过执行join来完成Book Repository:
@Repository
public interface BookRepository extends R2dbcRepository<Book, UUID> {
    @Query("select book.*, author.id as authorId, author.name as authorName from Book book join Author author on author.id = book.author ")
    public Flux<Book> findAll();
}

有趣的是,使用bufferUntilChanged方法将在一个作者对象完成后立即将一些聚合数据推入返回Flux中。
 
结合JDBC和R2DBC
与JPA实现相比,R2DBC要求开发人员执行更多的手动操作来处理应用程序的ORM。
在应用程序的开发过程中是否可以将两种方法结合在一起?
 
总结
就目前而言,我认为Spring Data R2DBC尚未完全为每个生产项目准备就绪。
  • 没有高并发性问题的项目不需要使用它
  • 某些有用的功能仍未实现(例如改善关系处理),但是它们在团队路线图中,即使目前尚未定义明确的发布日期。Spring团队完全了解开发人员的需求,并且正在为此工作。

尽管如此,在某些特殊情况下,如果开发团队准备做更多的手动工作,则值得考虑的是R2DBC带来的性能改进。Spring正在为其提供支持的事实证明,它是一项技术,它将在将来为我们带来一些帮助。