2018年9月19日水曜日

knex.js の transaction でなぜか delete されないときに注目すること。


オフィス狛 技術部 CTOの Taka-yamです。

knex.jsは、Node.jsでActiveRecordを用いてSQLを発行するライブラリです。
fluentにSQLを構築でき、大変便利だなと感じる一方で、
トランザクション中にSELECT分を使用する際にちょっとした落とし穴があり注意が必要です。

例えば、booksというidとnameを保存するシンプルなテーブルがあるとして考えます。
id |  name  |
---+-------------+
 1 |  我輩は猫である|
 2 | こころ       |
 3 | 三四郎       | 

例えば、このテーブルのid1のデータを 削除->存在チェック->登録と実行します。
// knexの定義
const knex = require('../db/knex');

knex.transaction(trx => {
    
    // 全て削除
    knex.from('books').del();

     // 消えているか確認(存在チェック)
    const user = knex.from('books') . where('id', 1).first();

    if (typeof user !== 'undefined') {
        throw new Error(' データがあるよ')
    }

    // 登録
    const books = {id: 1, name: '銀河鉄道の夜'}
    knex.insert(books).into('books');
}

上記の メソッドは存在チェック後のifでエラーを投げます。

なぜか?

これは selectがクロージャ内に渡されているKnex.Transactionインスタンスを使用していない ため、
トランザクションとは別に処理を走らせてしまっているためです。

クロージャ内にいるので一見すると同じトランザクション として扱われるのかなと思ってしまいますが、実際には下記のようにしないといけません。
knex.transaction(trx => {
    
    // 全て削除
    trx.from('books').del();

     // 消えているか確認(存在チェック)
    const user = trx.from('books').where('id', 1).first();

    if (typeof user !== 'undefined') {
        throw new Error(' データがあるよ')
    }

    // 登録
    const books = {id: 1, name: '銀河鉄道の夜'}
    trx.insert(books).into('books');
}

selectに関しては下記のようにも 書けます。
const user = knex.transactioning(trx).from('books') . where('id', 1).first();

例えば処理を共通化して別メソッドとして切り出したりしていると、
思わぬところでトランザクションが途切れてしまっていることがあり、
「???」になることがあります。

また、個人的にですが、Realmなどのライブラリですと、
クロージャ内がトランザクション領域として扱われるので
勘違い して同じように使用してはまってしまったという経緯もありました。

もし同じような現象で困っている方がいらっしゃいましたら、
上記のような可能性も考えてみてください。

,

0 件のコメント:

コメントを投稿