本博客描述 Sequelize 中的各种关联类型。当调用诸如 User.hasOne(Project) 之类的方法时,我们说 User 模型(函数被调用的模型)是 sourceProject 模型(作为参数传递的模型)是 target

一、一对一关联

  一对一关联是通过单个外键连接的两个模型之间的关联。

1、BelongsTo:源model - 属于的意思

  属于的意思:源属于目标,在源上添加目标外键

  BelongsTo 关联是在 source model 上存在一对一关系的外键的关联。一个简单的例子是 Player 通过 player 的外键作为 Team 的一部分。

const Player = this.sequelize.define('player', {/* attributes */});
const Team  = this.sequelize.define('team', {/* attributes */});

Player.belongsTo(Team); // 将向 Player 添加一个 teamId 属性以保存 Team 的主键值

2、外键

  默认情况下,将从目标模型名称和目标主键名称生成 belongsTo 关系的外键。

  默认的样式是 camelCase,但是如果源模型配置为 underscored: true ,那么将使用字段 snake_case 创建 foreignKey。

const User = this.sequelize.define('user', {/* attributes */})
const Company  = this.sequelize.define('company', {/* attributes */});

User.belongsTo(Company); // 将 companyId 添加到 user

const User = this.sequelize.define('user', {/* attributes */}, {underscored: true})
const Company  = this.sequelize.define('company', {
  uuid: {
    type: Sequelize.UUID,
    primaryKey: true
  }
});

User.belongsTo(Company); // 将 company_uuid 添加到 user

  在已定义 as 的情况下,将使用它代替目标模型名称。

const User = this.sequelize.define('user', {/* attributes */})
const UserRole  = this.sequelize.define('userRole', {/* attributes */});

User.belongsTo(UserRole, {as: 'role'}); // 将 role 添加到 user 而不是 userRole

  在所有情况下,默认外键可以用 foreignKey 选项覆盖。当使用外键选项时,Sequelize 将按原样使用:

const User = this.sequelize.define('user', {/* attributes */})
const Company  = this.sequelize.define('company', {/* attributes */});

User.belongsTo(Company, {foreignKey: 'fk_company'}); // 将 fk_company 添加到 User

3、目标键

  目标键是源模型上的外键列指向的目标模型上的列。 默认情况下,belongsTo 关系的目标键将是目标模型的主键。 要定义自定义列,请使用 targetKey 选项。

const User = this.sequelize.define('user', {/* attributes */})
const Company  = this.sequelize.define('company', {/* attributes */});

User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // 添加 fk_companyname 到 User

4、HasOne:目标model

  有一个的意思,源有一个目标,在目标上添加源的外键

  HasOne 关联是在 target model 上存在一对一关系的外键的关联。

const User = sequelize.define('user', {/* ... */})
const Project = sequelize.define('project', {/* ... */})
 
// 单向关联
Project.hasOne(User)

/*
  在此示例中,hasOne 将向 User 模型添加一个 projectId 属性 ! 
  此外,Project.prototype 将根据传递给定义的第一个参数获取 getUser 和 setUser 的方法。 
  如果启用了 underscore 样式,则添加的属性将是 project_id 而不是 projectId。

  外键将放在 users 表上。

  你也可以定义外键,例如 如果您已经有一个现有的数据库并且想要处理它:
*/
 
Project.hasOne(User, { foreignKey: 'initiator_id' })
 
/*
  因为Sequelize将使用模型的名称(define的第一个参数)作为访问器方法,
  还可以将特殊选项传递给hasOne:
*/
 
Project.hasOne(User, { as: 'Initiator' })
// 现在你可以获得 Project.getInitiator 和 Project.setInitiator
 
// 或者让我们来定义一些自己的参考
const Person = sequelize.define('person', { /* ... */})
 
Person.hasOne(Person, {as: 'Father'})
// 这会将属性 FatherId 添加到 Person
 
// also possible:
Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'})
// 这将把属性 DadId 添加到 Person
 
// 在这两种情况下,你都可以:
Person.setFather
Person.getFather
 
// 如果你需要联结表两次,你可以联结同一张表
Team.hasOne(Game, {as: 'HomeTeam', foreignKey : 'homeTeamId'});
Team.hasOne(Game, {as: 'AwayTeam', foreignKey : 'awayTeamId'});

Game.belongsTo(Team);

  即使它被称为 HasOne 关联,对于大多数1:1关系,您通常需要BelongsTo关联,因为 BelongsTo 将会在 hasOne 将添加到目标的源上添加 foreignKey。

5、HasOne 和 BelongsTo 之间的区别

  在Sequelize 1:1关系中可以使用HasOne和BelongsTo进行设置。 它们适用于不同的场景。 让我们用一个例子来研究这个差异。假设我们有两个表可以链接 PlayerTeam 。 让我们定义他们的模型。

const Player = this.sequelize.define('player', {/* attributes */})
const Team  = this.sequelize.define('team', {/* attributes */});

  当我们连接 Sequelize 中的两个模型时,我们可以将它们称为一对 sourcetarget 模型。像这样

  将 Player 作为 sourceTeam 作为 target

Player.belongsTo(Team);
//
Player.hasOne(Team);

  将 Team 作为 sourcePlayer 作为 target

