本部分描述了 Sequelize 中的各种关联类型。 Sequelize 中有四种类型的关联

  • BelongsTo
  • HasOne
  • HasMany
  • BelongsToMany

基本概念

Source & Target

我们首先从一个基本概念开始,你将会在大多数关联中使用 sourcetarget 模型。 假设您正试图在两个模型之间添加关联。 这里我们在 usersarticles 之间添加一个 hasOne 关联。

const UserModel = sequelize.define('user',{name: Sequelize.STRING,age: Sequelize.INTEGER},{ timestamps: false }
)const ArticleModel = sequelize.define('article', {title: Sequelize.STRING,content: Sequelize.STRING
})UserModel.hasOne(ArticleModel)

相当于:

CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER NOT NULL auto_increment ,`name` VARCHAR(255), `age` INTEGER,PRIMARY KEY (`id`)
) ENGINE=InnoDB;CREATE TABLE IF NOT EXISTS `articles` (`id` INTEGER NOT NULL auto_increment ,`title` VARCHAR(255),`content` VARCHAR(255),`createdAt` DATETIME NOT NULL,`updatedAt` DATETIME NOT NULL,`userId` INTEGER,PRIMARY KEY (`id`),FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETESET NULL ON UPDATE CASCADE
) ENGINE=InnoDB;

UserModel(函数被调用的模型)是 sourceArticleModel 模型(作为参数传递的模型)是 target

articles 表的 userId 依赖于 users 表的 id

此时删除 users 表(source), 就会报错了 Cannot drop table ‘users’ referenced by a foreign key constraint ‘articles_ibfk_1’ on table ‘articles’.

外键

当您在模型中创建关联时,会自动创建带约束的外键引用。 下面是设置:

const TaskModel = sequelize.define('task', { title: Sequelize.STRING })
const UserModel = sequelize.define('user', { name: Sequelize.STRING }, { timestamps: false })UserModel.hasMany(TaskModel) // 将会添加 userId 到 TaskModel
TaskModel.belongsTo(UserModel) // 也将会添加 userId 到 TaskModel

将生成以下 SQL:

CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER NOT NULL auto_increment,`name` VARCHAR(255),PRIMARY KEY (`id`)
) ENGINE = InnoDB;CREATE TABLE IF NOT EXISTS `tasks` (`id` INTEGER NOT NULL auto_increment,`title` VARCHAR(255),`createdAt` DATETIME NOT NULL,`updatedAt` DATETIME NOT NULL,`userId` INTEGER,PRIMARY KEY (`id`),FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETESET NULL ON UPDATE CASCADE
) ENGINE = InnoDB;

tasksusers 模型之间的关系通过在 tasks 表上注入 userId 外键,并将其标记为对 users 表的引用。
默认情况下,如果引用的用户被删除,userId 将被设置为 NULL,如果更新了 userId,则更新 userId。 这些选项可以通过将 onUpdateonDelete 选项传递给关联调用来覆盖。
验证选项是RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL

对于 1:11:m 关联,默认选项是 SET NULL 用于删除,CASCADE 用于更新。
对于 n:m,两者的默认值是 CASCADE。 这意味着,如果您从 n:m 关联的一侧删除或更新一行,则引用该行的连接表中的所有行也将被删除或更新。

循环依赖 & 禁用约束

在表之间添加约束意味着当使用 sequelize.sync 时,表必须以特定顺序在数据库中创建表。
如果 Task 具有对 User 的引用,users 表必须在创建 tasks 表之前创建。
这有时会导致循环引用,那么 sequelize 将无法找到要同步的顺序。
想象一下文档和版本的场景。 一个文档可以有多个版本,并且为了方便起见,文档引用了它的当前版本。

const Document = sequelize.define('document', { author: Sequelize.STRING }, { timestamps: false })
const Version = sequelize.define('version', { timestamp: Sequelize.DATE })Document.hasMany(Version) // 这将 documentId 属性添加到 version
Document.belongsTo(Version, {as: 'Current',foreignKey: 'currentVersionId'
}) // 这将 currentVersionId 属性添加到 document

但是,上面的代码将导致以下错误: Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents.

