はじめに
Node.jsでmysqlを使おうとした時、大体はnode-mysqlかpromise-mysqlを使うと思うのですが、どちらにしても大変面倒くさいのでnode-mysqlをラップしてmysqlを簡単に操作するためのコードを書いてみました。
結論から言うと、ネストをほぼ発生させずに処理を3行~にまとめる事が出来ました。モジュールにしたら保守とか色々面倒くさそうなのでコードのままここに上げておきます。promiseを返したかったり、足りない部分があった場合は各位で調整してください。
また動作確認はしていますが、下記コードを使用したことによる損害等は一切保証しませんので悪しからず。「これコピペしても動かないんだけど!」とか言う輩はNode.js向いてないので辞めたほうが良いと思うよ
使い方
vartest=require("./DbAdapter");varhoge=newtest();hoge.getData("select * from ?","hoge",function(result){console.log(result)});
実際に使う時はこんな感じ、データの取得に関しては、ほぼnode-mysqlの使い勝手で行ける。プレースホルダ使わない場合は値の代わりにnullぶち込むだけでOK
vartest=require("./DbAdapter");varhoge=newtest();test.setDataAddSql("insert into hoge value('110');");test.setDataAddSql("insert into hoge value(?);",120);test.setDataExe();
データを書き込む時は、SQLを追加してから実行メソッドを叩くだけでOKです。
setDataAddSql()は配列に値をぶち込むだけなので同期処理とか考えなくても良いのは便利ですね。書き込みが成功したかの判定は適宜select文とかでどうぞ。後述したコード見れば意味が分かると思いますが、多分それが一番早いと思います。(普通にエラー投げて呼び出し側で受ければ良くない?と思ったので後で書き直すマン)
本体の実装
/**DBを操作するクラス。
* newしてメソッドを叩くだけ!!! */classDbAdapter{constructor(){this.sqlArray=newArray();this.valueArray=newArray();}/**単一の情報取得クエリを実行します(コミット無し) */getData(SQL,value=null,callback=function(result){}){varpool=require("./pool").getPool();pool.getConnection(function(err,connection){if(err){console.log(err);}connection.beginTransaction(function(err){if(err){console.log(err);}connection.query(SQL,value,function(err,results,fields){if(err){console.log(err);}connection.rollback(function(err){if(err){console.log(err);}connection.release();callback(results);});})})})}/**SQL文をインスタンスに追加します */setDataAddSql(SQL,value=null){this.sqlArray.push(SQL);this.valueArray.push(value);}/**追加されたSQL文を実行します */setDataExe(){//thisの値とメソッドを取得varnowSqlArray=this.sqlArray;varnowValueArray=this.valueArray;varrecursiveQuery=this._recursiveQuery;//コネクションを実行varpool=require("./pool").getPool();pool.getConnection(function(err,connection){if(err){console.log(err);}//トランザクション開始connection.beginTransaction(function(err){if(err){console.log(err);}recursiveQuery(nowSqlArray,nowValueArray,connection,recursiveQuery);//jsのクラスはメソッドの中から自身への参照を取れないので引数で渡す})})}/**connectionの処理をラップしたメソッド。クラス外部からは使用禁止 */_recursiveQuery(sqlArray,valueArray,connection,self){varnewSqlArray=sqlArray;varnewValueArray=valueArray;varmyself=self;//エラー制御if(newSqlArray.length==0){console.log("SQLが設定されていません");connection.rollback(function(err){if(err){console.log(err);}connection.release();});return;}//クエリを送信connection.query(newSqlArray.shift(),newValueArray.shift(),function(err,results,fields){//エラー処理if(err){connection.rollback(function(err){if(err){console.log(err);}connection.release();});console.log(err);}//SQLを全て実行し終わった時if(newSqlArray.length==0){connection.commit(function(err){if(err){console.log(err);connection.rollback(function(err){if(err){console.log(err);}connection.release();});}console.log("SQL exe");return;})//まだSQLが残っている時(再帰処理)}else{myself(newSqlArray,newValueArray,connection);}})}}module.exports=DbAdapter;
………まあこのコードを見たJS使いの諸兄が言いたいことは分からんでも無いですが、元々JavaとかC#とか書いてた民なのでPromiseそんなに好きじゃないんだ。「Promiseを返す」って所までは分かるけど、再帰処理とか値を返す処理とか(そもそもjsでそれを書くべきではない)を書き始めると一気に難読化しない???
という与太はそこら辺にしておいて解説に移りたいと思います。
と言ってもgetData()とかその辺はコード読めば分かりますね。ライブラリの呼び出しをただ単にラップしてるだけです。beginTransactionとか何回も書くのは嫌なので、そういう何やかんやは全てメソッドの中にまとめました。
途中で出てくるvar pool = require("./pool").getPool();は別クラスで定義したコネクションプールの呼び出しです。
実装はこんな感じ
classPool{constructor(){varmysql=require("mysql");this.pool=mysql.createPool({connectionhoLimit:100,host:"host",user:"hoge",password:"password",database:"huga"});}getPool(){returnthis.pool}}module.exports=newPool()
特段解説する所は無いですね。よりセキュアな実装を求めるならPASSはベタ書きじゃなくて、より安全な保管場所から拾うべきです。ちなみmodule.exportsしてるのはクラスじゃなくてインスタンスなので、地味にシングルトン風味だったりします。
で話をDbAdapter本体に戻すのですが、書き込み側は配列にSQLを追加するsetDataAddSql()と実行処理のトリガー、再帰処理を含むクエリの実行と言った感じに3つのメソッドで実装しました。使用感としてはJavaのStringBuilderを参考にしたので、一部の方には馴染みが深いと思います。
と言った風に、まあ書いてしまえばそれだけなのですが、この実装にたどり着くまで5回書き直して丸3日かかりました。
意外かもしれませんが、最初はSELECT文発行してデータを受け取る所で詰まりましたね。今思えば何で詰まってたんだろうって感じですが、Node.jsはJavaみたいにスタックでコードを考えると十中八九うまく行かないみたいです。
というかどれだけ糖衣錠しようが実際に動かす時はスクリプトチックな挙動をするのでコールスタックなんて幻想ですよ。ちょっとした関数でも値を返すより、その後の処理を値で受け取るのがベター(まあ素直にpromise返せばそれで良い気がするけど……)。Node.js の return と for を信じてはいけない(戒め)
あと詰まった所といえばクラスプロパティのスコープですね。コールバック関数の中でthis.hoge = huga とか書いても値入らないしエラー吐かないのにはビビる。
あとコードの中にコメント書いてるけど、メソッドの中から自分自身をthisで取得しようとするとエラるね。これは引数で渡してご強引に解決しました。
他にも色々詰まった気がするけど、正気を保つためにコーディング中の記憶を全て消去したので、あとは覚えてないです。
おわりに
現在はNode.jsでWebサービスを開発中です。
開発メンバーを募集してるので興味のある方はTwitter@hoge19194545までご連絡ください
↑とかやると金関係人間関係その他その他の問題がこじれて開発どころの話では無くなるので募集しません。1度言ってみたかっただけ
その代わりと言ってはなんですが、サービス開発してるのは本当なのでツイッター等フォローして下さるとありがたいです。
またどこかでお会いしましょう。ご安全に!
参考資料とか
Node.jsでMySQLを使うメモ
https://qiita.com/PianoScoreJP/items/7ed172cd0e7846641e13
[Node.js][MySQL]Promiseを使ってトランザクションを書きやすくする
https://tech.chakapoko.com/nodejs/mysql/promise.html
Node.jsでシングルトンなクラスモジュール
https://memo.appri.me/programming/nodejs-singleton-class