Node.js SQL Server: транзакция

В этом руководстве вы узнаете, как выполнить транзакцию SQL Server с помощью приложения Node.js.

Это руководство начинается с того места, где закончилось руководство по удалению данных из таблицы с помощью Node.js.

Настройка выборочных данных

Шаг 1. Запустите SQL Server Management Studio(SSMS) для подключения к базе данных BookStore.

Шаг 2. Выполните следующие операторы для вставки данных в таблицы Books, BookAuthors, Customers и Inventories:

-- Insert books into the Books table
INSERT INTO Books(Title, Publisher, ISBN, PublishedDate)
VALUES('The Adventures of Tom Sawyer', 'American Publishing Company', '978-0-123456-47-2', '1876-01-01'),
      ('The Adventures of Huckleberry Finn', 'Chatto & Windus', '978-0-123456-48-9', '1884-12-10'),
      ('A Connecticut Yankee in King Arthur''s Court', 'Charles L. Webster And Company', '978-0-123456-49-6', '1889-04-10'),
      ('The Prince and the Pauper', 'James R. Osgood & Co.', '978-0-123456-50-2', '1881-12-01'),
      ('Life on the Mississippi', 'James R. Osgood & Co.', '978-0-123456-51-9', '1883-02-01');
-- Get the generated BookID values for the inserted books
DECLARE @BookID1 INT, @BookID2 INT, @BookID3 INT, @BookID4 INT, @BookID5 INT;
SELECT @BookID1 = BookID FROM Books WHERE ISBN = '978-0-123456-47-2';
SELECT @BookID2 = BookID FROM Books WHERE ISBN = '978-0-123456-48-9';
SELECT @BookID3 = BookID FROM Books WHERE ISBN = '978-0-123456-49-6';
SELECT @BookID4 = BookID FROM Books WHERE ISBN = '978-0-123456-50-2';
SELECT @BookID5 = BookID FROM Books WHERE ISBN = '978-0-123456-51-9';
-- Insert records into the BookAuthors table
INSERT INTO BookAuthors(BookID, AuthorID)
VALUES(@BookID1, 6),
      (@BookID2, 6),
      (@BookID3, 6),
      (@BookID4, 6),
      (@BookID5, 6);
-- Insert inventories
INSERT INTO Inventories(BookID, Qty)
SELECT BookID, ABS(CHECKSUM(NEWID()) % 101) + 100
FROM Books;

Выполнить транзакцию

Шаг 1. Создайте новый файл с именем transaction.js в каталоге проекта.

Шаг 2. Определите функцию createOrder в модуле transaction.js, которая выполняет следующие шаги:

  • Сравните инвентарь с объемом заказа в таблице «Запасы».
  • Создайте заказ, вставив данные в таблицу Orders и OrderDetails.
  • Уменьшите инвентарь книги в таблице «Инвентарь».
import sql from 'mssql';
import { config } from './config.js';
async function createOrder(customerId, bookId, quantity, price, orderDate) {
  try {
    // Connect to the database
    const pool = await sql.connect(config);
    // Begin transaction
    const transaction = new sql.Transaction(pool);
    await transaction.begin();
    try {
      // Check inventory
      const inventoryResult = await transaction
        .request()
        .input('BookID', sql.Int, bookId).query(`
          SELECT Qty
          FROM Inventories
          WHERE BookID = @BookID
        `);
      const currentQty = inventoryResult.recordset[0]?.Qty;
      if(!currentQty || currentQty < quantity) {
        throw new Error('Not enough inventory to fulfill the order');
      }
      // Insert into Orders table
      const orderResult = await transaction
        .request()
        .input('CustomerID', sql.Int, customerId)
        .input('OrderDate', sql.DateTime, orderDate)
        .input('TotalAmount', sql.Decimal(10, 2), price * quantity).query(`
          INSERT INTO Orders(CustomerID, OrderDate, TotalAmount)
          OUTPUT INSERTED.OrderID
          VALUES(@CustomerID, @OrderDate, @TotalAmount)
        `);
      const orderId = orderResult.recordset[0].OrderID;
      // Insert into OrderDetails table
      await transaction
        .request()
        .input('OrderID', sql.Int, orderId)
        .input('BookID', sql.Int, bookId)
        .input('Quantity', sql.Int, quantity)
        .input('Price', sql.Decimal(10, 2), price).query(`
          INSERT INTO OrderDetails(OrderID, BookID, Quantity, Price)
          VALUES(@OrderID, @BookID, @Quantity, @Price)
        `);
      // Update Inventories table
      await transaction
        .request()
        .input('BookID', sql.Int, bookId)
        .input('Quantity', sql.Int, quantity).query(`
          UPDATE Inventories
          SET Qty = Qty - @Quantity
          WHERE BookID = @BookID
        `);
      // Commit transaction
      await transaction.commit();
      console.log('Order created successfully');
    } catch(err) {
      // Rollback transaction on error
      await transaction.rollback();
      throw err;
    }
  } catch(err) {
    console.error('Error creating order:', err);
  }
}
export { createOrder };

