为什么在 Spring 数据 JDBC 中将实体 - 值关系实现为反向引用

Why is a entity - value relationship implemented as a back reference in Spring Data JDBC

在 Spring 数据中 JDBC 如果一个实体(例如 Customer)有一个值(例如 Address ) 就像示例中的 一样,该值有一个向后引用列(table address 中的 customer 列)数据库模式中的实体:

CREATE TABLE "customer" (
  "id"                  BIGSERIAL       NOT NULL,
  "name"                VARCHAR(255)    NOT NULL,
  PRIMARY KEY (id)
);

CREATE TABLE "address" (
  "customer"            BIGINT,
  "city"                VARCHAR(255)    NOT NULL
);

问题在于,如果您在一个实体甚至不同实体中多次使用该 Address 值,您必须为每次使用定义一个额外的列。只有实体的主要 ID 存储在这些列中,否则无法区分它是哪个实体。在我的实际实现中,Address 值有五个这样的列:

  "order_address"            BIGINT,    -- backreference for orderAddress to customer id
  "service_address"          BIGINT,    -- backreference for serviceAddress to customer id
  "delivery_address"         BIGINT,    -- backreference for deliveryAddress to customer id
  "installation_address"     BIGINT,    -- backreference for installationAddress to provider_change id
  "account_address"          BIGINT,    -- backreference for accountAddress to payment id

我明白它是如何工作的,但我不明白这个反向引用实现背后的想法。那么有人可以阐明这个问题吗?谢谢!

对于大多数好的问题,答案有很多方面。

historical/symmetry答案

当谈到实体之间的引用时 Spring 数据 JDBC 支持 1:1(您询问的)和 1:N(列表、集合和映射)。 对于后者,除了反向引用之外的任何东西都只是 weird/wrong。 并且为 1:1 使用反向引用变得基本相同,简化了代码,这是一件好事。

DML过程答案

使用反向引用,插入和删除的过程变得更加容易:首先插入聚合根(在您的示例中为 customer),然后是所有引用的实体。如果这些实体有更多实体,它会继续工作。删除以相反的方式工作,但同样直接。

依赖答案

聚合中引用的实体只能作为该聚合的一部分存在。从这个意义上说,它们依赖于聚合根。没有那个聚合根就没有内部实体,而聚合根通常也可能在没有内部实体的情况下存在。因此,内部实体带有引用是有道理的。

ID答案

采用这种设计,内部实体甚至不需要 id。它的身份完全由聚合根的身份给出,并且在与同一实体 class 的多个一对一关系的情况下,使用反向引用列。

备选方案

所有的原因都或多或少基于一个单一的一对一关系。我当然同意,对于相同的 class 和 5 的两个这样的关系,它看起来有点奇怪,因为在你的例子中它变得荒谬。在这种情况下,您可能需要寻找替代方案:

使用地图

而不是像这样建模你的 Customer class:

class Customer {
  @Id
  Long id;
  String name;
  Address orderAddress
  Address serviceAddress
  Address deliveryAddress
  Address installationAddress
  Address accountAddress
}

使用这样的地图

class Customer {
  @Id
  Long id;
  String name;
  Map<String,Address> addresses
}

这会导致像这样的 address table

CREATE TABLE "address" (
  "customer"            BIGINT,
  "customer_key"        VARCHAR(20).    NOT NULL,
  "city"                VARCHAR(255)    NOT NULL
);

您可以使用 @MappedCollection 注释控制列名称,如果需要,您可以为单个地址添加瞬态 getter 和 setter。

使其成为真实值

您将 Address 称为 ,而我将其称为 实体 。如果它应该被视为一个值,我认为你应该将它映射为 embedded 像这样

class Customer {
  @Id
  Long id;
  String name;
  @Embedded(onEmpty = USE_NULL, prefix="order_")
  Address orderAddress
  @Embedded(onEmpty = USE_NULL, prefix="service_")
  Address serviceAddress
  @Embedded(onEmpty = USE_NULL, prefix="delivery_")
  Address deliveryAddress
  @Embedded(onEmpty = USE_NULL, prefix="installation_")
  Address installationAddress
  @Embedded(onEmpty = USE_NULL, prefix="account_")
  Address accountAddress
}

这将使 address table 变得多余,因为数据将被折叠到 customer table:

CREATE TABLE "customer" (
  "id"                  BIGSERIAL       NOT NULL,
  "name"                VARCHAR(255)    NOT NULL,
  "order_city"          VARCHAR(255)    NOT NULL,
  "service_city"        VARCHAR(255)    NOT NULL,
  "deliver_city"        VARCHAR(255)    NOT NULL,
  "installation_city"   VARCHAR(255)    NOT NULL,
  "account_city"        VARCHAR(255)    NOT NULL,
  PRIMARY KEY (id)
);

还是聚合?

但也许您需要自己的地址,而不是作为客户的一部分。 如果是这种情况,地址就是它自己的集合。 聚合之间的引用应建模为 ids 或 AggregateReference. This is described in more detail in Spring Data JDBC, References, and Aggregates