【Sequelize】关联模型和孤儿记录
一、关联模型的核心机制
1. 关联类型与组合规则
• 基础四类型:
• hasOne
:外键存储于目标模型(如用户档案表存储用户ID)
• belongsTo
:外键存储于源模型(如订单表存储用户ID)
• hasMany
:一对多关系,外键在目标模型(如用户有多篇文章)
• belongsToMany
:通过中间表实现多对多(如用户-角色中间表)
• 必须成对定义:
• 单边定义会导致预加载失效。例如仅定义Foo.hasOne(Bar)
时,无法通过Bar.findAll({ include: Foo })
查询
• 正确组合方式:
// 一对一User.hasOne(Profile);Profile.belongsTo(User);// 一对多User.hasMany(Post);Post.belongsTo(User);// 多对多User.belongsToMany(Role, { through: UserRole });Role.belongsToMany(User, { through: UserRole });
有关联字段的表,一定是属于 belongsTo 其他表的。
2. 高级关联配置
• 自定义外键与别名:
User.hasMany(Comment, {foreignKey: 'authorId', // 覆盖默认的userIdas: 'reviews' // 通过user.getReviews()访问
});
当模型名称复数形式异常时(如Person→People
),需通过as
别名修正方法名
• 非主键关联:
Ship.belongsTo(Captain, {targetKey: 'name', // 关联船长表的name字段(需唯一约束)foreignKey: 'captainName'
});
需确保目标字段有唯一索引,否则会报错
3. 实际应用
在模型定义的时候关联模型:
然后再进行查询的第三个配置参数中定义 condition,来对查询结果进行关联:
二、孤儿记录的防护策略
孤儿记录:比如一个分类中关联了多个课程,此时将这个分类删掉,那么课程将找不到父记录,因此就成为了孤儿记录。
解决这个问题有三个方法:
- 设置外键约束(一般不用,在数据量大、高并发的场景下,会出现性能瓶颈)
- 删除分类的同时,删除该分类下的所有课程(代码层面,问题是可能发生误删除操作)
- 只有当没有关联该分类的课程时,才能被删掉(代码层面,推荐)
1. 外键约束级联
在定义关联时启用数据库级联删除,确保父记录删除时自动清理子记录:
// 用户删除时级联删除文章
User.hasMany(Post, {foreignKey: { allowNull: false }, // 强制外键非空onDelete: 'CASCADE' // 关键配置
});
此配置会在SQL层面生成ON DELETE CASCADE
约束,彻底避免孤儿记录
2. 中间表清理(多对多关系)
使用钩子函数清理关联记录:
// 删除教师前清理关联表
Teacher.beforeDestroy(async (teacher) => {await Relation.destroy({where: { teacherId: teacher.id }});
});
特别适用于多对多关系中间表的数据维护
3. 事务保护
关键操作使用事务保证原子性:
await sequelize.transaction(async (t) => {const user = await User.findByPk(1, { transaction: t });await user.destroy({ transaction: t });// 自动触发关联的级联删除
});
防止在删除过程中断导致数据不一致
三、最佳实践建议
-
索引优化:
• 所有外键字段必须建立索引
• 多对多中间表的联合索引:Relation.init({studentId: { type: INTEGER, primaryKey: true },teacherId: { type: INTEGER, primaryKey: true } }, { sequelize });
通过复合主键提升查询效率
-
数据验证:
Post.belongsTo(User, {foreignKey: {validate: {isUserIdValid(value) {if (!validator.isUUID(value)) throw new Error('非法用户ID');}}} });
-
预加载优化:
User.findAll({include: [{model: Post,attributes: ['id', 'title'], // 按需加载字段where: { status: 'published' } // 过滤条件}] });
避免N+1查询问题