準備: 動作確認用テーブル
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);
});