为了缓解这一点,我们可以向其中一个关联传递 constraints: false:

Document.hasMany(Version)
Document.belongsTo(Version, {as: 'Current',foreignKey: 'currentVersionId',constraints: false
})

这将可以让我们正确地同步表:

CREATE TABLE IF NOT EXISTS `documents` (`id` INTEGER NOT NULL auto_increment,`author` VARCHAR(255),`currentVersionId` INTEGER,PRIMARY KEY (`id`)
) ENGINE = InnoDB;CREATE TABLE IF NOT EXISTS `versions` (`id` INTEGER NOT NULL auto_increment,`timestamp` DATETIME,`createdAt` DATETIME NOT NULL,`updatedAt` DATETIME NOT NULL,`documentId` INTEGER,PRIMARY KEY (`id`),FOREIGN KEY (`documentId`) REFERENCES `documents` (`id`) ON DELETESETNULL ON UPDATE CASCADE
) ENGINE = InnoDB;

无限制地执行外键引用

有时您可能想引用另一个表,而不添加任何约束或关联。 在这种情况下,您可以手动将参考属性添加到您的模式定义中,并标记它们之间的关系。

const Trainer = sequelize.define('trainer', {firstName: Sequelize.STRING,lastName: Sequelize.STRING
})// Series 将有一个 trainerId = Trainer.id 外参考键
// 之后我们调用 Trainer.hasMany(series)
const Series = sequelize.define('series', {title: Sequelize.STRING,subTitle: Sequelize.STRING,description: Sequelize.TEXT,// 用 `Trainer` 设置外键关系(hasMany)trainerId: {type: Sequelize.INTEGER,references: {model: Trainer,key: 'id'}}
})// Video 将有 seriesId = Series.id 外参考键
// 之后我们调用 Series.hasOne(Video)
const Video = sequelize.define('video', {title: Sequelize.STRING,sequence: Sequelize.INTEGER,description: Sequelize.TEXT,// 用 `Series` 设置关系(hasOne)seriesId: {type: Sequelize.INTEGER,references: {model: Series, // 既可以是表示表名的字符串,也可以是 Sequelize 模型key: 'id'}}
})Series.hasOne(Video)
Trainer.hasMany(Series)

一对一关联

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

BelongsTo

BelongsTo 关联是在 source model 上存在一对一关系的外键的关联。

一个简单的例子是 Player 通过 player 的外键作为 Team 的一部分。

const Player = sequelize.define('player', {}, { timestamps: false })
const Team = sequelize.define('team', {}, { timestamps: false })Player.belongsTo(Team) // 将向 Player 添加一个 teamId 属性以保存 Team 的主键值
CREATE TABLE IF NOT EXISTS `teams` (`id` INTEGER NOT NULL auto_increment,PRIMARY KEY (`id`)
) ENGINE = InnoDB;CREATE TABLE IF NOT EXISTS `players` (`id` INTEGER NOT NULL auto_increment,`teamId` INTEGER,PRIMARY KEY (`id`),FOREIGN KEY (`teamId`) REFERENCES `teams` (`id`) ON DELETESETNULL ON UPDATE CASCADE
) ENGINE = InnoDB;

外键/目标键

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

默认的样式是 camelCase(小驼峰),但是如果源模型配置为 underscored: true(下划线) ,那么将使用字段 snake_case 创建 foreignKey

const User = sequelize.define('user', {}, { timestamps: false, underscored: true })
const Company = sequelize.define('company', {uuid: {type: Sequelize.UUID,primaryKey: true}
})User.belongsTo(Company) // 将用字段 company_uuid 添加 companyUuid 到 user

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

const User = sequelize.define('user', {}, { timestamps: false })
const UserRole = sequelize.define('userRole', {}, { timestamps: false })User.belongsTo(UserRole, { as: 'role' }) // 将 role 添加到 user 而不是 userRole

生成的 users

CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER NOT NULL auto_increment,`roleId` INTEGER,PRIMARY KEY (`id`),FOREIGN KEY (`roleId`) REFERENCES `userRoles` (`id`) ON DELETESETNULL ON UPDATE CASCADE
) ENGINE = InnoDB;

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

