带你彻底了解sequelize 表关联 -- 多对多
目录
[TOC]
在使用Sequelize进行数据库建模时,通常需要处理多个表之间的关联关系,特别是在涉及多对多(many-to-many)关系时。为了让你更好地理解如何使用这些概念,本文将通过一个具体的示例(用户与角色的关系)来详细讲解 Sequelize 中的 belongsToMany , through , foreignKey , otherKey 和 references 等概念。
1. 理解多对多关系
在数据库设计中,多对多关系是一种常见的关联类型,表示两个模型之间可以互相拥有多个实例。例如,用户(User)和角色(Role)之间就是典型的多对多关系:一个用户可以拥有多个角色,一个角色也可以被多个用户拥有。
为了在 Sequelize 中定义这种关系,我们需要使用 belongsToMany 方法。这个方法需要指定一个中间表(也叫连接表或 junction table),用来存储两个模型之间的关联关系。
2. 定义中间表模型
首先,我们定义了三个模型: User 、 Role 和 UserRole 。
- User模型 :代表用户的信息。
- Role模型 :代表角色的信息。
- UserRole模型 :作为中间表,存储用户和角色之间的关联。
每个用户可以拥有多个角色,每个角色也可以被多个用户拥有。因此,这是一个典型的多对多关系。
为了表示这种关系,我们需要一个中间表: 用户角色表(UserRole) ,它包含了用户 ID和角色 ID,以表示某个用户与某个角色的关联
const { DataTypes } = require("sequelize");
const seq = require("../db/seq");
const UserRole = seq.define("st_UserRole", {
users_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: "用户ID",
references: {
model: "st_users", // 关联的模型表名
key: "id", // 关联的主键
},
},
role_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: "角色ID",
references: {
model: "st_roles", // 关联的模型表名
key: "id", // 关联的主键
},
},
});
module.exports = UserRol
这里, UserRole 定义了两个字段: users_id 和 role_id ,分别对应 User 和 Role 的主键。 references 属性用来指定外键关联的模型和字段。
references:该属性定义了外键约束的具体表和字段,确保users_id和role_id字段分别与users表和roles表的主键字段进行关联。
3. 定义关联关系
接下来,在 setupAssociations 函数中,我们使用 belongsToMany 方法来建立 User 和 Role 之间的多对多关系:
const setupAssociations = () => {
User.belongsToMany(Role, {
through: UserRole, // 指定中间表
foreignKey: "users_id", // 本模型的外键
otherKey: "role_id", // 目标模型的外键
});
Role.belongsToMany(User, {
through: UserRole, // 指定中间表
foreignKey: "role_id", // 本模型的外键
otherKey: "users_id", // 目标模型的外键
});
};
module.exports = setupAssociations;
through:指定中间表模型,这里是UserRole。foreignKey:表示当前模型用于关联的外键字段。例如,在User.belongsToMany(Role)中,foreignKey是users_id,表示User的id在UserRole中的字段。otherKey:表示目标模型用于关联的外键字段。在User.belongsToMany(Role)中,otherKey是role_id,表示Role的id在UserRole中的字段。references:在定义UserRole模型时使用,用来指定外键关联的模型和具体字段。
4. 查询用户角色的方法
接下来,我们来看如何通过 Sequelize 的方法来获取一个用户所拥有的角色。在提供的代码中,有一个 getUserAssignedRoles 函数:
async getUserAssignedRoles(id) {
try {
// 使用 findOne 替代 findByPk,并添加更多的查询选项
const user = await User.findOne({
where: { id },
attributes: ["id"],
include: [
{
model: Role,
through: {
attributes: [], // 不包含中间表字段
},
attributes: ["id", "role_name", "permission_key"], // 只返回需要的字段
where: {
status: 1, // 只查询启用的角色
},
},
],
});
return {
assignedRoles: user?.st_Roles || [], // 使用可选链操作符
};
} catch (error) {
console.error("Get user assigned roles error:", error);
// 其他错误返回空数组
return {
assignedRoles: [],
};
}
}
User.findOne:根据用户的ID查找用户。attributes:指定返回的用户信息字段,这里只返回id。include:包含关联的角色信息。
model: Role:指定要包含的模型。through: { attributes: [] }:指定不包含中间表UserRole的任何额外信息。attributes: ["id", "role_name", "permission_key"]:只返回角色的指定字段。where: { status: 1 }:添加条件,只查找状态为1(启用)的角色。
- 返回结果 :如果找到用户,返回其关联的角色数组;如果用户不存在或发生其他错误,返回空数组。
5. 官方文档的补充说明
参考 Sequelize 官方文档中关于 Advanced Many-to-Many Associations 的说明,我们可以了解到:
-
通过表(Through Table) vs. 普通表(Normal Table) :在多对多关系中,通常需要一个中间表来存储关联信息。Sequelize 提供了自动生成这个中间表的选项,但也可以手动定义,以便增加更多的字段或索引。
-
超级多对多关系(Super Many-to-Many Association) :通过在两个模型之间同时定义
belongsToMany关联,并且在中间表中添加额外的字段,可以实现更复杂的数据结构和查询逻辑。
通过 Sequelize 的 belongsToMany 、 through 、 foreignKey 、 otherKey 和 references 等功能,我们能够轻松地建立和管理多对多关系。理解这些概念有助于我们更好地设计和查询复杂的数据库模型,使得我们可以高效地管理表之间的关联数据。
核心概念总结:
belongsToMany:用于建立多对多关系。through:指定多对多关系中的中间表。foreignKey:指定当前模型在中间表中的外键字段。otherKey:指定关联模型在中间表中的外键字段。references:定义外键约束,确保字段和关联表的主键一致。
通过这些工具,Sequelize 可以自动处理多对多关系中的数据操作,大大简化了复杂的查询和数据插入工作。
foreignKey 和 otherKey 具体作用
当你使用 belongsToMany 设置多对多关联时,Sequelize 需要知道:
- 当前模型在中间表中的外键字段是哪个。
- 关联模型在中间表中的外键字段是哪个。
这时, foreignKey 和 otherKey 就用来解决这个问题。
1. foreignKey
foreignKey 是 当前模型 在中间表中使用的外键字段。换句话说,它指定了在中间表中用来指向当前模型(比如用户模型)的字段。
举个例子:
User.belongsToMany(Role, {
through: UserRole, // 使用中间表 UserRole
foreignKey: 'users_id', // 'users_id' 在 UserRole 中指向 'users' 表的主键(通常是 id)
otherKey: 'role_id', // 'role_id' 在 UserRole 中指向 'roles' 表的主键(通常是 id)
});
- 在这个例子中,
User模型的foreignKey: 'users_id'表示,在UserRole中,users_id字段指向的是users表的主键(id)。 - 也就是说,
users_id表示这个关联是哪个 用户 。
2. otherKey
otherKey 是 关联模型 在中间表中的外键字段。它指定了在中间表中用来指向关联模型(比如角色模型)的字段。
在上面的代码中, otherKey: 'role_id' 表示,在 UserRole 中, role_id 字段指向的是 roles 表的主键( id )。也就是说, role_id 表示这个关联是哪个 角色 。
为什么需要这两个字段?
假设我们有以下两个模型:
- User :用户模型
- Role :角色模型
这两个模型通过 UserRole 表建立多对多关系。在 UserRole 表中,有两个外键字段:
- 一个是
users_id,它指向users表的主键。 - 另一个是
role_id,它指向roles表的主键。
如果你只定义了 foreignKey ,Sequelize 只能知道如何将当前模型( User )连接到中间表,但它不知道如何将关联模型( Role )连接到中间表。
所以, foreignKey 是当前模型的外键字段,而 otherKey 是关联模型的外键字段。它们一起工作,确保多对多关系能够正常运作。
总结
foreignKey:指定当前模型在中间表中的外键字段,通常指向当前模型(如users)的主键。otherKey:指定关联模型在中间表中的外键字段,通常指向关联模型(如roles)的主键。
这两个字段帮助 Sequelize 正确地建立和查询多对多的关系。
关联表的增删改
其实关联关系搞定之后 关联表的操作余crud一样操作就行
- 验证用户存在 :确保要操作的用户存在。
- 处理特殊情况 :如果角色列表为空,直接删除所有关联。
- 查找有效角色 :确保传入的角色ID都是有效的。
- 比对当前关联 :找出需要删除和新增的角色关联。
- 执行删除和新增 :精准地删除不再需要的关联,批量新增新的关联。
- 返回结果 :操作成功返回
true,失败则抛出错误。
async saveUserRole(userId, roleIdList) {
try {
const user = await User.findByPk(userId);
if (!user) {
throw new Error("User not found");
}
// 如果传入空数组,直接删除所有角色关联
if (roleIdList.length === 0) {
await UserRole.destroy({
where: {
users_id: userId,
},
});
return true; // 直接返回,表示操作成功
}
// 查找所有有效角色
const roles = await Role.findAll({
where: {
id: roleIdList,
},
});
if (!roles.length) {
throw new Error("No valid roles found");
}
// 获取当前用户已关联的角色
const currentUserRoles = await UserRole.findAll({
where: { users_id: userId },
attributes: ["role_id"],
});
// 提取当前用户的角色ID列表
const currentRoleIds = currentUserRoles.map((role) => role.role_id);
// 找出需要删除和新增的角色
const rolesToDelete = currentRoleIds.filter(
(roleId) => !roleIdList.includes(roleId)
);
const rolesToAdd = roleIdList.filter(
(roleId) => !currentRoleIds.includes(roleId)
);
// 如果有需要删除的角色,先删除
const deleteCount = rolesToDelete.length;
if (deleteCount > 0) {
await UserRole.destroy({
where: {
users_id: userId,
role_id: rolesToDelete,
},
});
}
// 如果有需要新增的角色,进行插入
const addCount = rolesToAdd.length;
if (addCount > 0) {
const roleAssociations = rolesToAdd.map((roleId) => ({
users_id: userId,
role_id: roleId,
}));
await UserRole.bulkCreate(roleAssociations);
}
// 返回更新成功
return true;
} catch (error) {
console.error("Save user role error:", error);
throw error;
}
}