您不需要使用Spring WebFlux和R2DBC Hibernate


在Java中使用关系数据库时,问题之一是由于数据源的表格性质,它们无法转换对象关系(例如组合)。这意味着作为开发人员,我们通常倾向于拥有一个中间层,该中间层负责抽象数据源的数据组织。这称为ORM或对象关系映射。在Spring的生态系统中,事实上的标准是Hibernate,但是尚未提供新的非阻塞Spring R2DBC API。

坦白地说,您不需要Hibernate进行对象映射,因为R2DBC易于使用,您可以自己完成。而且,这是一种极大的乐趣,特别是因为我们(Java开发人员)倾向于依赖于不是软件设计最新技术的工具。

这篇文章着重于从Postgresql获取组合实体(使用INNER JOIN)并使用具有自定义Spring ReactiveCrudRepository扩展名的Data Mapper模式(如EAA的P中定义)将其映射的问题。

问题 在面向对象的程序设计中,我们基于聚合,关联或组成来处理彼此之间拥有丰富关系的对象。想象一下,您在一个任务管理应用程序上工作,其中每个任务实体都拥有对项目实体的引用。或者,另一种情况是工作应用程序,其中工作实体指向相应的雇主对象。

但是,在关系数据库中,尽管有其名称,但数据是以表格形式组织的。这意味着我们需要在数据库和业务逻辑之间有一个中间层。这种机制称为对象关系映射(ORM),它允许以与数据实体在数据源中存储方式无关的方式访问数据实体。在Java中,事实上选择的是Hibernate,而作为Spring开发人员,我们在关系数据库(例如MySQL,Postgre等)中经常使用它。

但是,当您使用非阻塞式Webflux API和R2DBC来处理关系数据库时,您将没有ORM,因为R2DBC不支持Hibernate。但是,也许。我认为这很好–作为Java开发人员,我们喜欢谈论软件设计,但是我们依赖工具,这些工具不是软件体系结构原理的最佳示例。这篇文章是关于在Spring R2DBC和PostgreSQL中没有Hibernate的实体中处理合成的。

解决方案 请考虑以下示例:我们构建了一个任务管理应用程序,并且拥有Task和Project实体。两者都存储在单独的表中,但是使用project_id键进行引用。当我们想要检索任务列表时,我们希望获得有关相应项目的信息,因此我们可以在客户端应用程序中显示它。但是,我们可以有两个单独的存储库,然后合并数据,这是非常低效的想法。更好的方法是编写一个TaskRepository返回组成对象的自定义。

步骤1.定义自定义存储库 默认情况下,它ReactiveCrudRepository是一个入口点,提供基本的CRUD功能,但仅限于此。为了提供自定义查询,我们需要使用自定义存储库对其进行扩展。为此,我们首先创建一个新接口,为自定义存储库定义合同,然后使用它扩展条目存储库:

public interface CustomTaskRepository {    
  Flux<Task> findAllTasks (UUID userId);    
  Flux<Task> findTasksForDay (UUID userId, LocalDate day); }

下一步骤是扩展核心TaskRepositoryCustomTaskRepository,如下图所示:

public interface TaskRepository extends ReactiveCrudRepository<Task, UUID>,    CustomTaskRepository {}

步骤2.履行合同 现在,我们可以实现上述接口。请注意,Impl由于Spring DI规则,实现应以结尾结尾。在该组件中,我们需要注入一个DatabaseClient非阻塞客户端来处理数据库操作。在Spring Boot中,事情是预先配置的,因此您只需要定义一个依赖项并使用基于构造函数的DI来让Spring注入它:

public class CustomTaskRepositoryImpl implements CustomTaskRepository {

    private DatabaseClient client;

    public CustomTaskRepositoryImpl(DatabaseClient client) {
        this.client = client;
    }

    public Flux<Task> findAllTasks (UUID userId) {
        return null;
    }

    public Flux<Task> findTasksForDay (UUID userId, LocalDate day) {
        return null;
    }
}

步骤3.准备SQL查询 提取组成的实体意味着我们需要使用JOIN操作。PostgreSQL有6种类型的联接操作,但这超出了本文的范围。也许将来,我会将PostgreSQL添加为我的博客的主题,但现在不是。在这里,我们使用的INNER JOIN操作将返回与两个表中的给定条件相匹配的行。

