使用 ORM 时如何确保域完整性
Posted
技术标签:
【中文标题】使用 ORM 时如何确保域完整性【英文标题】:How to ensure domain integrity when using an ORM 【发布时间】:2019-03-26 13:31:25 【问题描述】:除了声明列类型之外,应该如何强制域完整性?
ENUM()
是一个选项,但是从 SQL 角度和 Doctrine 角度来看,它都有缺点。
作为替代方案,在使用 ORM 之前设计数据库时,我会改为使用自然键和外部约束。虽然我的time_unit
表有一个名称和秒列,但它唯一真正的意义是将其他表中的值限制为time_unit.unit
的值。
MariaDB [tracker]> select * from time_unit;
+------+-----------+----------+
| unit | name | seconds |
+------+-----------+----------+
| d | Days | 86400 |
| h | Hours | 3600 |
| i | Minutes | 60 |
| m | Month | 2592000 |
| q | Quarter | 10368000 |
| s | Seconds | 1 |
| w | Week | 604800 |
| y | Year | 31536000 |
+------+-----------+----------+
8 rows in set (0.01 sec)
MariaDB [tracker]> select * from sign;
+------+
| sign |
+------+
| -1 |
| 1 |
+------+
2 rows in set (0.00 sec)
CREATE TABLE `agenda` (
`id` int(11) NOT NULL,
`time_value` smallint(6) DEFAULT NULL,
`time_unit` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`sign` smallint(6) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_agenda_time_unit1_idx` (`time_unit`),
KEY `fk_agenda_sign1_idx` (`sign`),
CONSTRAINT `FK_5A9C89CF7106057E` FOREIGN KEY (`time_unit`) REFERENCES `time_unit` (`unit`),
CONSTRAINT `FK_22ACC67D9F7E91FE` FOREIGN KEY (`sign`) REFERENCES `sign` (`sign`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
但这在使用 ORM 时会出现问题。首先,为每个对象创建一个对象,这似乎过多并使序列化变得痛苦。其次,我read 表示,在使用 ORM 时,不应该使用外键,而是使用关联的对象。
在数据库级别,表示实体之间的关系 通过外键。但是有了 Doctrine,你将永远不必(也永远不会 应该)直接使用外键。你应该只与 通过自己的身份表示外键的对象。
为了强制域完整性,是否应该使用 ENUM()、自然键的外部约束或其他方法?
【问题讨论】:
“域完整性”是什么意思?可能这些规则过于复杂,无法在 SQL 的微薄功能中实现,即使使用触发器、FK、数据类型、约束等。在您的客户端代码中执行。 @RickJames 我指的是确保给定的列值必须在一组指定的可能值中。 FK 通常工作得很好,但是 Doctrine 会尝试为其创建一个不需要的对象。<snide-remark>
有时,第 3 方软件的障碍多于帮助。 </snide-remark>
【参考方案1】:
这主要是风格问题。在构建模式时,参照完整性是保证质量的一种方式。还有其他方法可以做到这一点 - 例如,单元测试保证系统不会接受除 1 和 -1 之外的“符号”值。
因此,如果您有一个仅用于限制有效条目的表,如果没有其他属性,我建议您做最简单的事情并将该逻辑放在您的应用程序层中,并通过单元测试来验证你想要的行为。只需将数据作为没有外键的整数存储在数据库中。
如果你真的想让数据库验证条目,你可以使用枚举,或者告诉 ORM 该列只是一个整数,但仍然创建外键。这是否是一个好主意又取决于风格。我非常喜欢“DRY”——不要重复自己。如果您有逻辑来限制应用程序层中属性的有效选项,请专注于使其更好,不要在数据库模式中重复该逻辑。不过,这并不是那么简单——如果您认为人们会在您的应用程序之外访问数据库,那么拥有外键或枚举是合理的。
我认为您的time_unit
不仅仅是一组查找值 - 我猜“秒”列用于在单位之间进行转换。这里有几个选项,但我还是想依靠单元测试来验证我的转换逻辑,如果发生转换逻辑,您可能会考虑将这些作为常量存储在应用程序代码中。然后,您可以将该单元作为 char 列存储在议程表中。
这使您的持久性逻辑更容易和更快,但将验证行为的责任置于您的单元测试中,而不是您的模式中。
我认为关于从不直接使用外键的引用旨在表明使用您的 ORM 的“正常”行为是通过请求议程对象 (print agendaItem.time_unit.name
) 来访问 time_unit,而不是明确地请求外键键并检索相关对象 (timeUnitID = agendaItem.timeUnitID; print time_unit.findByID(timeUnitID
)。我不认为这是针对外键的全面建议。
【讨论】:
感谢 Neville,对于“符号”表,为什么不包含外键约束,以便 DB 只强制执行允许的值,但让 ORM 忽略符号表,所以它不会创建一个Sign
对象?
我已经更新了答案以反映这一点 - 我更喜欢“不要重复自己”,但这是一个风格问题......【参考方案2】:
ENUM() 是一个选项,但是从 SQL 的角度来看它有缺点 以及教义的观点。
为了强制域完整性,应该 ENUM(),对一个域的外部约束 自然键,还是使用其他方法?
您可以替换 mysql 的 / MariaDB 的数据类型
ENUM('1', '2', '3');
采用更理性的方法。
CREATE TABLE enum_data (
position TINYINT
, value CHAR(1)
);
INSERT INTO enum_data (position, value) VALUES(1, '1');
INSERT INTO enum_data (position, value) VALUES(2, '2');
INSERT INTO enum_data (position, value) VALUES(3, '3');
并在你的表中使用它
CREATE TABLE your_table (
id INT ....
, enum_data_id TINYINT
...
...
)
在场外,您也可以/应该也使用FOREIGN KEY
以合理的方法确保数据完整性。
与ENUM
相比,使用额外的表也具有优势。
当您想从ENUM
数据类型中添加、编辑、删除时,您将需要使用ALTER TABLE
,这是在使用其他表时不需要的。
【讨论】:
谢谢雷蒙德。这不会导致创建EnumData
对象吗?还有一点题外话,但你会建议在使用 ORM 时只使用代理键吗?
@user1032531 我已经很长时间没有使用学说了。我也不能提出建议,如果你应该或不应该使用有很多理论的代理键.. 也我不知道你的数据模型,如果你有不改变使用的自然键
再次感谢。明白了。只是好奇,但是您使用的是另一个 ORM 吗?我一直只是简单地编写我的 SQL 并使用 php 执行,而整个 Doctrine 事情花费的时间比我预期的要多。
" 只是好奇,但你在使用另一个 ORM 吗?" 我已经构建并使用了我自己的抽象数据库层 @user1032531,它支持对象关系映射、Active Record、使用跨数据库供应商的方法链接进行直接查询和查询构建。它还支持优化事务中的查询,例如多个更新查询将被重写为一个更新查询,其中包含一个 case 语句。以上是关于使用 ORM 时如何确保域完整性的主要内容,如果未能解决你的问题,请参考以下文章