Sequelize ORM中HasOne和BelongsTo的区别
Posted
技术标签:
【中文标题】Sequelize ORM中HasOne和BelongsTo的区别【英文标题】:Difference between HasOne and BelongsTo in Sequelize ORM 【发布时间】:2016-04-06 13:02:24 【问题描述】:我正在使用sequelize ORM 开发一个sails.js 应用程序。我对何时需要使用 BelongsTo 和 HasOne 感到有些困惑。
文档指出:
BelongsTo 关联是关联的外键 源模型上存在一对一关系。
HasOne 关联是其中的外键的关联 目标模型上存在一对一关系。
除了指定这些的地方还有其他区别吗?在这两种情况下,行为是否仍然保持不变?
【问题讨论】:
【参考方案1】:我知道这是一个迟了 4 年的答案,但我从昨天开始就一直在思考、搜索文档和谷歌搜索。并且找不到让我相信正在发生的事情的答案。今天我得出一个结论:区别不绝对只是语义问题!
假设您有以下语句 (from the docs):
Project.hasMany(Task);
它在Project
模型中为Project
的实例创建了一些实用方法,例如:addTask
、setTask
等。因此您可以执行以下操作:
const project = await Project.create(...);
// Here, addTask exists in project instance as a
// consequence of Project.hasMany(Task); statement
project.addTasks([task1, task2]);
另外,在数据库中,tasks
关系中的外键将被创建,指向projects
关系。
现在,如果不是Project.hasMany(Task);
,我只说:
Task.belongsTo(Project);
然后,类似地,在数据库中,tasks
关系中的外键将被创建,指向projects
关系。但是project
实例上不会有任何addTasks
方法。但是,通过执行Task.belongsTo(Project);
,Sequelize 将创建一组不同的方法,但这次仅在 task
实例上。之后,您可以使用以下方法将任务与项目相关联:
const proj = await Project.findByPk(...);
const task1 = await Task.create(...);
...
// Here, setProject exists in task instance as a
// consequence of Task.belongsTo(Project); statement
task1.setProject(proj);
文档定义为 source,即拥有用于创建关联的方法的模型。所以,在:
Project.hasMany(Task);
:在此声明中,Project
是 来源 模型。 Task
又是 target 模型。
Task.belongsTo(Project);
:在此声明中,Task
是 来源 模型。 Project
又是 target 模型。
问题是,当使用hasOne
、hasMany
、belongsTo
和belongsToMany
创建关联时,实例实用程序方法仅在源 模型。总之:如果您想在Project
和 Task
实例中创建实用程序方法,则必须使用这两个语句来描述相同的关联。在数据库本身中,两者都会产生相同的冗余效果(在tasks
关系上创建一个外键,指向projects
关系的主键):
// All the instances of Project model will have utility methods
Project.hasMany(Task);
// All the instances of Task model will have utility methods
Task.belongsTo(Project);
const project = await Project.create(...);
const task1 = await Task.create(...);
const task2 = await Task.create(...);
...
// as a consequence of Project.hasMany(Task), this can be done:
project.addTask(task1);
...
// as a consequence of Task.belongsTo(Project), this can be done:
task2.setProject(project);
顺便说一句,写完这个答案后,我意识到这与Vladsyslav Turak 在他的答案中解释的内容相同,但我决定将我的答案保留在这里,因为它添加了一些涉及实用方法的重要实用信息。
【讨论】:
【参考方案2】:这是一个更普遍的问题。
主要区别在于语义。你必须决定什么是关系(一些愚蠢的例子):
人只有一只右臂。右臂属于一个人。
反过来说有点奇怪:
右臂有个男人。男人属于右臂。
你可以拥有没有右臂的人。但是单靠右臂是没用的。
如果 RightArm 和 Man 是模型,在 sequelize 中,它可能看起来像:
Man.hasOne(RightArm); // ManId in RigthArm
RightArm.belongsTo(Man); // ManId in RigthArm
正如您所注意到的,db 表结构也有所不同:
BelongsTo 将在源上添加 foreignKey,而 hasOne 将在目标上添加(Sequelize 在表 'RightArm' 中创建新列 'ManId' ,但不会在 ' 中创建 'RightArmId' 列人的桌子)。
我没有看到更多的差异。
【讨论】:
那么在这种情况下,我应该使用Man.hasOne(RightArm);
还是RightArm.belongsTo(Man);
?还是两者都用?
在大多数情况下我会同时使用它们
我认为@KrzysztofSztompka 想说的是:根据每种情况,他可以使用或者 hasOne 或 belongsTo 考虑语义。但是没有必要设置例如:Man.hasOne(RightArm); RightArm.belongsTo(Man);
因为他们做同样的事情就是为 RighArm 设置外键。
@YangjunWang,请看下面我的回答。【参考方案3】:
我同意Krzysztof Sztompka关于两者之间的区别:
Man.hasOne(RightArm);
RightArm.belongsTo(Man);
我想回答Yangjun Wang的问题:
所以在这种情况下,我应该使用
Man.hasOne(RightArm);
还是RightArm.belongsTo(Man);
?还是两者都用?
确实,Man.hasOne(RightArm);
关系和 RightArm.belongsTo(Man);
做同样的事情 - 这些关系中的每一个都会将外键 manId
添加到 RightArm
表中。
从物理数据库层的角度来看,这些方法做的事情是一样的,对于我们的数据库我们将使用哪种方法没有区别。
那么,有什么区别呢?主要区别在于 ORM 的层(在我们的例子中是 Sequalize ORM,但下面的逻辑适用于 Laravel 的 Eloquent ORM 甚至 Ruby 的 Active Record ORM)。
使用Man.hasOne(RightArm);
关系,我们将能够使用Man
模型填充该人的RightArm
。如果这对我们的应用程序来说已经足够了,我们可以停下来,不要将RightArm.belongsTo(Man);
关系添加到RightArm
模型中。
但是如果我们需要获取RightArm
的所有者怎么办?如果不在 RightArm
模型上定义 RightArm.belongsTo(Man);
关系,我们将无法使用 RightArm
模型执行此操作。
另一个例子是User
和Phone
模型。定义User.hasOne(Phone)
关系,我们将能够填充我们的User
的Phone
。如果不定义Phone.belongsTo(User)
关系,我们将无法填充Phone
的所有者(例如我们的User
)。如果我们定义Phone.belongsTo(User)
关系,我们将能够获得Phone
的所有者。
因此,这里有主要区别:如果我们希望能够从两个模型中填充数据,我们需要在它们上定义关系(hasOne
和 belongsTo
)。如果我们只得到User
的Phone
就足够了,而不是Phone
的User
,我们可以在User
模型上只定义User.hasOne(Phone)
关系。
上述逻辑适用于所有具有hasOne
和belongsTo
关系的ORM。
我希望这能澄清你的理解。
【讨论】:
我们可以在两个型号上都使用belongsTo
,否则它将不起作用?另外——如何正确定义迁移?我们是否应该在手机模型上添加列(例如user_id
)?以上是关于Sequelize ORM中HasOne和BelongsTo的区别的主要内容,如果未能解决你的问题,请参考以下文章
laravel 中的 Eloquent ORM 里,hasOne 和 belongsTo 有啥区别
在使用 Sequelize 作为 ORM 的 Sails 应用程序中使用原始查询