基本概念
来源(Source) & 目标(Target)
让我们首先介绍一个基本概念,你将在大多数关联中使用,来源(Source) 和 目标(Target) 模型. 假设你正在尝试在两个模型之间添加关联. 这里我们在 User
和 Project
之间添加一个 hasOne
关联.
class User extends Model {}
User.init({
name: Sequelize.STRING,
email: Sequelize.STRING
}, {
sequelize,
modelName: 'user'
});
class Project extends Model {}
Project.init({
name: Sequelize.STRING
}, {
sequelize,
modelName: 'project'
});
User.hasOne(Project);
User
模型(调用函数的模型)是 来源(Source).
Project
模型(作为参数传递的模型)是 目标(Target).
外键
当你在模型中创建关联时,会自动创建带约束的外键引用. 下面是设置:
class Task extends Model {}
Task.init({ title: Sequelize.STRING }, { sequelize, modelName: 'task' });
class User extends Model {}
User.init({ username: Sequelize.STRING }, { sequelize, modelName: 'user' });
User.hasMany(Task); // 将userId添加到Task模型
Task.belongsTo(User); // 同样会将userId添加到Task模型中
将生成以下SQL:
CREATE TABLE IF NOT EXISTS "users" (
"id" SERIAL,
"username" VARCHAR(255),
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "tasks" (
"id" SERIAL,
"title" VARCHAR(255),
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"userId" INTEGER REFERENCES "users" ("id") ON DELETE
SET
NULL ON UPDATE CASCADE,
PRIMARY KEY ("id")
);
tasks
和 users
模型之间的关系通过在 tasks
表上注入 userId
外键,并将其标记为对 users
表的引用. 默认情况下,如果引用的用户被删除,userId
将被设置为 NULL
,如果更新了则更新 userId
. 可以通过将 onUpdate
和 onDelete
参数传递给关联调用来覆盖这些参数 . 验证参数是 RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
.
对于 1:1 和 1:m 关联,默认选项是 SET NULL
用于删除,CASCADE
用于更新. 对于 n:m,两者的默认值是 CASCADE
. 这意味着,如果你从 n:m 关联的一侧删除或更新一行,则引用该行的连接表中的所有行也将被删除或更新.
underscored 选项
Sequelize 允许为 Model 设置 underscored
选项. 当 true
时,此选项会将所有属性的 field
参数设置为其名称的下划线版本. 这也适用于关联生成的外键.
让我们修改最后一个例子来使用 underscored
选项.
class Task extends Model {}
Task.init({
title: Sequelize.STRING
}, {
underscored: true,
sequelize,
modelName: 'task'
});
class User extends Model {}
User.init({
username: Sequelize.STRING
}, {
underscored: true,
sequelize,
modelName: 'user'
});
// 将 userId 添加到 Task 模型,但字段将设置为 `user_id`
// 这意味着列名称将是 `user_id`
User.hasMany(Task);
// 同样会将 userId 添加到 Task 模型,但字段将设置为 `user_id`
// 这意味着列名称将是 `user_id`
Task.belongsTo(User);
将生成以下SQL:
CREATE TABLE IF NOT EXISTS "users" (
"id" SERIAL,
"username" VARCHAR(255),
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "tasks" (
"id" SERIAL,
"title" VARCHAR(255),
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL,
"user_id" INTEGER REFERENCES "users" ("id") ON DELETE
SET
NULL ON UPDATE CASCADE,
PRIMARY KEY ("id")
);
注入模型的命名属性仍然为驼峰,只是 field
参数将其设置为下划线版本.
循环依赖关系 & 禁用约束
在表之间添加约束意味着在使用 sequelize.sync
时,必须以特定顺序在数据库中创建表. 如果Task
引用了User
,则必须在创建tasks
表之前创建users
表. 这有时会导致循环引用,其中 sequelize 无法找到要同步的顺序. 想象一下文档和版本的场景. 文档可以有多个版本,为方便起见,文档可以引用其当前版本.
class Document extends Model {}
Document.init({
author: Sequelize.STRING
}, { sequelize, modelName: 'document' });
class Version extends Model {}
Version.init({
timestamp: Sequelize.DATE
}, { sequelize, modelName: 'version' });
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" SERIAL,
"author" VARCHAR(255),
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"currentVersionId" INTEGER,
PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "versions" (
"id" SERIAL,
"timestamp" TIMESTAMP WITH TIME ZONE,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"documentId" INTEGER REFERENCES "documents" ("id") ON DELETE
SET
NULL ON UPDATE CASCADE,
PRIMARY KEY ("id")
);
在没有约束的情况下强制执行外键引用
有时你可能想引用另一个表,而不添加任何约束或关联. 在这种情况下,你可以手动将参考属性添加到你的模式定义中,并标记它们之间的关系.
class Trainer extends Model {}
Trainer.init({
firstName: Sequelize.STRING,
lastName: Sequelize.STRING
}, { sequelize, modelName: 'trainer' });
// 在我们调用 Trainer.hasMany(series) 后,
// series 将有一个 trainerId = Trainer.id 外键
class Series extends Model {}
Series.init({
title: Sequelize.STRING,
subTitle: Sequelize.STRING,
description: Sequelize.TEXT,
// 用 `Trainer` 设置 FK 关系(hasMany)
trainerId: {
type: Sequelize.INTEGER,
references: {
model: Trainer,
key: 'id'
}
}
}, { sequelize, modelName: 'series' });
// 在我们调用 Series.hasOne(Video) 之后
// video 将有 seriesId = Series.id 外键
class Video extends Model {}
Video.init({
title: Sequelize.STRING,
sequence: Sequelize.INTEGER,
description: Sequelize.TEXT,
// 用 `Series` 设置关系(hasOne)
seriesId: {
type: Sequelize.INTEGER,
references: {
model: Series, // 既可以是表示表名的字符串,也可以是 Sequelize 模型
key: 'id'
}
}
}, { sequelize, modelName: 'video' });
Series.hasOne(Video);
Trainer.hasMany(Series);