const User = sequelize.define('user', {}, { timestamps: false })
const Company = sequelize.define('company', {}, { timestamps: false })User.belongsTo(Company, { foreignKey: 'fk_company' })

目标键

User.belongsTo(Company, { foreignKey: 'fk_companyname', targetKey: 'id' })

效果:

const User = sequelize.define('user',{fk_companyname: {references: {model: Company,key: 'id'}}},{ timestamps: false }
)

HasOne

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

const User = sequelize.define('user', {}, { timestamps: false })
const Project = sequelize.define('project', {}, { timestamps: false })// 单向关联
Project.hasOne(User)// the same as
const User = sequelize.define('user',{projectId: {references: {model: Project,key: 'id'}}},{ timestamps: false }
)
// 你也可以定义外键,例如 如果您已经有一个现有的数据库并且想要处理它:
Project.hasOne(User, { foreignKey: 'initiator_id' })// 因为Sequelize将使用模型的名称(define的第一个参数)作为访问器方法,
// 还可以将特殊选项传递给hasOne:
Project.hasOne(User, { as: 'Initiator' })// 或者让我们来定义一些自己的参考
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

源键

源关键是源模型中的属性,它的目标模型指向外键属性。 默认情况下,hasOne 关系的源键将是源模型的主要属性。 要使用自定义属性,请使用 sourceKey 选项。

const User = sequelize.define('user', {})
const Company = sequelize.define('company', {})// 将 companyName 属性添加到 User
// 使用 Company 的 name 属性作为源属性
Company.hasOne(User, { foreignKey: 'companyName', sourceKey: 'name' })

HasOne 和 BelongsTo 之间的区别

在 Sequelize 1:1 关系中可以使用 HasOneBelongsTo 进行设置。 它们适用于不同的场景。 让我们用一个例子来研究这个差异。

假设我们有两个表可以链接 PlayerTeam 。 让我们定义他们的模型。

const Player = sequelize.define('player', {}, { timestamps: false })
const Team = sequelize.define('team', {}, { timestamps: false })

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

Player 作为 sourceTeam 作为 target

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

Team 作为 sourcePlayer 作为 target

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

HasOneBelongsTo 将关联键插入到不同的模型中。 HasOnetarget 模型中插入关联键,而 BelongsTo 将关联键插入到 source 模型中。

下是一个示例,说明了 BelongsToHasOne 的用法。

const Player = sequelize.define('player', {}, { timestamps: false })
const Team = sequelize.define('team', {}, { timestamps: false })
const Coach = sequelize.define('coach', {}, { timestamps: false })Player.belongsTo(Team) // `teamId` 将被添加到 Player / Source 模型中
Coach.hasOne(Team) // `coachId` 将被添加到 Team / Target 模型中// the same as
const Player = sequelize.define('player', {teamId: {references: {model: Team,key: 'id'}}
})const Team = sequelize.define('team', {coachId: {references: {model: Coach,key: 'id'}}
})

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

  • 当关于关联的信息存在于 source 模型中时,我们可以使用 belongsTo。 在这种情况下,Player 适用于belongsTo,因为它具有 teamId 列。
  • 当关于关联的信息存在于 target 模型中时,我们可以使用 hasOne。 在这种情况下, Coach 适用于 hasOne ,因为 Team 模型将其 Coach 的信息存储为 coachId 字段。

一对多关联 (hasMany)

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

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

这会将 projectId 属性添加到 User。 根据您强调的设置,表中的列将被称为 projectIdproject_idProject 的实例将获得访问器 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' })

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

多对多关联 (BelongsToMany)

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

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

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

CREATE TABLE IF NOT EXISTS `UserProject` (`createdAt` DATETIME NOT NULL,`updatedAt` DATETIME NOT NULL,`projectId` INTEGER,`userId` INTEGER,PRIMARY KEY (`projectId`, `userId`),FOREIGN KEY (`projectId`) REFERENCES `projects` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB;

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

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

有时,您可能需要在关联中使用它们时重命名模型。 让我们通过使用别名(as)选项将 users 定义为 workersprojects 定义为 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' } })

默认情况下,上面的代码会将 projectIduserId 添加到 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 }}}]
})

