ORM2是一款基於Node.js實現的ORM框架,名字相當的霸氣,算是同類框架中非常出色的一款,具體介紹請猛擊:https://github.com/dresende/node-orm2
剛接觸Node.js + MySQL,在引入項目之初,受Asp.Net經驗的影響,產生了許多不小的麻煩。下面是我定義的一個BaseProvider,作為所有DB Provider的父類,提供了一些公共的方法和屬性。

function BaseProvider() { this.table_name = {}; this.properties = {}; this.opts = {}; this.getProviderModel = function (callback) { define(this.table_name, this.properties, this.opts, function (error, model) { callback(error, model); }); }; }; var define = function (name, properties, opts, callback) { ORM.connect(pomelo.app.get("mysql"), function (error, db) { if (error) { logger.error(error); callback(error); } else { db.settings.set("connection.pool", true); var model = db.define(name, properties, opts); callback(null, model); } }); };
getProviderModel 相當於獲得一個Model,具有CRUD的功能,簡化我們寫SQL語句的繁瑣過程,下面是一個具體子類的實現。

function Dungeon() { this.table_name = "dungeon_userdungeons"; this.properties = { dungeon_userdungeon_id: Number, user_id: Number, dungeon_id: String, latest_scene_id: String, status: Number, create_datetime_utc: Date }; this.opts = {id: 'dungeon_userdungeon_id'}; this.getUserCompleteDungeonIds = function (user_id, callback) { this.getProviderModel(function (error, model) { model.aggregate(["dungeon_id"], {user_id: user_id, status: Dungeon.Status.Complete}).max("dungeon_id").groupBy("dungeon_id").get(function (error, userdungeons) { var dungeon_ids = []; userdungeons.forEach(function (e) { dungeon_ids.push(e.dungeon_id); }) callback(error, dungeon_ids); }); }); }; }; Dungeon.prototype = new BaseProvider();
在閱讀ORM2的源碼后,我發現了一個嚴重的問題,該方法用於連接,返回一個名為db的ORM對象,這個對象的職責是維護Nodejs到具體DB的連接信息、各種配置、連接池、緩存、Model等,而非ADO.Net中單純的Connection,下面是ORM. Connect的部分代碼。

var connect = function () { try { var Driver = require("./Drivers/DML/" + proto).Driver; var settings = new Settings.Container(exports.settings.get('*')); var debug = extractOption(opts, "debug"); var pool = extractOption(opts, "pool"); var driver = new Driver(opts, null, { debug: (debug !== null ? Boolean(debug) : settings.get("connection.debug")), pool: (pool !== null ? Boolean(pool) : settings.get("connection.pool")), settings: settings }); db = new ORM(proto, driver, settings); driver.connect(function (err) { if (typeof cb === "function") { if (err) { return cb(err); } else { return cb(null, db); } } db.emit("connect", err, !err ? db : null); }); } catch (ex) { if (ex.code === "MODULE_NOT_FOUND" || ex.message.indexOf('find module')) { return ORM_Error(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "CONNECTION_PROTOCOL_NOT_SUPPORTED"), cb); } return ORM_Error(ex, cb); } return db; };
而我常用define方法,就是在上面的那個db中的models數組中push一個對象而已。

ORM.prototype.define = function (name, properties, opts) { var i; properties = properties || {}; opts = opts || {}; for (i = 0; i < this.plugins.length; i++) { if (typeof this.plugins[i].beforeDefine === "function") { this.plugins[i].beforeDefine(name, properties, opts); } } this.models[name] = new Model({ db: this, settings: this.settings, driver_name: this.driver_name, driver: this.driver, table: opts.table || opts.collection || ((this.settings.get("model.namePrefix") || "") + name), properties: properties, extension: opts.extension || false, indexes: opts.indexes || [], cache: opts.hasOwnProperty("cache") ? opts.cache : this.settings.get("instance.cache"), id: opts.id || this.settings.get("properties.primary_key"), autoSave: opts.hasOwnProperty("autoSave") ? opts.autoSave : this.settings.get("instance.autoSave"), autoFetch: opts.hasOwnProperty("autoFetch") ? opts.autoFetch : this.settings.get("instance.autoFetch"), autoFetchLimit: opts.autoFetchLimit || this.settings.get("instance.autoFetchLimit"), cascadeRemove: opts.hasOwnProperty("cascadeRemove") ? opts.cascadeRemove : this.settings.get("instance.cascadeRemove"), hooks: opts.hooks || {}, methods: opts.methods || {}, validations: opts.validations || {} }); for (i = 0; i < this.plugins.length; i++) { if (typeof this.plugins[i].define === "function") { this.plugins[i].define(this.models[name], this); } } return this.models[name]; };
ORM2在connect時就把mysql的連接池自己維護起來了,存在一個變量中,所以Provider的每一次操作都會ORM.connect,返回的db作為一個局部變量用完就丟棄,維護的連接池也沒有保存,造成連接數太多,從而產生many connection error,下圖是mysql統計的數據,丟失的連接數達1000多。
每次調用都是新的連接池。
我在系統啟動時創建ORM的DB,存到BaseProvider的原型中。
ORM.connect(pomelo.app.get("mysql"), function (error, db) { BaseProvider.prototype.db = db; console.log("init connect~"); });
Define的時候直接訪問緩存的db變量。
var define = function (name, properties, opts, callback) { var model = BaseProvider.prototype.db.define(name, properties, opts); callback(null, model); };
我並發執行6個操作,連接池會創建6個連接滿足執行需求。
因為先前已經創建了6個連接,第二次執行的時候,則直接返回。
測試了一下午,連接池終於正常了。