在我们的示例中,我们有Task和Project实体与相连project_id。看一下下面的代码片段:

public Flux<Task> findAllTasks (UUID userId) {
    String query = "SELECT task_id, task_content, is_completed, user_id, task_date, task_project, project_id, project_name "
                    + " FROM tasks INNER JOIN projects ON task_project = project_id WHERE user_id = :userId";
    return null;
}

步骤4.绑定参数并执行查询 同样,要在普通JDBC中执行此操作,我们首先准备一个查询,然后执行它。在R2DBC中,我们DatabaseClient.execute()为此使用方法。我们还可以绑定一些变量,例如userId。这是使用bind()方法完成的,该方法接受两个参数:key(查询中变量的名称)和一个值。

public Flux<Task> findAllTasks (UUID userId) {
    String query = "SELECT task_id, task_content, is_completed, user_id, task_date, task_project, project_id, project_name "
                    + " FROM tasks INNER JOIN projects ON task_project = project_id WHERE user_id = :userId";
    Flux<Task> result = client.execute(query)
                        .bind("userId", userId)
                        ///...
    return null;
}

第5步。使用Mapper处理结果 这很麻烦,因为这就是我们使用ORM框架的原因。我们需要将原始结果映射到Java对象。为此(以及促进可重用性),我们创建了一个映射器。Martin Fowler定义的这种设计模式用于在对象和数据库之间移动数据,同时使它们彼此之间以及映射器本身保持独立。这个想法显示在下面:

1591430113574.png 映射器实现

在R2DC中,该map()方法执行反应式客户端映射操作。它接受BiFunction将原始行结果映射到相应的Java模型的法线。我们可以使用如下功能接口来实现它:

public class TaskMapper implements BiFunction<Row, Object, Job> {


    @Override
    public Task apply(Row row, Object o) {
        UUID taskId = row.get("task_id", UUID.class);
       String content = row.get("task_content", String.class);
        Boolean completed = row.get("is_completed", Boolean.class);
        LocalDate createdAt = row.get("task_date", LocalDate.class);

        UUID projectId = row.get("project_id", UUID.class);
        String projectName = row.get("project_name", String.class);

        Project project = new Project(projectId, projectName);
        Task task = new Task(taskId, content, complted, createdAt, project);
        return task;
    }
}

接下来,我们可以将此组件添加到我们的自定义存储库中:

public Flux<Task> findAllTasks (UUID userId) {
    String query = "SELECT task_id, task_content, is_completed, user_id, task_date, task_project, project_id, project_name "
                    + " FROM tasks INNER JOIN projects ON task_project = project_id WHERE tasks.user_id = :userId";

    TaskMapper mapper = new TaskMapper();

    Flux<Task> result = client.execute(query)
                        .bind("userId", userId)
                        .map(mapper:apply)
                        //...
    return null;
}

步骤6.消费数据 最后一步是调用终端操作以消耗数据管道。DatabaseClient有三个可用于查询的操作:

all()=返回结果的所有行。 first() =返回整个结果的第一行。 one()=仅返回一个结果,如果结果包含更多行,则失败。 在我们的示例中,我们需要满足查询条件的所有实体,因此我们使用该all()方法,如下所示:

public Flux<Task> findAllTasks (UUID userId) {
    String query = "SELECT task_id, task_content, is_completed, user_id, task_date, task_project, project_id, project_name "
                    + " FROM tasks INNER JOIN projects ON task_project = project_id WHERE tasks.user_id = :userId";

    TaskMapper mapper = new TaskMapper();

    Flux<Task> result = client.execute(query)
                        .bind("userId", userId)
                        .map(mapper:apply)
                        .all();
    return result;
}

如您所见,将R2DBC与复杂的组合对象一起使用,而不需要像Hibernate这样的ORM框架,这并不是火箭科学。R2DBC提供了一个流畅的API,并且易于使用和抽象化数据库操作,因此您可以自己实现所需的持久层逻辑。

如果您对此帖子有疑问,请随时在下面发表评论或与我联系。祝你今天过得愉快!


原文链接:http://codingdict.com