手把手教你彻底学会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
相关推荐
uhakadotcom3 分钟前
Apache APISIX 简介与实践
后端·面试·github
Asthenia04125 分钟前
面试官问“epoll的原理”,我该怎么回答?
后端
uhakadotcom5 分钟前
Kong Gateway 简介与实践
后端·面试·github
潘多编程13 分钟前
Spring Boot分布式项目实战:装饰模式的正确打开方式
spring boot·分布式·后端
uhakadotcom15 分钟前
Apache SkyWalking:分布式系统的可观测性平台
后端·面试·github
uhakadotcom19 分钟前
RocketMQ:解密阿里巴巴都在用的高性能消息队列
后端·面试·github
uhakadotcom19 分钟前
Apache Beam:统一的大数据处理模型
后端·面试·github
uhakadotcom20 分钟前
Apache Superset:现代化数据分析与可视化平台
后端·面试·github
Lvan22 分钟前
程序员必看:两个思想优化90%的代码
java·后端
失业写写八股文23 分钟前
分布式事务深度解析:从理论到实践
分布式·后端·微服务