Как это работает.

Сначала импортируйте объект SQL из пакета mssql и объект конфигурации из модуля config.js:

import sql from 'mssql';
import { config } from './config.js';

Во-вторых, определите функцию createOrder(), которая создает заказ для клиента на основе заказанной книги, указанной по идентификатору книги, количеству книг, цене и дате заказа.

async function createOrder(customerId, bookId, quantity, price, orderDate) {
    //...
};

В-третьих, подключитесь к базе данных, используя предоставленный объект конфигурации:

const pool = await sql.connect(config);

В-четвертых, начните транзакцию, создав новый объект Transaction и вызвав метод begin().

const transaction = new sql.Transaction(pool);
await transaction.begin();

В-пятых, проверьте инвентарь по идентификатору книги и верните количество книг в инвентаре:

const inventoryResult = await transaction
  .request()
  .input('BookID', sql.Int, bookId).query(`
          SELECT Qty
          FROM Inventories
          WHERE BookID = @BookID
        `);

В-шестых, если количество запасов меньше количества заказа, то выдается ошибка:

const currentQty = inventoryResult.recordset[0]?.Qty;
if(!currentQty || currentQty < quantity) {
  throw new Error('Not enough inventory to fulfill the order');
}

В-седьмых, вставьте новую строку в таблицу «Заказы»:

const orderResult = await transaction
  .request()
  .input('CustomerID', sql.Int, customerId)
  .input('OrderDate', sql.DateTime, orderDate)
  .input('TotalAmount', sql.Decimal(10, 2), price * quantity).query(`
          INSERT INTO Orders(CustomerID, OrderDate, TotalAmount)
          OUTPUT INSERTED.OrderID
          VALUES(@CustomerID, @OrderDate, @TotalAmount)
        `);

Восьмое, получите вставленный идентификатор заказа:

const orderId = orderResult.recordset[0].OrderID;

Девятое, вставьте новую строку в таблицу OrderDetails:

await transaction
  .request()
  .input('OrderID', sql.Int, orderId)
  .input('BookID', sql.Int, bookId)
  .input('Quantity', sql.Int, quantity)
  .input('Price', sql.Decimal(10, 2), price).query(`
          INSERT INTO OrderDetails(OrderID, BookID, Quantity, Price)
          VALUES(@OrderID, @BookID, @Quantity, @Price)
        `);

В-десятых, обновите таблицу «Запасы», уменьшив количество запасов на величину заказа:

await transaction
  .request()
  .input('BookID', sql.Int, bookId)
  .input('Quantity', sql.Int, quantity).query(`
          UPDATE Inventories
          SET Qty = Qty - @Quantity
          WHERE BookID = @BookID
        `);

В-одиннадцатых, зафиксируйте транзакцию, вызвав метод commit() объекта транзакции:

await transaction.commit();

Двенадцатое, если во время транзакции произошла какая-либо ошибка, откатите ее, вызвав метод rollback() объекта Transaction:

await transaction.rollback();

Дополнительно выдается ошибка:

throw err;

Шаг 3. Измените файл index.js, чтобы использовать функцию createOrder:

import { createOrder } from './transaction.js';
try {
  await createOrder(1, 1, 5, 9.99, new Date(2024, 6, 16));
} catch(err) {
  console.log(err);
}

Как это работает.

Сначала импортируйте функцию createOrder из модуля transaction.js:

import { createOrder } from './transaction.js';

Во-вторых, создайте новый заказ, вызвав функцию createOrder:

try {
  await createOrder(1, 1, 500, 9.99, new Date(2024, 6, 16));
} catch(err) {
  console.log(err);
}

Шаг 4. Откройте терминал и выполните следующую команду для выполнения файла index.js:

npm start

Загрузите исходный код проекта

Загрузите исходный код проекта

Краткое содержание

  • Вызовите метод transaction.begin(), чтобы начать транзакцию.
  • Зафиксируйте транзакцию, вызвав метод transaction.commit().
  • Откатите транзакцию, вызвав метод transaction.rollback().
Мирослав С.

Автор статей, ИБ-специалист