为啥在 Spring Data JDBC 中将实体-值关系实现为反向引用
Posted
技术标签:
【中文标题】为啥在 Spring Data JDBC 中将实体-值关系实现为反向引用【英文标题】:Why is a entity - value relationship implemented as a back reference in Spring Data JDBC为什么在 Spring Data JDBC 中将实体-值关系实现为反向引用 【发布时间】:2021-09-05 10:40:01 【问题描述】:在 Spring Data JDBC 中,如果一个实体(例如 Customer)有一个值(例如 Address),如示例 here 中的值有一个反向引用列(表 address 中的列 customer)到 db 架构中的实体:
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
我了解它的工作原理,但我不了解这个反向参考实现背后的想法。那么有人可以解释一下这个问题吗?谢谢!
【问题讨论】:
【参考方案1】:对于大多数好的问题,答案有很多方面。
历史/对称答案
当涉及到实体之间的引用时,Spring Data JDBC 支持 1:1(您询问的那个)和 1:N(列表、集合和映射)。 对于后者,除了反向引用之外的任何东西都是奇怪/错误的。 并且使用 1:1 的反向引用变得基本相同,简化了代码,这是一件好事。
DML 流程答案
使用反向引用,插入和删除的过程变得更加容易:首先插入聚合根(在您的示例中为customer
),然后是所有引用的实体。如果这些实体有更多实体,它会继续工作。删除的工作方式相反,但同样直接。
依赖答案
聚合中的引用实体只能作为该聚合的一部分存在。从这个意义上说,它们依赖于聚合根。没有那个聚合根就没有内部实体,而聚合根通常也可以在没有内部实体的情况下存在。因此,内部实体带有引用是有道理的。
身份证答案
通过这种设计,内部实体甚至不需要 id。它的身份完全由聚合根的身份给出,如果与同一实体类存在多个一对一关系,则使用反向引用列。
替代方案
所有原因或多或少都基于一个一对一的关系。我当然同意,对于同一个班级的两个这样的关系看起来有点奇怪,而在你的例子中是 5,这变得很荒谬。在这种情况下,您可能需要寻找替代方案:
使用地图
不要像这样对Customer
类建模:
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
表
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
表变得多余,因为数据将被折叠到customer
表中:
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)
);
还是聚合?
但也许您需要自己的地址,而不是作为客户的一部分。
如果是这种情况,地址就是它自己的聚合。
聚合之间的引用应该建模为 id 或 AggregateReference
。这在Spring Data JDBC, References, and Aggregates中有更详细的描述
【讨论】:
感谢您的详细说明 - 有一些背景信息总是很高兴!实际上,我首先使用的是 Mapcustomer
表中的城市过多。
关于实体与价值。这有点取决于你在哪里看。在数据库中,它确实有一个 id。以上是关于为啥在 Spring Data JDBC 中将实体-值关系实现为反向引用的主要内容,如果未能解决你的问题,请参考以下文章
为啥使用 Spring Data JPA 更新实体时@Transactional 隔离级别不起作用?