为什么在 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
在 Spring 数据中 JDBC 如果一个实体(例如 Customer)有一个值(例如 Address ) 就像示例中的
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