Node.js中的ORM

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);
        }
    });
};
View Code

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();
View Code

在阅读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;
};
View Code

而我常用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];
};
View Code

ORM2在connect时就把mysql的连接池自己维护起来了,存在一个变量中,所以Provider的每一次操作都会ORM.connect,返回的db作为一个局部变量用完就丢弃,维护的连接池也没有保存,造成连接数太多,从而产生many connection error,下图是mysql统计的数据,丢失的连接数达1000多。

clip_image001

每次调用都是新的连接池。

image

我在系统启动时创建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个连接满足执行需求。

clip_image001[5]

因为先前已经创建了6个连接,第二次执行的时候,则直接返回。

clip_image002[4]

测试了一下午,连接池终于正常了。

clip_image003

posted @ 2013-11-13 22:37  冯翔  阅读(21701)  评论(1编辑  收藏  举报