在Spring專案中如何處理R2DBC的實體關係? - sipios

banq發表於2021-01-15

反應式程式設計是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正在為其提供支援的事實證明,它是一項技術,它將在將來為我們帶來一些幫助。

相關文章