参考

  • 模型(表)之间的关系/关联
  • Associations - 关联

Sequelize - associations相关推荐

  1. Sequelize 中文文档 v4 - Querying - 查询

    Querying - 查询 此系列文章的应用示例已发布于 GitHub: sequelize-docs-Zh-CN. 可以 Fork 帮助改进或 Star 关注更新. 欢迎 Star. 属性 想要只选 ...

  2. egg连接oracle,egg插件sequelize:表自连接

    地区表: area id pid name level 1 0 福建 1 2 1 福州 2 3 2 厦门 2 自连接查询数据: const area= ctx.model.Area.getTableN ...

  3. php sequelize,Sequelize 中文文档 v4 - Querying - 查询

    Querying - 查询 此系列文章的应用示例已发布于 GitHub: sequelize-docs-Zh-CN. 可以 Fork 帮助改进或 Star 关注更新. 欢迎 Star. 属性 想要只选 ...

  4. nestjs 优秀的ORM框架sequelize操作数据库

    奉上最新代码: nestjs服务demo代码=>gitee地址.github地址 nodejs的ORM–sequelize 笔者在使用koa2开发后端服务的时候用的ORM框架是sequelize ...

  5. NodeJS的Sequelize与Sequelize-cli入门

    1.Sequelize与Sequelize入门 可以查看Sequelize 中文文档:https://www.sequelize.com.cn/ 以及结合下面的代码 进行学习. 1.初始化工作 在no ...

  6. sequelize笔记

    安装 npm install --save sequelize # 选择对应的安装: $ npm install --save pg pg-hstore # Postgres $ npm instal ...

  7. Sequelize使用

    Node.js 使用sequlize 操作mysql数据库时,查询一条记录中两个字段的加和 一.使用Sequelize连接数据库 Sequelize - 使用 model 查询数据 Sequelize ...

  8. 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查

    Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...

  9. Sequelize 4.43.0 发布,基于 Nodejs 的异步 ORM 框架

    Sequelize 4.43.0 发布了,Sequelize 是一款基于 Nodejs 的异步 ORM 框架,它同时支持 PostgreSQL.MySQL.SQLite 和 MSSQL 多种数据库,很 ...

最新文章

  1. 动态生成CheckBox(Winform程序)
  2. mysql 启动出错问题排查
  3. 21 | 哈希算法(上):如何防止数据库中的用户信息被脱库?
  4. MapReduce基础
  5. ikun 潜入?疑似 B 站后台源码泄露
  6. JavaScript中this的指向问题及面试题你掌握了吗?
  7. L1-041 寻找250-PAT团体程序设计天梯赛GPLT
  8. CSS显示:内联vs内联块[重复]
  9. unity 模型销毁_Unity GameObject 销毁(Destroy)后的几种状态
  10. [BZOJ 4403]序列统计(Lucas定理)
  11. Oracle中级篇-物化视图
  12. sql 获取当前日期的季度,年份,月份等日期部分
  13. Excel 列累加技巧
  14. 视频教程-Cisco CCNP路由实验专题讲解视频课程--路由重分发篇-思科认证
  15. pr如何处理音效,如何让你的声音变得干净又清晰?PR音频降噪教程
  16. shell命令实现txt文件转换为csv文件
  17. python就业方向
  18. 无法加载 MySQL ODBC 5.3 Unicode Driver ODBC 驱动程序的安装例程,因为存在错误代码126.
  19. win10 蓝牙耳机已连接但是耳机仍没有声音,音频仍是扬声器输出问题的出现条件及解决方案
  20. 在学习少儿编程中体会AI乐趣

热门文章

  1. Bootstrap笔记(十三) 常用类别 - 文绕图
  2. CSS3 Box Shadow
  3. 【数字信号处理】带通采样定理及其MATLAB仿真
  4. 唐金州的Vue开发实战学习笔记(生态篇)
  5. HashAlgorithm哈希算法信息
  6. C++如何避免数组越界
  7. C#中get和set方法
  8. 高防服务器是如何确认哪些是恶意IP/流量?ip:103.88.32.XXX
  9. ZeroMemory memset 等清零的用法
  10. 写一篇作文:《荷塘月色》