【问题标题】:Setting up singleton connection with node.js and mongo使用 node.js 和 mongo 建立单例连接
【发布时间】:2014-07-03 07:17:26
【问题描述】:

以前我使用 mongodb 和 php 并查询我使用单例的数据库。这样我只实例化了一次连接,然后重用它:

class MDB{
    protected static $instance;
    public static function use(){
        if(!self::$instance) self::$instance = new MongoClient();
        $db = self::$instance->selectDB('DB_name');
        return $db;
    }
}

我可以创建类 Cats 并拥有 addCat 和 showCats 方法,如下所示:

MDB::use->{'cats'}->insert([...]);
MDB::use->{'cats'}->find([...]);

现在我开始使用 mongodb 和 node.js。 Mongodb tutorial 向我展示了这样的内容:

var MongoClient = require('mongodb').MongoClient;
MongoClient.connect("mongodb://localhost:27017/exampleDb", function(err, db) {
  if(err) { return console.dir(err); }

  var collection = db.collection('test');
  var doc1 = {'hello':'doc1'};
  collection.insert(doc1);
});

这基本上告诉我,我必须将所有节点操作设置为连接内的回调。阅读类似question的人提供:

当您的应用启动并重用时,您打开一次 MongoClient.connect 数据库对象。每个 .connect 都不是单例连接池 创建一个新的连接池。

但我不明白我应该如何使用它(例如我的 cat 类)?

【问题讨论】:

  • 基本节点驱动程序本身是相当低级的。有多种实现可以为您抽象出此类工作。在有人认为“使用猫鼬”是一个答案之前,请注意这一点。所以这里的问题是,你真的想在这么低的水平上工作吗?如果是这样,那么它是关于如何获取该连接实例以便您可以在其他地方使用它,是吗?
  • @NeilLunn 现在对我来说看起来并不低级。我也不需要猫鼬提供的所有模式验证。根据我之前在这里回答 mongo 问题的经验,我注意到并非所有库都实现了所有功能。同样基于我之前构建 php/node 应用程序的经验,我能够使用 vanilla mongo 创建体面的应用程序。回到你的最后一个问题:据我所知,mongodb.github.io/node-mongodb-native/driver-articles/… 我可以将 db 传递给我的类方法。
  • 我并不打算提倡特定的实现,而是指出在其他“抽象”库之前已经完成了很多工作。并且相当多的工作是开源的,所以有地方可以看看它是如何完成的。我明白你在这里想要什么,这是一个单例实例,你可以从中请求连接,而不是到处传递它。我真的只是说,“其他东西已经这样做了”,所以如果你不想使用它们,你仍然可以借一些代码。
  • @NeilLunn 如果您能指出一些这样做的图书馆,我将不胜感激。现在我知道 mongoose 和 mongojs。
  • mongoskin 和mongo 有一些相当简化的实现,你可以看看。不过,按照您的 PHP 代码行的 Javascript 中的单例并不难。并且 asnwers 与已经存在的一般 javascript 答案并没有真正的不同。所以我真的只是在探索你是否真的想这样做。

标签: node.js mongodb


【解决方案1】:

这是在单例上使用异步等待的方法。 在我的 db.js

var MongoClient = require('mongodb').MongoClient;

var DbConnection = function () {

    var db = null;
    var instance = 0;

    async function DbConnect() {
        try {
            let url = 'mongodb://myurl.blablabla';
            let _db = await MongoClient.connect(url);

            return _db
        } catch (e) {
            return e;
        }
    }

   async function Get() {
        try {
            instance++;     // this is just to count how many times our singleton is called.
            console.log(`DbConnection called ${instance} times`);

            if (db != null) {
                console.log(`db connection is already alive`);
                return db;
            } else {
                console.log(`getting new db connection`);
                db = await DbConnect();
                return db; 
            }
        } catch (e) {
            return e;
        }
    }

    return {
        Get: Get
    }
}


module.exports = DbConnection();

并且在所有将使用相同连接的模块中

var DbConnection = require('./db');

async function insert(data) {
    try {
        let db = await DbConnection.Get();
        let result = await db.collection('mycollection').insert(data);

        return result;
    } catch (e) {
        return e;
    }
}

【讨论】:

  • 我很困惑。所有教程,包括来自 Mongo 的教程总是关闭连接。我使用类似的单例模式并且不关闭连接。我已经进行了巨大的压力测试,并且没有与 mongo 相关的问题。所以问题是:我们是否必须用这种单例模式关闭业务逻辑中的连接?
【解决方案2】:

这是一种方法。您可以将数据库连接详细信息放在一个小模块中,在您的应用程序启动时对其进行初始化,然后从需要数据库连接的任何其他模块中使用该模块。这是我一直在使用的代码,并且一直在一个相当简单的内部应用程序中为我工作。

文件: DataAccessAdapter.js

var Db = require('mongodb').Db;
var Server = require('mongodb').Server;
var dbPort = 27017;
var dbHost = 'localhost';
var dbName = 'CatDatabase';

var DataBase = function () {
};

module.exports = DataBase;

DataBase.GetDB = function () {
    if (typeof DataBase.db === 'undefined') {
        DataBase.InitDB();
    }
    return DataBase.db;
}

DataBase.InitDB = function () {
    DataBase.db = new Db(dbName, new Server(dbHost, dbPort, {}, {}), { safe: false, auto_reconnect: true });

    DataBase.db.open(function (e, d) {
        if (e) {
            console.log(e);
        } else {
            console.log('connected to database :: ' + dbName);
        }
    });
}

DataBase.Disconnect = function () {
    if (DataBase.db) {
        DataBase.db.close();
    }
}

