Sequelize 关联关系实践
· 阅读需 2 分钟
ORM 最容易让人上头的地方,是看起来什么关系都能一句话配完;但真正落到博客系统里,关联一旦建得太随意,列表接口的查询成本会立刻暴露出来。
以博客为例,最常见的关系有两种:
- 分类和文章:
Category hasMany Post - 标签和文章:
Post belongsToMany Tag
先看最基础的一对多:
Category.hasMany(Post, {
foreignKey: 'categoryId',
as: 'posts',
});
Post.belongsTo(Category, {
foreignKey: 'categoryId',
as: 'category',
});
这类关系的关键不在于语法,而在于命名统一。as 字段如果今天叫 category,明天又叫 postCategory,接口层很快就会变得混乱。
标签为什么更适合走中间表
博客标签的本质就是多对多。一个文章可以有多个标签,一个标签也会挂多个文章。这时候就不应该偷懒往 Post 表里塞一个逗号分隔字符串,而应该老老实实上中间表。
Post.belongsToMany(Tag, {
through: 'post_tags',
foreignKey: 'postId',
otherKey: 'tagId',
as: 'tags',
});
Tag.belongsToMany(Post, {
through: 'post_tags',
foreignKey: 'tagId',
otherKey: 'postId',
as: 'posts',
});
这样做最大的好处有两个:
- 可以很自然地按标签反查文章列表。
- 中间表未来能扩展排序、权重、来源等附加信息。
列表接口不要默认把所有关联都查出来
很多后台一开始图省事,findAll 时把 category、tags、author 全部 include 进去。结果首页文章一多,SQL 立刻变重。
一个更务实的策略是分层:
- 列表页只查必须展示的关联。
- 详情页再把完整关系取出来。
const posts = await Post.findAll({
where: { status: 'published' },
include: [
{
model: Category,
as: 'category',
attributes: ['id', 'name', 'slug'],
},
],
order: [['publishedAt', 'DESC']],
});
什么时候不要着急上关联
如果某个字段只是为了后台筛选,或者只是为了展示一个简单字符串,那就没必要为了“看起来正规”强行拉一层表关系。ORM 设计最忌讳的,就是业务还没稳定,模型已经先复杂起来。
小结
Sequelize 关联配置写出来并不难,真正难的是知道哪些关系值得显式建模,哪些关系应该晚一点再抽象。对博客系统来说,分类和标签是高频核心关系,先把这两层做好,内容列表的质量就能立刻提升。
