Sequelize save/create 方法仅在第一次对 CRUD 应用程序有效

Sequelize save/create method only works at first time on CRUD app

一般问题:
我一直很难尝试使用 JavaScript + node JS + express + sequelize (MySQL).

构建一个简单的 CRUD 应用程序

关于项目的情境化
我正在开发我的 CRUD 应用程序来管理来自特定英语老师的学生。
创建了一些 table 模型:Alunos - 英语:Students、Boletos - 英语:Pay Order、Aulas - 英文:类,其他暂不强制说明此问题

具体问题:
有一个 post 路由获取我的一些 body 内容并将新学生插入到 table“Alunos”。创建包含学生数据的行后,我需要为“Boletos”创建注册表,它将是 12 个月的支付订单注册到此 table。这部分有两个问题:首先我注册了一个学生,它工作正常,但是我无法获得模型生成的 auto-increment id 插入外键“AlunoId”,因此“Boletos”table 的外键设置为空。 另一个问题是这两个条目(1 个进入“Alunos”和 12 个进入“Boletos”)起初工作正常,注册第一个学生, 但是在页面刷新后我尝试注册另一个学生,节点 JS 抛出错误:

(node:5720) UnhandledPromiseRejectionWarning: SequelizeUniqueConstraintError: Validation error
    at Query.formatError (D:\ProgramacaoEstudos\ProjetoCRUD2\node_modules\sequelize\lib\dialects\mysql\query.js:242:16)
    at Query.run (D:\ProgramacaoEstudos\ProjetoCRUD2\node_modules\sequelize\lib\dialects\mysql\query.js:77:18)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async D:\ProgramacaoEstudos\ProjetoCRUD2\node_modules\sequelize\lib\sequelize.js:619:16
    at async MySQLQueryInterface.insert (D:\ProgramacaoEstudos\ProjetoCRUD2\node_modules\sequelize\lib\dialects\abstract\query-interface.js:749:21)
    at async model.save (D:\ProgramacaoEstudos\ProjetoCRUD2\node_modules\sequelize\lib\model.js:3954:35)
    at async D:\ProgramacaoEstudos\ProjetoCRUD2\routes\admin.js:101:30
(node:5720) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)

代码:

  1. 模型 - Alunos:
// Create Aluno table model and export to be called to other file
module.exports = (sequelize, DataTypes) => {
  const Aluno = sequelize.define('Aluno', {
    name: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    surname: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    birth: {
      type: DataTypes.DATEONLY,
      allowNull: false,
    },
    phone_number: {
      type: DataTypes.STRING,
      allowNull: true,
      unique: true,
    },
    mobile_number: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    residential_address: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    profession: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    company: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    num_classes_week: {
      type: DataTypes.INTEGER,
      allowNull: false,
    },
    classes_day: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    classes_time: {
      type: DataTypes.TIME,
      allowNull: false,
    },
    starts_at: {
      type: DataTypes.DATEONLY,
      allowNull: false,
    },
    value: {
      type: DataTypes.FLOAT,
      allowNull: false,
    },
    discount: {
      type: DataTypes.FLOAT,
      defaultValue: 0,
    },
    due_date: {
      type: DataTypes.DATEONLY,
      allowNull: false,
    },
  });

  Aluno.associate = (models) => {
    Aluno.hasMany(models.Aula, {
      onDelete: 'CASCADE',
    });
  };

  Aluno.associate = (models) => {
    Aluno.hasMany(models.Boleto, {
      onDelete: 'NO ACTION',
    });
  };

  Aluno.associate = (models) => {
    Aluno.hasMany(models.Assiste_Aula, {
      onDelete: 'NO ACTION',
    });
  };

  return Aluno;
};

  1. 模特 - Boletos:
module.exports = (sequelize, DataTypes) => {
    const Boleto = sequelize.define('Boleto', {
        value: {
            type: DataTypes.FLOAT,
            allowNull: false
        },
        due_date: {
            type: DataTypes.DATEONLY,
            allowNull: false
        },
        status: {
            type: DataTypes.INTEGER,
            defaultValue: 0,
            allowNull: false
        }
    })

    Boleto.associate = (models) => {
        Boleto.belongsTo(models.Aluno, {
            foreignKey: 'AlunoId'  
        })
    }

    return Boleto
}

路由 - Post - 注册表:

// Post Routes
router.post('/realizado', async (req, res) => {
  // Create Aluno
  // Compute values of some attributes
  var cls_day = req.body.classes_day;
  var num_cls = cls_day.length;

  var dias;
  for (let i = 0; i < num_cls; i++) {
    if (i + 1 < num_cls) {
      dias += cls_day[i] + '-';
    } else {
      dias += cls_day[i];
    }
  }

  // Instantiate Aluno model
  const aluno = db.Aluno.build({
    name: req.body.name,
    surname: req.body.surname,
    birth: req.body.birth,
    phone_number: req.body.phone_number,
    mobile_number: req.body.mobile_number,
    email: req.body.email,
    residential_address: req.body.residential_address,
    profession: req.body.profession,
    company: req.body.company,
    num_classes_week: num_cls,
    classes_day: dias,
    classes_time: req.body.classes_time,
    starts_at: req.body.starts_at,
    value: req.body.value,
    discount: req.body.discount,
    due_date: req.body.due_date,
  });

  // Insert into database
  const newAluno = await aluno.save();

  // Create boleto
  var objList = [];
  for (var i = 0; i < 12; i++) {
    var firstDt = new Date(req.body.due_date);

    // Compute current date
    var dt = firstDt.setMonth(firstDt.getMonth() + i);
    //dt.setDate(dt.getMonth() + i)
    // Build boleto object
    var boleto;
    boleto = db.Boleto.build({
      value: req.body.value,
      due_date: dt,
      alunoId: newAluno.id,
    });

    objList.push(boleto);
  }

  for (var i = 0; i < objList.length; i++) {
    try {
      await objList[i].save();
    } catch (err) {
      console.log(err);
    }
  }
  res.render('realizado');
});

最后的考虑: 由于我是 node JS 的新手,JavaScript 我不知道 Promises 和语法糖 await/async。我已经学习了很多视频,我想我已经掌握了它的基本概念,但我无法将它应用到项目中。

您需要使用 db.Aluno.create() 或设置 db.Aluno.build({...}, { isNewRecord: true }) 让 Sequelize 知道这是一个插入而不是主键值为 0 的记录。您的数据库可能会看到 ID 0 和插入它或将其设置为 1,无论哪种方式,您都会在第二个插入时发生冲突。

将您的 router/controller 代码包装在 try/catch 中以处理任何错误也是一个好主意。使用传递给所有 queries/inserts 的事务,以便在任何阶段出现错误时都可以将它们全部回滚 - const transaction = await sequelize.transaction(); const aluno = await db.Aluno.create({...}, { transaction });.在最后使用 await transaction.commit() 提交或在 catch 块中使用 await transaction.rollback().

回滚

不要在 for 循环中使用 await - 这与每次调用都阻塞一样,效率低且速度慢。相反,您可以将一系列承诺传递给 Promise.all() 并同时解决它们.... objList.push(boleto.save()); (不要在这里等待)然后 await Promise.all(objList);.

一些最后的说明 - 当变量不会改变时使用 const 是很好的,而当它可能使你的结果更一致时使用 let 是很好的。您还应该尝试从箭头函数中获得明确的 return。

这是应用了更改的代码。

// Post Routes
router.post('/realizado', async (req, res) => {
  // we may or may not be able to create a transaction, so use let
  let transaction;
  try {
    // start a new transaction an pass to all the create() calls
    transaction = await sequelize.transaction();

    // Compute values of some attributes
    var cls_day = req.body.classes_day;
    var num_cls = cls_day.length;

    var dias;
    for (let i = 0; i < num_cls; i++) {
      if (i + 1 < num_cls) {
        dias += cls_day[i] + '-';
      } else {
        dias += cls_day[i];
      }
    }

    // Create Aluno model, use const since it won't change
    const aluno = await db.Aluno.create(
      {
        name: req.body.name,
        surname: req.body.surname,
        birth: req.body.birth,
        phone_number: req.body.phone_number,
        mobile_number: req.body.mobile_number,
        email: req.body.email,
        residential_address: req.body.residential_address,
        profession: req.body.profession,
        company: req.body.company,
        num_classes_week: num_cls,
        classes_day: dias,
        classes_time: req.body.classes_time,
        starts_at: req.body.starts_at,
        value: req.body.value,
        discount: req.body.discount,
        due_date: req.body.due_date,
      },
      {
        // use the transaction
        transaction,
      }
    );

    // Create boleto insert promise array
    // Use const because we will be adding items, but into the same const array
    const promises = [];
    for (let i = 0; i < 12; i++) {
      const firstDt = new Date(req.body.due_date);
      // put the promise into the promises array
      promises.push(
        // the create call here will start the insert but not wait for it to complete
        db.Boleto.create(
          {
            value: req.body.value,
            due_date: firstDt.setMonth(firstDt.getMonth() + i),
            alunoId: aluno.id,
          },
          {
            // use the transaction so we can rollback if there are errors
            transaction,
          }
        )
      );
    }

    // await the result of all the boleto inserts
    await Promise.all(promises);

    // no errors, we can commit the transaction
    await transaction.commit();

    return res.render('realizado');
  } catch (err) {
    console.log(err);
    if (transaction) {
      await transaction.rollback;
    }
    return res.status(500).send(err.message);
  }
});