DataBase.BsonIdFromString = function (id) {
    var mongo = require('mongodb');
    var BSON = mongo.BSONPure;
    return new BSON.ObjectID(id);
}

然后从 server.js,当你的应用程序启动时:

// Startup database connection
require('./DataAccessAdapter').InitDB();

当您需要使用数据库时,例如在“Cat.js”文件中,您可以执行以下操作:

var dataAccessAdapter = require('./DataAccessAdapter');

var Cat = function () {
    if (!Cat.db) {
        console.log('Initializing my Cat database');
        Cat.db = dataAccessAdapter.GetDB();
    }
    if (!Cat.CatCollection) {
            console.log('Initializing cats collection');
        Cat.CatCollection = Cat.db.collection('Cats'); // Name of collection in mongo
    }
    return Cat;
}

module.exports = Cat;

Cat.Name = null;
Cat.HasFur = false;

Cat.Read = function (catId, callback) {
    var o_id = dataAccessAdapter.BsonIdFromString(catId);
    Cat.CatCollection.findOne({ '_id': o_id }, function (err, document) {
        if (!document) {
            var msg = "This cat is not in the database";
            console.warn(msg);
            callback(null, msg);
        }
        else {
            callback(document);
        }
    });
}

我希望这至少有助于了解不同的方法。我并不声称自己是专家,并且愿意接受一些关于此的 SO 反馈,但到目前为止,此解决方案对我来说效果很好。

【讨论】:

  • 这不适用于新版本的Mongo lib/mongo.js:22 DataBase.db.open(function (e, d) { ^ TypeError: DataBase.db.open is not a function
  • 那是哪个版本?这篇文章相当老了,所以我们应该在答案的顶部指定哪个版本不起作用。
  • 从版本 3 及更高版本开始,它不响应此代码
【解决方案3】:

我赞成 Scampbell 的解决方案,但恕我直言,他的解决方案应该得到增强。 目前不是异步的,InitDBGetDB()都应该有回调属性。

因此,每当您更改要连接的数据库时,它都会失败,因为它会在有机会连接到数据库之前返回。 如果您始终连接到同一个数据库,则该错误不存在(因此返回 Database.db 始终成功)

这是我对他的解决方案的错误修复/增强:

Database.InitDB = function (callback) {

  if (_curDB === null || _curDB === undefined ||_curDB === '') {
    _curDB = _dbName;
  }

  Database.db = new Db(_curDB,
                   new Server(_dbHost, _dbPort, {}, {}),
                              { safe: false, auto_reconnect: true });

  Database.db.open(function (err, db) {
    if (err) {
        console.log(err);
    } else {
        console.log('connected to database :: ' + _curDB);
        if (callback !== undefined) {callback(db);}
    }
  });
};

他的其他职能也是如此。另请注意 if (callback 部分,它允许在 app.js/server.js 的开头不带参数地调用 Database.InitDB(),无论您的主文件是什么。

(((我应该将我的回复写为对 Scampbell 解决方案的评论,但我没有足够的声誉这样做。也感谢他的解决方案,这是一个很好的起点))

【讨论】:

    【解决方案4】:

    您可以使用 ES6 类来制作真正的单例。

    这是 Typescript 中的示例:

    import { MongoClient } from "mongodb";
    
    class MongoSingleton {
        private static mongoClient: MongoClient;
    
      static isInitialized(): boolean {
        return this.mongoClient !== undefined;
      }
    
      static getClient(): MongoClient {
        if (this.isInitialized()) return this.mongoClient;
    
        // Initialize the connection.
        this.mongoClient = new MongoClient(mongoUri, {
          useNewUrlParser: true,
          useUnifiedTopology: true,
        });
        return this.mongoClient;
    }
    

    您可以改进这个类来连接和保持内部连接或连接和断开其他类。

    【讨论】:

      【解决方案5】:

      我使用 typescript 连接到 node.js 中的 mongodb 的单例类的完整且有效的示例。

      import {Db, MongoClient, MongoError} from 'mongodb'
      
      // Connexion credentials type 
      type MongoDBCredential = {
          dbName: string;
          username: string,
          password: string;
          cluster: string;
      }
      
      // Singleton DBInstance Class 
      export class DbInstance {
       private static _instance: DbInstance;
       private _database: Db;
       private _dbClient: MongoClient;
      
       private constructor() {};
      
       public static async getInstance(cred: Readonly<MongoDBCredential>): Promise<DbInstance> {
       return new Promise((resolve, reject) => {
        if(this._instance) {
         resolve(this._instance);
        }
        this._instance = new DbInstance();
        this._instance._dbClient = new MongoClient(`mongodb+srv://${cred.username}:${cred.password}@${cred.cluster}.mongodb.net/${cred.dbName}?retryWrites=true&w=majority&readPreference=secondary`, {
          useNewUrlParser: true,
          useUnifiedTopology: true,
        });
        this._instance._dbClient.connect((error: MongoError) => {
           if(error) {
            reject(error);
           }
           this._instance._database = this._instance._dbClient.db(cred.dbName);
           resolve(this._instance);
         });
        });
      }
       get db(): Db {
        return DbInstance._instance._database;
       }
       get client(): MongoClient {
        return DbInstance._instance._dbClient;
       }
      }
      
      // To use it  
      const cred : MongoDBCredential = {  dbName: '***', username: '***', password: '***', cluster: '***' };
      DbInstance.getInstance(cred).then((dbi: DbInstance) => {
      // do your crud operations with dbi.db
       dbi.db.collection('employee').findOne({'salary': '80K€ ?'}).then(account => {
        console.info(account);
        dbi.client.close().
       });
      }).catch((error: MongoError )=> console.error(error));
      

      【讨论】:

        猜你喜欢
        • 2018-10-26
        • 2016-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多