0.前言
我们在 Node 项目中使用 Sequelize 操作数据库时,通常需要处理多个表之间的关联关系。例如用户和订单之间的一对多关系、用户和角色之间的多对多关系。
网上的教程大部分都是给两行代码,缺少很多细节。
本文将通过详细的代码案例来讲解 Sequelize 中的一对一、一对多、多对多关系。绝对细节满满、通俗易懂。
1. 一对一
这里以用户和资料为例讲解一对一关系。一个用户对应一个资料。
1.1 创建数据库表
用户表
css
CREATE TABLE `z_user` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`age` int DEFAULT NULL,
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`status` tinyint DEFAULT NULL,
`create_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `email` (`email`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
资料表
css
CREATE TABLE `z_profile` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int DEFAULT NULL COMMENT '用户id',
`birthday` datetime DEFAULT NULL COMMENT '生日',
`work` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '工作',
PRIMARY KEY (`id`),
KEY `foreign_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
1.2 定义模型(实体)
User 模型
css
const User = sequelize.define('User', {
id: {
type: Sequelize.INTEGER, // 类型
primaryKey: true, // 主键
autoIncrement: true // 是否允许自增
},
name: {
type: Sequelize.STRING(50),
allowNull: false, // 是否允许为空
unique: true // 唯一
},
age: {
type: Sequelize.INTEGER,
allowNull: false, // 是否允许为空
},
email: {
type: Sequelize.STRING,
},
status: {
type: Sequelize.INTEGER,
defaultValue: 1
}
}, {
sequelize, // 我们需要传递连接实例
tableName: 'z_user',// 对应的数据库表
timestamps: false
});
Profile 实体
css
const Profile = sequelize.define('Profile', {
id: {
type: Sequelize.INTEGER, // 类型
primaryKey: true, // 主键
autoIncrement: true // 是否允许自增
},
birthday: {
type: Sequelize.DATE,
allowNull: false, // 是否允许为空
},
work: {
type: Sequelize.STRING(50),
allowNull: false, // 是否允许为空
},
}, {
sequelize, // 我们需要传递连接实例
tableName: 'z_profile',// 对应的数据库表
timestamps: false
});
1.3 定义一对一关系
我们在 profile 的模型里面定义关联关系:
1. 基本版本
css
// 一个用户拥有一个资料
User.hasOne(Profile);
// 一个资料只属于一个用户
Profile.belongsTo(User);
测试:
css
// 获取用户详情
router.get('/detail', async (req, res) => {
try {
let result = await User.findOne({ include: Profile, where: { id: req.query.id } });
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
findOne 可以通过 id 查询信息,include 表示查询结果包含 Profile 的信息。
但是我们发现测试结果报错。
这是因为 hasOne 和 belongsTo 方法如果只传递一个模型参数,那 sequelize 默认会认为 Profile 模型对应数据库表的外键就是 userId 。而我们设计的外键字段是 user_id
这时候我们把 z_profile 表中的 user_id 改为 userId,再次测试:
成功!
2. 自定义外键版
那如果不想修改表中的字段,外键字段还想是 user_id 呢?
只需要在设置关联关系时,通过 foreignKey 指定外键是 user_id 即可:
测试结果同上。
2.一对多
这里以用户和订单为例讲解一对多关系。一个用户对应多个订单。
2.1 创建数据库表
订单表
css
CREATE TABLE `z_order` (
`id` int NOT NULL AUTO_INCREMENT,
`number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '编号',
`user_id` int DEFAULT NULL COMMENT '用户id',
`order_amount` decimal(10,2) DEFAULT NULL COMMENT '订单总金额',
`status` tinyint DEFAULT '0' COMMENT '订单状态',
`create_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `email` (`order_amount`) USING BTREE,
KEY `order_ibfk_1` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
2.2 定义模型(实体)
Order 模型
css
const Order = sequelize.define('Order', {
id: {
type: Sequelize.INTEGER, // 类型
primaryKey: true, // 主键
autoIncrement: true // 是否允许自增
},
number: {
type: Sequelize.STRING(50),
allowNull: false, // 是否允许为空
unique: true // 唯一
},
orderAmount: {
type: Sequelize.DECIMAL,
defaultValue: 0,
field: "order_amount"
},
status: {
type: Sequelize.INTEGER,
defaultValue: 1
}
}, {
sequelize, // 我们需要传递连接实例
tableName: 'z_order',// 对应的数据库表
timestamps: false
});
注:如果数据库订单表中金额是 order_amount,js 模型中是 orderAmount,需要通过 field 属性指定表的属性。
2.3 定义一对多关系
1.基本版
在 order.js 模型中定义一对多的关联关系
如果 z_order 表中用户外键是 userId,可以使用以下方式指定关联关系:
css
// 一个用户拥有多个订单
User.hasMany(Order);
// 一个订单属于一个用户
Order.belongsTo(User);
2.自定义外键版
如果 z_order 表中用户外键是 user_id,可以使用指定 foreignKey 的方式指定关联关系:
测试:
css
// 获取用户订单列表
router.get('/getOrderList', async (req, res) => {
try {
const result = await User.findOne({ include: Order, where: { id: req.query.id } });
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
3.多对多
这里以用户和角色为例讲解多对多关系。一个用户对应多个角色。一个角色属于多个用户。
3.1 创建数据库表
角色表
css
CREATE TABLE `z_role` (
`id` int NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
用户角色关联表
css
CREATE TABLE `z_user_role` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`role_id` int NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
3.2 定义模型(实体)
Role 模型
css
const Role = sequelize.define('Role', {
id: {
type: Sequelize.INTEGER, // 类型
primaryKey: true, // 主键
autoIncrement: true // 是否允许自增
},
roleName: {
type: Sequelize.STRING(50),
allowNull: false, // 是否允许为空
field: "role_name"
},
}, {
sequelize, // 我们需要传递连接实例
tableName: 'z_role',// 对应的数据库表
timestamps: false
});
UserRole 模型
css
const { Sequelize } = require('sequelize');
const sequelize = require('../database/sequelize');
const User = require("./user")
const Role = require("./role")
const UserRole = sequelize.define('UserRole', {
userId: {
type: Sequelize.INTEGER,
field: "user_id",
references: {
model: User,
key: 'id',
},
},
roleId: {
type: Sequelize.INTEGER,
field: "role_id",
references: {
model: Role,
key: 'id',
},
},
}, {
sequelize, // 我们需要传递连接实例
tableName: 'z_user_role',// 对应的数据库表
timestamps: false
});
references 该属性定义了外键约束的具体表和字段,确保 user_id 和 role_id 字段分别与 z_user 表和 z_role 表的主键字段进行关联。
3.3 定义多对多关系
1.基本版
在 role.js 模型中定义多对多的关联关系
如果 z_user_role 表中外键是 userId 和 roleId,可以使用以下方式指定关联关系:
css
User.belongsToMany(Role, {
through: UserRole // 指定中间表
});
Role.belongsToMany(User, {
through: UserRole // 指定中间表
});
2.自定义外键版
如果 z_user_role 表中外键是 user_id 和 role_id,可以使用指定 foreignKey 和 otherKey 的方式指定关联关系:
css
User.belongsToMany(Role, {
through: UserRole, // 指定中间表
foreignKey: "user_id", // 本模型的外键
otherKey: "role_id", // 目标模型的外键
});
// 'user_id' 在 UserRole 中指向 'z_user' 表的主键(通常是 id)
// 'role_id' 在 UserRole 中指向 'z_role' 表的主键(通常是 id)
Role.belongsToMany(User, {
through: UserRole, // 指定中间表
foreignKey: "role_id", // 本模型的外键
otherKey: "user_id", // 目标模型的外键
});
- through:指定中间表模型,这里是 UserRole。
- foreignKey:表示当前模型用于关联的外键字段。例如,在 User.belongsToMany(Role) 中,foreignKey 是 user_id,表示 User 的 id 在 UserRole 中的字段。
- otherKey:表示目标模型用于关联的外键字段。在 User.belongsToMany(Role) 中,otherKey 是 role_id,表示 Role 的 id 在 UserRole 中的字段。
测试:
css
// 获取用户和角色
// 获取用户和角色
router.get('/getUserAndRoles', async (req, res) => {
try {
const result = await User.findOne({
include: [
{
model: Role,
attributes: ["id", "role_name"], // 只返回需要的字段
through: {
attributes: [], // 不包含中间表字段
},
where: {
status: 0, // 只查询启用的角色
},
},
], where: { id: req.query.id }
});
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
- User.findOne:根据用户的ID查找用户。
- include:包含关联的角色信息。
- model: Role:指定要包含的模型。
- through: { attributes: [] }:指定不包含中间表 UserRole 的任何额外信息。
- attributes: ["id", "role_name"]:只返回角色的指定字段。
- where: { status: 0 }:添加条件,只查找状态为0(启用)的角色。
注:z_role 表这里又额外增加了一个 status 字段
4.完整代码
css
链接: https://pan.baidu.com/s/1RTbgaCmt6vRSqn7ACvrzPg?pwd=6666
提取码: 6666
拿到代码之后记得:
- npm install 安装依赖
- 修改 Sequelize.js 里面连接 MySQL 数据库的账号密码等配置项
- npm app.js 启动后端项目下x