You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Sequelize多对多关系中允许重复记录(图书借阅场景)

图书借阅功能主键重复问题解决

问题描述

实现图书借阅功能时,逻辑如下:

  • 借阅图书时创建记录:
const userBook = await UserBook.create({
  UserId,
  BookId,
  status: 0,
});
  • 归还图书时更新状态:
await userBook.update({ status: 1 });
userBook.save();

初始功能正常,但同一用户再次借阅已归还的图书时,触发主键重复错误:

{
    code: 'ER_DUP_ENTRY',
    errno: 1062,
    sqlState: '23000',
    sqlMessage: "Duplicate entry '1-1' for key 'UserBooks.PRIMARY'",
    sql: 'INSERT INTO `UserBooks` (`status`,`UserId`,`BookId`,`createdAt`,`updatedAt`) VALUES (?,?,?,?,?);',
    parameters: [ 0, '1', '1', '2023-08-26 12:55:03', '2023-08-26 12:55:03' ]
  },
  fields: { PRIMARY: '1-1' },
  sql: 'INSERT INTO `UserBooks` (`status`,`UserId`,`BookId`,`createdAt`,`updatedAt`) VALUES (?,?,?,?,?);'
}

现有模型定义

User模型

module.exports = (sequelize) => {
  const { DataTypes, Model } = require('sequelize');
  class User extends Model {}

  User.init(
    {
      name: {
        type: DataTypes.STRING,
        allowNull: false,
      },
    },
    {
      sequelize,
      timestamps: true,
      deletedAt: true,
      modelName: 'User',
    }
  );
  User.associations = () => {
    User.belongsToMany(sequelize.models.Book, {
      through: { model: sequelize.models.UserBook, unique: false },
      constraints: false,
    });
  };
  return User;
};

Book模型

module.exports = (sequelize) => {
  const { DataTypes, Model } = require('sequelize');
  class Book extends Model {}

  Book.init(
    {
      name: {
        type: DataTypes.STRING,
        allowNull: false,
      },
    },
    {
      sequelize,
      timestamps: true,
      deletedAt: true,
      modelName: 'Book',
    }
  );
  Book.associations = () => {
    Book.belongsToMany(sequelize.models.User, {
      through: { model: sequelize.models.UserBook, unique: false },
      constraints: false,
    });
  };
  return Book;
};

UserBook模型

module.exports = (sequelize) => {
  const { DataTypes, Model } = require('sequelize');
  class UserBook extends Model {}

  UserBook.init(
    {
      status: { type: DataTypes.INTEGER, allowNull: false },
      UserId: { type: DataTypes.INTEGER, allowNull: false },
      BookId: { type: DataTypes.INTEGER, allowNull: false },
    },
    {
      sequelize,
      timestamps: true,
      deletedAt: true,
      modelName: 'UserBook',
    }
  );

  UserBook.associations = () => {
    UserBook.hasOne(sequelize.models.Book, {
      constraints: false,
    });
    UserBook.hasOne(sequelize.models.User, {
      constraints: false,
    });
  };
  return UserBook;
};

期望业务场景

Example Scenerio I want:  |  UserBook Table
userId 1 borrows bookId 1 -> id:1 status:0, userId:1, bookId:1
userId 1 returns bookId 1 -> id:1 status:1, userId:1, bookId:1
userId 1 borrows bookId 1 -> id:2 status:0, userId:1, bookId:1
userId 1 returns bookId 1 -> id:2 status:1, userId:1, bookId:1

尝试过的方案

曾尝试为UserBook的字段添加primaryKey:true,但未解决问题:

UserBook.init(
  {
    status: { type: DataTypes.INTEGER, primaryKey:true, allowNull: false },
    UserId: { type: DataTypes.INTEGER, primaryKey:true, allowNull: false },
    BookId: { type: DataTypes.INTEGER, primaryKey:true, allowNull: false },
  },
  // ...其他配置
);

解决方案

1. 修改UserBook模型,添加自增主键

核心问题是原模型默认将UserId和BookId作为复合主键,导致同一用户与图书只能存在一条记录。添加自增主键id后,每条借阅记录成为独立行:

module.exports = (sequelize) => {
  const { DataTypes, Model } = require('sequelize');
  class UserBook extends Model {}

  UserBook.init(
    {
      id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true,
        allowNull: false
      },
      status: { type: DataTypes.INTEGER, allowNull: false },
      UserId: { type: DataTypes.INTEGER, allowNull: false },
      BookId: { type: DataTypes.INTEGER, allowNull: false },
    },
    {
      sequelize,
      timestamps: true,
      deletedAt: true,
      modelName: 'UserBook',
    }
  );

  // 修正关联关系:UserBook属于User和Book,而非hasOne
  UserBook.associations = () => {
    UserBook.belongsTo(sequelize.models.Book, { foreignKey: 'BookId', constraints: false });
    UserBook.belongsTo(sequelize.models.User, { foreignKey: 'UserId', constraints: false });
  };
  return UserBook;
};

2. 同步数据库结构

如果数据库已存在UserBooks表,需要同步模型变更:

  • 开发环境可使用自动同步(谨慎用于生产):
await sequelize.sync({ alter: true });
  • 生产环境建议使用迁移脚本:
npx sequelize-cli migration:generate --name add-primary-key-to-userbook

然后编写迁移文件修改表结构,添加自增id主键。

3. 保留原有业务逻辑

借阅和归还的代码无需修改,新增的自增主键会自动生成,同一用户多次借阅同一本书时会创建新的记录,满足业务需求。

内容的提问来源于stack exchange,提问作者cezikmertcan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 14:34:55