Team.belongsTo(Player);
//Or
Team.hasOne(Player);

  HasOne 和 BelongsTo 将关联键插入到不同的模型中。 HasOne 在 target 模型中插入关联键,而 BelongsTo 将关联键插入到 source 模型中。

  下是一个示例,说明了 BelongsTo 和 HasOne 的用法。

const Player = this.sequelize.define('player', {/* attributes */})
const Coach  = this.sequelize.define('coach', {/* attributes */})
const Team  = this.sequelize.define('team', {/* attributes */});

  假设我们的 Player 模型有关于其团队的信息为 teamId 列。 关于每个团队的 Coach 的信息作为 coachId 列存储在 Team 模型中。 这两种情况都需要不同种类的1:1关系,因为外键关系每次出现在不同的模型上。

  当关于关联的信息存在于 source 模型中时,我们可以使用 belongsTo。 在这种情况下,Player 适用于 belongsTo,因为它具有 teamId 列。

Player.belongsTo(Team)  // `teamId` 将被添加到 Player / Source 模型中
  当关于关联的信息存在于 target 模型中时,我们可以使用 hasOne。 在这种情况下, Coach 适用于 hasOne ,因为 Team 模型将其 Coach 的信息存储为 coachId 字段。
Coach.hasOne(Team)  // `coachId` 将被添加到 Team / Target 模型中

二、一对多关联 (hasMany)

  一对多关联将一个来源与多个目标连接起来。 而多个目标接到同一个特定的源。

const User = sequelize.define('user', {/* ... */})
const Project = sequelize.define('project', {/* ... */})
 
// 好。 现在,事情变得更加复杂(对用户来说并不真实可见)。
// 首先我们来定义一个 hasMany 关联
Project.hasMany(User, {as: 'Workers'})

  这会将 projectIdproject_id 属性添加到 User。Project 的实例将获得访问器 getWorkerssetWorkers

  有时您可能需要在不同的列上关联记录,您可以使用 sourceKey 选项:

const City = sequelize.define('city', { countryCode: Sequelize.STRING });
const Country = sequelize.define('country', { isoCode: Sequelize.STRING });

// 在这里,我们可以根据国家代码连接国家和城市
Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'});
City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'});

  到目前为止,我们解决了单向关联。 但我们想要更多! 让我们通过在下一节中创建一个多对多的关联来定义它。

三、多对多关联

  多对多关联用于将源与多个目标相连接。 此外,目标也可以连接到多个源。

Project.belongsToMany(User, {through: 'UserProject'});
User.belongsToMany(Project, {through: 'UserProject'});

  这将创建一个名为 UserProject 的新模型,具有等效的外键projectIduserId。 属性是否为camelcase取决于由表(在这种情况下为UserProject)连接的两个模型。

  定义 throughrequired。 Sequelize 以前会尝试自动生成名称,但并不总是导致最合乎逻辑的设置。

  这将添加方法 getUsers, setUsers, addUser,addUsersProject, 还有 getProjects, setProjects, addProject, 和 addProjectsUser

  有时,您可能需要在关联中使用它们时重命名模型。 让我们通过使用别名(as)选项将 users 定义为 workers 而 projects 定义为t asks。 我们还将手动定义要使用的外键:

User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' })
Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' })

  foreignKey 将允许你在 through 关系中设置 source model 键。

  otherKey 将允许你在 through 关系中设置 target model 键。

User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId', otherKey: 'projectId'})

  当然你也可以使用 belongsToMany 定义自我引用:

Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' })
// 这将创建存储对象的 ID 的表 PersonChildren。

  如果您想要连接表中的其他属性,则可以在定义关联之前为连接表定义一个模型,然后再说明它应该使用该模型进行连接,而不是创建一个新的关联:

const User = sequelize.define('user', {})
const Project = sequelize.define('project', {})
const UserProjects = sequelize.define('userProjects', {
    status: DataTypes.STRING
})
 
User.belongsToMany(Project, { through: UserProjects })
Project.belongsToMany(User, { through: UserProjects })

  要向 user 添加一个新 project 并设置其状态,您可以将额外的 options.through 传递给 setter,其中包含连接表的属性

user.addProject(project, { through: { status: 'started' }})
  默认情况下,上面的代码会将 projectId 和 userId 添加到 UserProjects 表中, 删除任何先前定义的主键属性 - 表将由两个表的键的组合唯一标识,并且没有其他主键列。 要在 UserProjects 模型上强添加一个主键,您可以手动添加它。
const UserProjects = sequelize.define('userProjects', {
  id: {
    type: Sequelize.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  status: DataTypes.STRING
})

  使用多对多你可以基于 through 关系查询并选择特定属性。 例如通过 through 使用findAll

User.findAll({
  include: [{
    model: Project,
    through: {
      attributes: ['createdAt', 'startedAt', 'finishedAt'],
      where: {completed: true}
    }
  }]
});

  当通过模型不存在主键时,Belongs-To-Many会创建唯一键。 可以使用 uniqueKey 选项覆盖此唯一键名。

Project.belongsToMany(User, { through: UserProjects, uniqueKey: 'my_custom_unique' })