Sequelize ORM中HasOne和BelongsTo的区别

Difference between HasOne and BelongsTo in Sequelize ORM

我正在开发 sails.js app with sequelize ORM。我有点困惑什么时候需要使用 BelongsTo 和 HasOne。

文档指出:

BelongsTo associations are associations where the foreign key for the one-to-one relation exists on the source model.

HasOne associations are associations where the foreign key for the one-to-one relation exists on the target model.

除了指定的地方,还有什么不同吗?在这两种情况下,行为是否仍然相同?

这是一个比较普遍的问题。

主要区别在于语义。你必须决定关系是什么(一些愚蠢的例子):

人只有一只右臂。右臂属于一个人。

反过来说有点奇怪:

右臂有个男人。男人属于右臂。

你可以拥有没有右臂的男人。但是单靠右臂是没用的

如果 RightArm 和 Man 是模型,在 sequelize 中,它可能看起来像:

Man.hasOne(RightArm);      // ManId in RigthArm
RightArm.belongsTo(Man);   // ManId in RigthArm

正如您所注意到的,db table 结构也存在差异:

BelongsTo 将在源上添加外键,而 hasOne 将在目标上添加外键(Sequelize 在 table [=38= 中创建新列 'ManId' ],但不会在 'Man' table).

中创建 'RightArmId' 列

我没有看到更多差异。

我同意 Krzysztof Sztompka 关于两者之间的区别:

Man.hasOne(RightArm);
RightArm.belongsTo(Man);

我想回答Yangjun Wang的问题:

So in this case, should I use either Man.hasOne(RightArm); or RightArm.belongsTo(Man);? Or use them both?

确实 Man.hasOne(RightArm); 关系和 RightArm.belongsTo(Man); 做同样的事情 - 这些关系中的每一个都会将外键 manId 添加到 RightArm table.

从物理数据库层的角度来看,这些方法做同样的事情,对于我们的数据库,我们将使用哪种确切方法没有区别。

那么,有什么区别呢?主要区别在于 ORM 层(在我们的例子中是 Sequalize ORM, but the logic below applies to Laravel's Eloquent ORM or even to Ruby's Active Record ORM)。

使用 Man.hasOne(RightArm); 关系,我们将能够使用 Man 模型填充男人的 RightArm。如果这对我们的应用程序来说足够了,我们可以停止它并且不将 RightArm.belongsTo(Man); 关系添加到 RightArm 模型。

但是如果我们需要得到 RightArm 的所有者怎么办?如果不在 RightArm 模型上定义 RightArm.belongsTo(Man); 关系,我们将无法使用 RightArm 模型执行此操作。

另一个例子是 UserPhone 模型。定义 User.hasOne(Phone) 关系,我们将能够填充 UserPhone。如果不定义 Phone.belongsTo(User) 关系,我们将无法填充 Phone 的所有者(例如我们的 User)。如果我们定义 Phone.belongsTo(User) 关系,我们将能够获得 Phone 的所有者。

所以,这里我们有主要区别:如果我们希望能够从两个模型中填充数据,我们需要在两个模型上定义关系(hasOnebelongsTo) .如果我们只得到就够了,比如UserPhone,但是PhoneUser不行,我们可以只定义User.hasOne(Phone) User 模型上的关系。

上述逻辑适用于所有具有 hasOnebelongsTo 关系的 ORM。

我希望这能澄清你的理解。

我知道这是一个迟了 4 年的答案,但从昨天开始我一直在思考它、搜索文档和谷歌搜索。并且无法找到让我相信正在发生的事情的答案。今天我得出一个结论:区别是不是只是语义问题,绝对!

假设您有以下语句 (from the docs):

Project.hasMany(Task);

它在 Project 模型中,在 Project 的实例上创建了一些实用方法,例如:addTasksetTask 等。所以你可以做类似的事情:

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);:在这个语句中,Projectsource 模型。 Task 又是 target 模型。
  • Task.belongsTo(Project);:在这个语句中,Tasksource 模型。 Project 又是 target 模型。

事实是,当使用 hasOnehasManybelongsTobelongsToMany 创建关联时,实例实用程序方法仅在 来源模型。总结:如果你想在 Project Task 实例中创建实用方法,你必须使用这两个语句来描述相同的关联。在数据库本身中,两者将具有相同的冗余效果(在指向 projects 关系的主键的 tasks 关系上创建外键):

// 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 在他的答案中解释的是同一件事,但我决定将我的答案保留在这里,因为它添加了一些涉及实用程序的重要实用信息方法的东西。

One-to-One belongTo 或 hasOne

使用右臂示例,以及Sequelize自己的文档。我们必须要问的问题是,人没有右臂还能活吗?或者没有男人右臂可以生存吗?确定我们希望外键存在的位置就是回答这个问题。举个更实际的例子吧

假设您有一个社区网站。您的用户全部由单个配置文件 model(或用户 model)表示。但在社区中,您还会有管理员和 mod 操作员,他们都有自己的权限集,甚至可能是不同类型的配置文件。不要向用户 model 添加 admin/mod 特定字段,最好创建一个单独的 model 来表示 admin/mod.

这是基本用户 model 的样子(忽略约束和验证):

class User extends Model {
  static associate(models) {}
}

User.init(
  {
    username: DataTypes.STRING(25),
    password: DataTypes.STRING(50)
  }
)

现在这里有一个 model 表示管理员或 mod,它旨在扩展用户 model:

class Staff extends Model {
   static associate(models) {}
{
    
   Staff.init(
     {
       permissions: DataTypes.ARRAY(DataTypes.STRING),
       roleType: DataTypes.STRING(20),
     }
   )

所以我们问自己,没有 admin/mod 用户还能存在吗? admin/mod 可以没有用户存在吗?用户不必是员工才能使用您的服务,但 admin/mod 仍然需要用户名和密码才能登录。您可以将这些字段添加到 Staff model,但事实是,它会重复信息并使事情更难跟踪。

本质上,admin/mod 将具有与普通用户相同的属性,只是具有特殊能力。如果您另有打算,我仍然会维护一个 BaseUser model 来组织和保持每个 model 的共同点。 admin/mod 帐户仍然有用户名和密码,可能还有电子邮件。否则,您最终会拥有两个拥有相同信息的用户,并且在一个可能令人困惑且难以管理的社区中。

确定用户不需要与其关联的Staff对象存在,因此我们不应该将外键放在用户配置文件中。不过,这仍然不能完全回答我们的问题。请记住,hasOne() 将 FK 放在目标 model 上,而 belongsTo() 将 FK 放在源上。所以我们可以说满足 FK 要求的 Staff.belongsTo(User)User.hasOne(Staff) 必须存在于工作人员 model.

无论是在 Staff model 上放置 belongsTo() 还是在 User model 上放置 hasOne() 都是语义问题,并不重要。两者都会将员工 model 与用户 model 相关联,从而允许您执行 User.getStaff() 方法。如果您希望能够从 Staff 实例获取用户帐户,您可以添加一个引用列,而无需在我们的 Staff model 上创建实际关联(这不会添加约束或关联,只是因为它暗示,参考):

user: {
  type: DataTypes.INTEGER,
  references: {
    model: User,
    key: 'userId'
  }
}

希望对您有所帮助。