Sequelize - associations
本部分描述了 Sequelize 中的各种关联类型。 Sequelize 中有四种类型的关联
BelongsTo
HasOne
HasMany
BelongsToMany
基本概念
Source & Target
我们首先从一个基本概念开始,你将会在大多数关联中使用 source
和 target
模型。 假设您正试图在两个模型之间添加关联。 这里我们在 users
和 articles
之间添加一个 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
(函数被调用的模型)是 source
。 ArticleModel
模型(作为参数传递的模型)是 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;
tasks
和 users
模型之间的关系通过在 tasks
表上注入 userId 外键,并将其标记为对 users
表的引用。
默认情况下,如果引用的用户被删除,userId
将被设置为 NULL
,如果更新了 userId
,则更新 userId
。 这些选项可以通过将 onUpdate
和 onDelete
选项传递给关联调用来覆盖。
验证选项是RESTRICT
, CASCADE
, NO ACTION
, SET DEFAULT
, SET NULL
。
对于 1:1
和 1: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
关系中可以使用 HasOne
和 BelongsTo
进行设置。 它们适用于不同的场景。 让我们用一个例子来研究这个差异。
假设我们有两个表可以链接 Player
和 Team
。 让我们定义他们的模型。
const Player = sequelize.define('player', {}, { timestamps: false })
const Team = sequelize.define('team', {}, { timestamps: false })
当我们连接 Sequelize
中的两个模型时,我们可以将它们称为一对 source
和 target
模型。像这样
将 Player 作为 source 而 Team 作为 target
Player.belongsTo(Team)
//或
Player.hasOne(Team)
将 Team 作为 source 而 Player 作为 target
Team.belongsTo(Player)
//Or
Team.hasOne(Player)
HasOne
和 BelongsTo
将关联键插入到不同的模型中。 HasOne
在 target
模型中插入关联键,而 BelongsTo
将关联键插入到 source
模型中。
下是一个示例,说明了 BelongsTo
和 HasOne
的用法。
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
。 根据您强调的设置,表中的列将被称为 projectId
或 project_id
。 Project
的实例将获得访问器 getWorkers
和 setWorkers
。
有时您可能需要在不同的列上关联记录,您可以使用 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
的新模型,具有等效的外键 projectId
和 userId
。 属性是否为 camelcase
取决于由表(在这种情况下为 User
和 Project
)连接的两个模型。
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;
定义 through
为 required
。 Sequelize
以前会尝试自动生成名称,但并不总是导致最合乎逻辑的设置。
这将添加方法 getUsers
,setUsers
, addUser
, addUsers
到 Project
, 还有 getProjects
, setProjects
, addProject
, 和 addProjects
到 User
.
有时,您可能需要在关联中使用它们时重命名模型。 让我们通过使用别名(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 }}}]
})
参考
- 模型(表)之间的关系/关联
- Associations - 关联
Sequelize - associations相关推荐
- Sequelize 中文文档 v4 - Querying - 查询
Querying - 查询 此系列文章的应用示例已发布于 GitHub: sequelize-docs-Zh-CN. 可以 Fork 帮助改进或 Star 关注更新. 欢迎 Star. 属性 想要只选 ...
- egg连接oracle,egg插件sequelize:表自连接
地区表: area id pid name level 1 0 福建 1 2 1 福州 2 3 2 厦门 2 自连接查询数据: const area= ctx.model.Area.getTableN ...
- php sequelize,Sequelize 中文文档 v4 - Querying - 查询
Querying - 查询 此系列文章的应用示例已发布于 GitHub: sequelize-docs-Zh-CN. 可以 Fork 帮助改进或 Star 关注更新. 欢迎 Star. 属性 想要只选 ...
- nestjs 优秀的ORM框架sequelize操作数据库
奉上最新代码: nestjs服务demo代码=>gitee地址.github地址 nodejs的ORM–sequelize 笔者在使用koa2开发后端服务的时候用的ORM框架是sequelize ...
- NodeJS的Sequelize与Sequelize-cli入门
1.Sequelize与Sequelize入门 可以查看Sequelize 中文文档:https://www.sequelize.com.cn/ 以及结合下面的代码 进行学习. 1.初始化工作 在no ...
- sequelize笔记
安装 npm install --save sequelize # 选择对应的安装: $ npm install --save pg pg-hstore # Postgres $ npm instal ...
- Sequelize使用
Node.js 使用sequlize 操作mysql数据库时,查询一条记录中两个字段的加和 一.使用Sequelize连接数据库 Sequelize - 使用 model 查询数据 Sequelize ...
- 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查
Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...
- Sequelize 4.43.0 发布,基于 Nodejs 的异步 ORM 框架
Sequelize 4.43.0 发布了,Sequelize 是一款基于 Nodejs 的异步 ORM 框架,它同时支持 PostgreSQL.MySQL.SQLite 和 MSSQL 多种数据库,很 ...
最新文章
- 动态生成CheckBox(Winform程序)
- mysql 启动出错问题排查
- 21 | 哈希算法(上):如何防止数据库中的用户信息被脱库?
- MapReduce基础
- ikun 潜入?疑似 B 站后台源码泄露
- JavaScript中this的指向问题及面试题你掌握了吗?
- L1-041 寻找250-PAT团体程序设计天梯赛GPLT
- CSS显示:内联vs内联块[重复]
- unity 模型销毁_Unity GameObject 销毁(Destroy)后的几种状态
- [BZOJ 4403]序列统计(Lucas定理)
- Oracle中级篇-物化视图
- sql 获取当前日期的季度,年份,月份等日期部分
- Excel 列累加技巧
- 视频教程-Cisco CCNP路由实验专题讲解视频课程--路由重分发篇-思科认证
- pr如何处理音效,如何让你的声音变得干净又清晰?PR音频降噪教程
- shell命令实现txt文件转换为csv文件
- python就业方向
- 无法加载 MySQL ODBC 5.3 Unicode Driver ODBC 驱动程序的安装例程,因为存在错误代码126.
- win10 蓝牙耳机已连接但是耳机仍没有声音,音频仍是扬声器输出问题的出现条件及解决方案
- 在学习少儿编程中体会AI乐趣