tech.chakapoko.com
Home / Node.js / MySQL

[Node.js][MySQL]Promiseを使ってトランザクションを書きやすくする

準備: 動作確認用テーブル

CREATE TABLE `posts` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `content` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `logs` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `message` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

トランザクションの利用

mysql モジュールにはトランザクションの機能もありますが、実際に利用するとコールバックのネストが多く、読みづらいコードになってしまいます。

const mysql = require('mysql');

const connection = mysql.createConnection({
    host: 'localhost',
    database: 'my_database',
    user: 'me',
    password: 'my_password',
});

connection.beginTransaction((err) => {
  if (err) { throw err; }

  connection.query('INSERT INTO posts (content) VALUES (?)', ['Hello!'], (error, results, fields) => {
    if (error) {
      return connection.rollback(() => {
        throw error;
      });
    }

    var log = 'Post ' + results.insertId + ' added';

    connection.query('INSERT INTO logs (message) VALUES (?)', log, (error, results, fields) => {
      if (error) {
        return connection.rollback(() => {
          throw error;
        });
      }
      connection.commit((err) => {
        if (err) {
          return connection.rollback(() => {
            throw err;
          });
        }
        console.log('success!');
      });
    });
  });
});

このコードの見通しをPromiseを使って改善してみます。

Promiseを返すラッパー関数を作る

mysqlPromise.js

まず mysql モジュールの関数をPromiseでラップします。

const beginTransaction = (connection) => {
  return new Promise((resolve, reject) => {
    connection.beginTransaction((err) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    })
  });
}

const query = (connection, statement, params) => {
  return new Promise((resolve, reject) => {
    connection.query(statement, params, (err, results, fields) => {
      if (err) {
        reject(err);
      } else {
        resolve(results, fields);
      }
    });
  });
}

const commit = (connection) => {
  return new Promise((resolve, reject) => {
    connection.commit((err) => {
      if (err) {
        reject(err);
      } else {
        resolve(err);
      }
    });
  });
};

const rollback = (connection, err) => {
  return new Promise((resolve, reject) => {
    connection.rollback(() => {
      reject(err);
    });
  });
};

module.exports = {
  beginTransaction: beginTransaction,
  query: query,
  commit: commit,
  rollback: rollback,
};

rollback は必ずrejectして、失敗の原因になったエラーを返すようにしました。

作ったラッパーを利用する

作ったラッパーは次のように利用できます。今回はasync/awaitを使ってみました。

app.js

const mysql = require('mysql');
const mysqlPromise = require('./mysqlPromise');

const connection = mysql.createConnection({
    host: 'localhost',
    database: 'my_database',
    user: 'me',
    password: 'my_password',
});

(async () => {
  try {
    await mysqlPromise.beginTransaction(connection);
    const results = await mysqlPromise.query(connection, 'INSERT INTO posts (content) VALUES (?)', ['Hello!']);
    var log = 'Post ' + results.insertId + ' added';
    await mysqlPromise.query(connection, 'INSERT INTO logs (message) VALUES (?)', log);
    await mysqlPromise.commit(connection);
  } catch (err) {
    await mysqlPromise.rollback(connection, err);
  } finally {
    connection.end();
  }
})().catch((err) => {
  console.error(err);
});