Node.jsからMySQLにアクセスする方法を調べようと思ったのですが、最初に見つけたのがこちら。
これが、標準的位置づけなライブラリのようですね。
※MySQL 8.0で登場している、Connector/Node.jsはいったん置いておきます
Connector/Node.js
ですが、これだとちょっと辛いので、Promiseを使えるようにしたいなーと思って少し調べたら、そのラッパーがあるようです。
Bluebirdを使って、Promiseを使えるようにしてあるみたいですね。
Promise-mysql is a wrapper for mysqljs/mysql that wraps function calls with Bluebird promises. Usually this would be done with Bluebird's .promisifyAll() method, but mysqljs/mysql's footprint is different to that of what Bluebird expects.
https://www.npmjs.com/package/promise-mysql
このPromise-mysqlを使って、MySQLにアクセスしてみます。
準備
まずは、ライブラリのインストール。
Promise-mysql。
$ npm i --save promise-mysql
テストライブラリには、Jestを使用することにしました。
$ npm i --save-dev jest
package.jsonのscript要素。
"scripts": { "test": "jest --detectOpenHandles" },
package.jsonで、JestのtestEnvironmentには「node」を指定。
"jest": { "testEnvironment": "node" }
MySQL側ですが、テーブル定義はこんな感じで。お題は、書籍とします。
mysql> create table book(isbn varchar(15), title varchar(100), price int, primary key(isbn));
また、現時点のmysqlライブラリではデフォルトの認証形式が変わってしまったため、特になにも指定していないとこんな感じで怒られてしまいます。
ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
これを回避するには、my.cnfに「default_authentication_plugin」と「mysql_native_password」と指定するか
default_authentication_plugin = mysql_native_password
ユーザー作成時に以下のように「mysql_native_password」を追加で加えます。
mysql> CREATE USER [username] IDENTIFIED WITH mysql_native_password BY '[password]';
参考)
日々の覚書: MySQL 8.0.4におけるデフォルト認証形式の変更
ここまでで、準備は完了です。
使ってみる
まずは簡単に、
- MySQLに接続
- データをINSERT
- データをSELECT
- 接続終了
という流れを書いてみます。
require。
const mysql = require('promise-mysql');
作成したコードは、こちら。
test('insert and select', done => { let connection; mysql.createConnection({ host: '172.17.0.2', port: 3306, user: 'kazuhira', password: 'password', database: 'practice' }).then(conn => { connection = conn; return conn; }).then(() => { const sql = 'insert into book(isbn, title, price) values(?, ?, ?)'; const data =['978-4798147406', '詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド', 3888]; return connection.query(sql, data); }).then(() => { const sql = 'select isbn, title, price from book where isbn = ?'; const data = ['978-4798147406']; return connection.query(sql, data); }).then(rows => { expect(rows).toHaveLength(1); expect(rows[0]['isbn']).toEqual('978-4798147406'); expect(rows[0]['title']).toEqual('詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド'); expect(rows[0]['price']).toEqual(3888); done(); return Promise.resolve(); }).catch(error => { console.log(error); done(); return Promise.resolve(); }).finally(() => { connection.end(); }); });
mysql#createConnectionで接続、あとは取得したConnectionに対してqueryでSQLを投げたり、endで接続終了、という感じになります。
APIそのものは、元のmysqlライブラリを見ることになるようです。
「?」に対して値をバインドさせることも可能なように見えますが、
const sql = 'insert into book(isbn, title, price) values(?, ?, ?)'; const data =['978-4798147406', '詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド', 3888]; return connection.query(sql, data);
これはどうやらエスケープのようです。
Escaping query values
予約後をエスケープする場合は、こちら。
Escaping query identifiers
async/awaitで書く
先の例は、ふつうにPromiseを使って書きましたが、今度はasync/awaitを使って書き直してみましょう。
Jestでもasync/awaitをサポートしているようなので、こちらの乗ってみます。
作成したのは、こちら。
test('insert and select with async / await', async() => { const connection = await mysql.createConnection({ host: '172.17.0.2', port: 3306, user: 'kazuhira', password: 'password', database: 'practice' }); try { const insertSql = 'insert into book(isbn, title, price) values(?, ?, ?)'; const insertData =['978-4774170206', 'MariaDB&MySQL全機能バイブル', 3780]; await connection.query(insertSql, insertData); const selectSql = 'select isbn, title, price from book where isbn = ?'; const selectData = ['978-4774170206']; const rows = await connection.query(selectSql, selectData); expect(rows).toHaveLength(1); expect(rows[0]['isbn']).toEqual('978-4774170206'); expect(rows[0]['title']).toEqual('MariaDB&MySQL全機能バイブル'); expect(rows[0]['price']).toEqual(3780); } catch (e) { console.log(e); } finally { await connection.end(); } });
データは少し変えましたが、すっきりしたのでは?
トランザクションを使う
最後に、トランザクションを使った例を書いてみましょう。
test('with transaction', async() => { const connection = await mysql.createConnection({ host: '172.17.0.2', port: 3306, user: 'kazuhira', password: 'password', database: 'practice' }); try { await connection.beginTransaction(); expect((await connection.query('select price from book where isbn = \'978-4798147406\''))[0]['price']).toEqual(3888); await connection.query('update book set price = 5000 where isbn = \'978-4798147406\''); expect((await connection.query('select price from book where isbn = \'978-4798147406\''))[0]['price']).toEqual(5000); await connection.rollback(); expect((await connection.query('select price from book where isbn = \'978-4798147406\''))[0]['price']).toEqual(3888); await connection.query('update book set price = 4000 where isbn = \'978-4798147406\''); expect((await connection.query('select price from book where isbn = \'978-4798147406\''))[0]['price']).toEqual(4000); await connection.commit(); expect((await connection.query('select price from book where isbn = \'978-4798147406\''))[0]['price']).toEqual(4000); } catch (e) { console.log(e); } finally { await connection.end(); } });
connection#beginTransactionでトランザクションの開始、それからcommit or rollbackという流れです。
こんなところではないでしょうか。