【问题标题】:Transaction for prepared statements using Node mssql使用 Node mssql 的预处理语句的事务
【发布时间】:2019-12-31 18:55:05
【问题描述】:

我想创建一个 Express REST API,并将 MSSQL 用于数据库部分。我使用mssql 模块并使用PreparedStatement 类。

有时我的查询必须是事务性的,所以我也必须使用 Transaction 类。

我找到了一些关于预处理语句here 和事务here 的信息,但不知道如何使用预处理语句执行事务查询。

有人介意解释如何在事务中使用带有准备好的语句的 mssql 模块吗?我的查询函数应该提供一种功能,可以在我的查询文件中写入类似这样的内容

  • 开始交易
  • 运行准备好的语句一
  • 运行准备好的语句二
  • 结束交易

所以要重现问题: 首先,我创建了一个数据库,其中包含一个名为“integer”的表和一个数字列“value”。

然后我用这段代码创建了一个空的 Node/Typescript 项目:

import sql, { ConnectionPool, PreparedStatement } from 'mssql';

class App {
    private connectionPool: ConnectionPool;

    constructor() {
        this.connectionPool = new sql.ConnectionPool(databaseConfig);
    }

    public run = async (): Promise<void> => {
        try {
            await this.connectionPool.connect();

            const preparedStatement: PreparedStatement = new sql.PreparedStatement(this.connectionPool);

            preparedStatement.input('number', sql.Numeric(18, 0));

            await preparedStatement.prepare('INSERT INTO integer (value) VALUES (@number)');
            await preparedStatement.execute({ number: 1 });
            await preparedStatement.unprepare();

            console.log('done...');
        } catch (error) {
            throw error;
        }
    }
}

(async () => {
    const app: App = new App();
    await app.run();
})().catch(error => {
    throw error;
});

到目前为止一切顺利。现在我想在一个事务中运行两个 INSERT,但第二个传入一个字符串,所以我预计会出现错误,第一个会回滚。我更新的run函数:

public run = async (): Promise<void> => {
    try {
        await this.connectionPool.connect();

        const transaction: Transaction = new sql.Transaction(this.connectionPool);

        const workingStatement: PreparedStatement = new sql.PreparedStatement(transaction);
        workingStatement.input('number', sql.Numeric(18, 0));

        const invalidStatement: PreparedStatement = new sql.PreparedStatement(transaction);
        invalidStatement.input('number', sql.Numeric(18, 0));

        try {
            await transaction.begin();

            await workingStatement.prepare('INSERT INTO integer (value) VALUES (@number)');
            await workingStatement.execute({ number: 1 });
            await workingStatement.unprepare();

            await invalidStatement.prepare('INSERT INTO integer (value) VALUES (@number)');
            await invalidStatement.execute({ number: false });
            await invalidStatement.unprepare();

            await transaction.commit();
        } catch (error) {
            await transaction.rollback();
            console.log('execution failed...');
        }

        console.log('done...');
    } catch (error) {
        throw error;
    }
}

现在我收到此错误

(node:18416) UnhandledPromiseRejectionWarning: TransactionError: Can't 回滚事务。有一个请求正在进行中。

而且我不确定我的语法是否错误。

【问题讨论】:

  • 您发布的链接显示“提示:您还可以在事务中创建准备好的语句(新 sql.PreparedStatement(transaction)),但请记住您不能在事务中执行其他请求直到你打电话给 unprepare。”,这似乎很清楚。您在让它工作或将其集成到您的 IDatabase 设计中时遇到问题吗?
  • 谢谢@Nickolay。是的,我需要帮助进行设置。我知道我可以将事务传递给准备好的语句,但是我将如何链接我的准备好的语句执行?您介意提供一个包含带有多个预准备语句的事务的示例代码吗?
  • FWIW 我仍然不确定我是否理解您的问题,特别是考虑到问题的最新编辑 - 我认为您可以简单地将 transaction 对象代替 this.connectionPool 传递给PreparedStatement。也许分享使用无效交易的尝试会带来一些清晰。
  • @Nickolay 我希望这次编辑能解决我的问题 :)

标签: javascript node.js sql-server typescript node-mssql


【解决方案1】:

我相信您遇到的错误是因为您没有按照文档中的建议调用 unprepare

请记住,在调用 unprepare 之前,您无法执行事务中的其他请求

在回滚事务之前在语句上调用 unprepare 对我有用:

try {
  // ...
  await invalidStatement.prepare('INSERT INTO integer (value) VALUES (@number)');
  try {
    await invalidStatement.execute({ number: false });
  } finally {
    await invalidStatement.unprepare();
  }
} catch (error) {
  // rollback here...
}

【讨论】:

  • 感谢您的回复。这对我有用:) 这是我更新的run 函数,你认为这是正确的吗?我认为trycatch 的开销很大,不是吗? pastebin.com/FBDTqNY2
  • 之后我可以奖励你赏金:)
  • 这就是我测试过的,对我有用的。我不喜欢强制为每个事务初始化(然后处理)准备好的语句的设计,但这似乎是这个库中连接池工作方式的副作用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多