基本概念

来源(Source) & 目标(Target)

让我们首先介绍一个基本概念,你将在大多数关联中使用,来源(Source)目标(Target) 模型. 假设你正在尝试在两个模型之间添加关联. 这里我们在 UserProject 之间添加一个 hasOne 关联.

  1. class User extends Model {}
  2. User.init({
  3. name: Sequelize.STRING,
  4. email: Sequelize.STRING
  5. }, {
  6. sequelize,
  7. modelName: 'user'
  8. });
  9. class Project extends Model {}
  10. Project.init({
  11. name: Sequelize.STRING
  12. }, {
  13. sequelize,
  14. modelName: 'project'
  15. });
  16. User.hasOne(Project);

User 模型(调用函数的模型)是 来源(Source). Project 模型(作为参数传递的模型)是 目标(Target).

外键

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

  1. class Task extends Model {}
  2. Task.init({ title: Sequelize.STRING }, { sequelize, modelName: 'task' });
  3. class User extends Model {}
  4. User.init({ username: Sequelize.STRING }, { sequelize, modelName: 'user' });
  5. User.hasMany(Task); // 将userId添加到Task模型
  6. Task.belongsTo(User); // 同样会将userId添加到Task模型中

将生成以下SQL:

  1. CREATE TABLE IF NOT EXISTS "users" (
  2. "id" SERIAL,
  3. "username" VARCHAR(255),
  4. "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  5. "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  6. PRIMARY KEY ("id")
  7. );
  8. CREATE TABLE IF NOT EXISTS "tasks" (
  9. "id" SERIAL,
  10. "title" VARCHAR(255),
  11. "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  12. "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  13. "userId" INTEGER REFERENCES "users" ("id") ON DELETE
  14. SET
  15. NULL ON UPDATE CASCADE,
  16. PRIMARY KEY ("id")
  17. );

tasksusers 模型之间的关系通过在 tasks 表上注入 userId 外键,并将其标记为对 users 表的引用. 默认情况下,如果引用的用户被删除,userId 将被设置为 NULL,如果更新了则更新 userId. 可以通过将 onUpdateonDelete 参数传递给关联调用来覆盖这些参数 . 验证参数是 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 选项.

  1. class Task extends Model {}
  2. Task.init({
  3. title: Sequelize.STRING
  4. }, {
  5. underscored: true,
  6. sequelize,
  7. modelName: 'task'
  8. });
  9. class User extends Model {}
  10. User.init({
  11. username: Sequelize.STRING
  12. }, {
  13. underscored: true,
  14. sequelize,
  15. modelName: 'user'
  16. });
  17. // 将 userId 添加到 Task 模型,但字段将设置为 `user_id`
  18. // 这意味着列名称将是 `user_id`
  19. User.hasMany(Task);
  20. // 同样会将 userId 添加到 Task 模型,但字段将设置为 `user_id`
  21. // 这意味着列名称将是 `user_id`
  22. Task.belongsTo(User);

将生成以下SQL:

  1. CREATE TABLE IF NOT EXISTS "users" (
  2. "id" SERIAL,
  3. "username" VARCHAR(255),
  4. "created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
  5. "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL,
  6. PRIMARY KEY ("id")
  7. );
  8. CREATE TABLE IF NOT EXISTS "tasks" (
  9. "id" SERIAL,
  10. "title" VARCHAR(255),
  11. "created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
  12. "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL,
  13. "user_id" INTEGER REFERENCES "users" ("id") ON DELETE
  14. SET
  15. NULL ON UPDATE CASCADE,
  16. PRIMARY KEY ("id")
  17. );

注入模型的命名属性仍然为驼峰,只是 field 参数将其设置为下划线版本.

循环依赖关系 & 禁用约束

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

  1. class Document extends Model {}
  2. Document.init({
  3. author: Sequelize.STRING
  4. }, { sequelize, modelName: 'document' });
  5. class Version extends Model {}
  6. Version.init({
  7. timestamp: Sequelize.DATE
  8. }, { sequelize, modelName: 'version' });
  9. Document.hasMany(Version); // 这会将 documentId 属性添加到 version 中
  10. Document.belongsTo(Version, {
  11. as: 'Current',
  12. foreignKey: 'currentVersionId'
  13. }); // 这会将 currentVersionId 属性添加到 document 中

但是,上面的代码将导致以下错误: Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents.为了解决这一问题,我们向其中一个关联传递 constraints: false:

  1. Document.hasMany(Version);
  2. Document.belongsTo(Version, {
  3. as: 'Current',
  4. foreignKey: 'currentVersionId',
  5. constraints: false
  6. });

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

  1. CREATE TABLE IF NOT EXISTS "documents" (
  2. "id" SERIAL,
  3. "author" VARCHAR(255),
  4. "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  5. "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  6. "currentVersionId" INTEGER,
  7. PRIMARY KEY ("id")
  8. );
  9. CREATE TABLE IF NOT EXISTS "versions" (
  10. "id" SERIAL,
  11. "timestamp" TIMESTAMP WITH TIME ZONE,
  12. "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  13. "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
  14. "documentId" INTEGER REFERENCES "documents" ("id") ON DELETE
  15. SET
  16. NULL ON UPDATE CASCADE,
  17. PRIMARY KEY ("id")
  18. );

在没有约束的情况下强制执行外键引用

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

  1. class Trainer extends Model {}
  2. Trainer.init({
  3. firstName: Sequelize.STRING,
  4. lastName: Sequelize.STRING
  5. }, { sequelize, modelName: 'trainer' });
  6. // 在我们调用 Trainer.hasMany(series) 后,
  7. // series 将有一个 trainerId = Trainer.id 外键
  8. class Series extends Model {}
  9. Series.init({
  10. title: Sequelize.STRING,
  11. subTitle: Sequelize.STRING,
  12. description: Sequelize.TEXT,
  13. // 用 `Trainer` 设置 FK 关系(hasMany)
  14. trainerId: {
  15. type: Sequelize.INTEGER,
  16. references: {
  17. model: Trainer,
  18. key: 'id'
  19. }
  20. }
  21. }, { sequelize, modelName: 'series' });
  22. // 在我们调用 Series.hasOne(Video) 之后
  23. // video 将有 seriesId = Series.id 外键
  24. class Video extends Model {}
  25. Video.init({
  26. title: Sequelize.STRING,
  27. sequence: Sequelize.INTEGER,
  28. description: Sequelize.TEXT,
  29. // 用 `Series` 设置关系(hasOne)
  30. seriesId: {
  31. type: Sequelize.INTEGER,
  32. references: {
  33. model: Series, // 既可以是表示表名的字符串,也可以是 Sequelize 模型
  34. key: 'id'
  35. }
  36. }
  37. }, { sequelize, modelName: 'video' });
  38. Series.hasOne(Video);
  39. Trainer.hasMany(Series);