
Давайте сначала разберемся, что такое транзакция и когда необходимо использовать транзакцию?
Когда я работал над одним из своих проектов, я заметил при операции обновления, которая обновляла несколько таблиц, иногда операция завершалась ошибкой, но половина таблиц все равно обновлялась, потому что я выполнял операции промисов параллельно. Теперь, если ваша операция включает единственную таблицу, будет достаточно простой операции try-catch-async-await.
Но все по-другому, когда вы обновляете несколько таблиц за одну операцию.
Давайте углубимся и поймем, как настроить транзакции с помощью Knex.js.
Отдельные транзакции
Я определяю метод transaction, который не привязан ни к какой модели. Собственный метод Knex transaction не поддерживает «цепочку», поэтому мы определяем новый метод.
checkIsProjectTransaction(mayBeTrx) {
return Boolean(mayBeTrx && mayBeTrx.executionPromise);
}
// Transaction Unified Logic
transaction(trxOrCallback, mayBeCallback) {
if(!this.db.engine) {
throw new Error('Transaction is not initialized');
}
if(mayBeCallback === undefined) {
if(typeof trxOrCallback !== 'function') {
throw new Error('Invalid Transaction call');
}
return this.db.engine.transaction(trxOrCallback);
}
if(this.checkIsProjectTransaction(trxOrCallback) {
return mayBeCallback(trxOrCallback);
}
return this.db.engine.transaction(mayBeCallback);
}
Приведенный выше код инициализирует объект транзакции, который впоследствии можно использовать в вашем приложении для операций обновления/сохранения. Лучшим подходом будет сохранение вышеуказанной логики внутри службы, а затем использование экземпляра службы для использования объекта транзакции (trx).
Одна вещь, на которую следует обратить внимание при использовании транзакции Knex, заключается в том, что мы должны передавать объект транзакции в каждом запросе, который взаимодействует с операцией БД.
А теперь самое интересное, давайте посмотрим, как мы можем использовать вышеуказанный объект транзакции и как его использовать.

Для простоты я выполняю операцию сохранения/обновления только с использованием транзакции, но вы можете поместить любое количество операций БД в транзакцию. Просто помните, что если одна из операций завершается неудачей, вся операция откатывается.
yourFunctionName(params, idFieldName) {
try{
await this.transaction(async(trx) => {
// Very important to pass trx object
/* Check to see if the value exists then do an update else create a new entry */
if(!isNew) {
await trx.update(params).into(this.tableName).where(idFieldName, params.id);
logger.info('Update Operation Successful');
} else {
await trx.insert(params).into(this.tableName);
logger.info('Insert Operation Successful');
}
}).then(() => {
logger.info('Transaction Complete');
});
}catch((err) => { // Catch Error if something fails
logger.error(err);
});
В приведенной выше операции, если какая-либо из операций завершается сбоем, вся транзакция откатывается к исходному состоянию.
Просто чтобы дать вам представление о том, что за кулисами (вам не нужно выполнять какие-либо настройки в кодовой базе ниже, логика предназначена только для дополнительных знаний), вся операция фиксации и отката происходит в транзакции. js, который Knex предоставляет нам из коробки. Внутри файла все операции происходят внутри контейнера, который имеет две основные операции.
- совершить
- откат
После того, как все операции выполнены успешно и результат имеет тип «функция», выполняется еще откат.
async _evaluateContainer(config,container) {
// logic for acquiring connection to DB
// logic for forming transactor
/* If we've returned a "thenable" from the trasnaction container, assume the rollback and commit are chained to this object's success / failure. Directly thrown errors are treated as automatic rollbacks. */
let result;
try{
result = container(transactor);
} catch(err) {
result = Promise.reject(err);
}
if(result && result.then && typeof result.then === 'function'){
result.then((val) => {
return transactor.commit(val);
})
.catch((err) => {
return transactor.rollback(err);
});
}
Не забывайте trx
Все эти методы позволяют четко управлять транзакциями внутри приложения. С другой стороны, они не гарантируют, что trx не будет забыто.
Как я уже сказал, когда мы забываем trx, мы обязательно будем использовать как минимум два подключения к базе данных: одно для транзакции и одно для запроса, забытого вне транзакции. Имейте эти настройки в своем проекте:
const knex = require('knex')({
client: 'postgres',
connection: {
host: '127.0.0.1',
user: 'your_database_user',
password: 'your_database_password',
database: 'myapp_test',
},
pool: { min: 1, max: 1 },
})
Приведенный выше параметр конфигурации Knex также поможет вам выполнить миграцию в базе данных.
Помимо этого, обязательно запустите свои модульные тесты, это отличный способ обнаружить упущения. Если вы забудете, тест не закончится и попадет в тайм-аут.
Я надеюсь, что это поможет, благодаря этим методам Knex наша команда теперь может без проблем обновлять нашу БД с помощью больших объектов. Надеюсь это поможет.