反应式编程是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正在为其提供支持的事实证明,它是一项技术,它将在将来为我们带来一些帮助。