学习结点模型模式

20 阅读3分钟

让我们假设你正在用Sequelize建立一个博客。

在你的博客上,你可以创建一堆Post。作为描述你的文章的一种方式,它可以属于许多不同的Genres。

例如,这篇博文可能有SequelizeOrm ,和Web Development 标签。

到目前为止,我们有两个模型:PostGenre

// Post.js
module.exports = function(sequelize, DataTypes) {
  const Post =  sequelize.define('post', {
    post_id: {
      type: DataTypes.INTEGER(11),
      allowNull: false,
      autoIncrement: true,
      primaryKey: true
    },
    text: {
      type: DataTypes.TEXT,
      allowNull: false
    }
  }, {
    timestamps: true,
    underscored: true,
    tableName: 'post',
  });

  return Post;
};
// Genre.js
module.exports = function(sequelize, DataTypes) {
  const Genre =  sequelize.define('genre', {
    genre_id: {
      type: DataTypes.INTEGER(11),
      allowNull: false,
      autoIncrement: true,
      primaryKey: true
    },
    name: {
      type: DataTypes.STRING(60),
      allowNull: false,
      unique: true
    }
  }, {
    timestamps: true,
    underscored: true,
    tableName: 'genre',
  });

  return Genre;
};

很好。现在我们如何对它们之间的多对多关系进行建模?

建立结点表的模型

我们需要创建一个结点表来存储这两者之间的关系。

为了对它进行建模,我们最终会有三个表,而不是只有两个。

image.png

我知道有一种方法可以让Sequelize仅仅通过使用正确的关联方法来为你创建这个,但我喜欢明确地说明这一点。

让我们继续前进,现在创建额外的模型。

// tagPostGenre.js
module.exports = function(sequelize, DataTypes) {
  const TagPostGenre = sequelize.define('tag_post_genre', {
    tag_post_genre_id: {
      type: DataTypes.UUID,
      defaultValue: DataTypes.INTEGER(11),
      primaryKey: true
    },
    post_id: {
      type: DataTypes.INTEGER(11),
      primaryKey: false,
      references: {
        model: 'post',
        key: 'post_id'
      },
      onDelete: 'cascade',
      onUpdate: 'cascade',
      unique: 'unique-genre-per-post'
    },
    genre_id: {
      type: DataTypes.INTEGER(11),
      primaryKey: false,
      references: {
        model: 'genre',
        key: 'genre_id'
      },
      onDelete: 'cascade',
      onUpdate: 'cascade',
      unique: 'unique-genre-per-post'
    },
  }, {
    timestamps: true,
    underscored: true,
    tableName: 'tag_post_genre'
  });

  return TagPostGenre;
};

另外,没有必要在模型名称前加上 "Tag-",我只是喜欢这样做,因为它可以让我一目了然地了解该表是名称中提到的其他两个表之间的一个连接表

到目前为止,最重要的是这一点。

  • 我们已经创建了与PostGenre 模型的外键关系。
  • 我们添加了一个复合唯一约束,以防止一个帖子两次添加相同的流派。

现在我们已经创建了模型,我们必须用Sequelize把它们全部关联起来,这样我们就可以获得使用它们所暴露的便利方法的好处。

更新关联

TagPostGenre 结界表/模型上添加关联。

TagPostGenre.associate = (models) => {
  TagPostGenre.belongsTo(models.Post, { foreignKey: 'post_id', targetKey: 'post_id', as: 'Post' });
  TagPostGenre.belongsTo(models.Genre, { foreignKey: 'genre_id', targetKey: 'genre_id', as: 'Genre' });
}

Genre 上添加关联。

Genre.associate = (models) => {
  Genre.belongsToMany(models.Post, { as: 'PostsInGenre', through: models.TagPostGenre, foreignKey: 'genre_id'});
}

最后,在Post 上添加关联。

Post.associate = (models) => {
  Post.belongsToMany(models.Genre, { as: 'GenresForPost', through: models.TagPostGenre, foreignKey: 'post_id'});
}

belongsToMany 在第二个参数的配置对象中,使用as 的键是我们在做Eager Loading / Include Queries时指定别名的一种方式。

使用方法

如果你想为一个Post 的实例设置流派...

const post = await Post.findOne({ where: { post_id: 1 }});

由于我们已经添加了关联,你应该可以使用Sequelize添加的便利方法。

在这种特殊情况下,期望在帖子实例上看到类似setGenres() 的东西。

const genreIds = [1,2,3];
await post.setGenres(genreIds);

const genres = await post.getGenres();
console.log(genres) // genre instances! [{}, {}, {}]

我们可以使用流派的id来set 这个帖子的流派。

你也可以使用get-"plural junction association name"() 来检索所有的流派。在这种情况下,getGenres()

你总是可以通过使用vscode调试器仔细检查这些方法是否已经被添加。