手把手教你彻底学会Sequelize表关联关系!少走弯路!

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 表中外键是 userIdroleId,可以使用以下方式指定关联关系:

css 复制代码
User.belongsToMany(Role, {
  through: UserRole        // 指定中间表
});

Role.belongsToMany(User, {
  through: UserRole    // 指定中间表
});

2.自定义外键版

如果 z_user_role 表中外键是 user_idrole_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
相关推荐
bingbingyihao几秒前
SpringBoot教程(vuepress版)
java·spring boot·后端
盛夏绽放1 分钟前
Vue3 + Node.js 实现客服实时聊天系统(WebSocket + Socket.IO 详解)
websocket·网络协议·node.js
一切皆有迹可循38 分钟前
Spring Boot 基于 CAS 实现单点登录:原理、实践与优化全解析
java·spring boot·后端
Kookoos1 小时前
从单体到微服务:基于 ABP vNext 模块化设计的演进之路
后端·微服务·云原生·架构·c#·.net
layman05282 小时前
node.js 实战——express图片保存到本地或服务器(七牛云、腾讯云、阿里云)
node.js·express
weixin_438335403 小时前
springboot使用阿里云OSS实现文件上传
spring boot·后端·阿里云
m0_zj3 小时前
58.[前端开发-前端工程化]Day05-webpack-Git安装-配置-Git命令
前端·webpack·node.js
Attacking-Coder3 小时前
前端面试宝典---JavaScript import 与 Node.js require 的区别
前端·javascript·node.js
大宁宁吖4 小时前
使用node.js创建一个简单的服务器
node.js
咸鱼睡不醒_5 小时前
SpringBoot项目接入DeepSeek
java·spring boot·后端