Mongodb与Nodejs的模块机制

Mongodb与Nodejs的模块机制

我做了一个测试页面:两个静态图片,一段取自mongodb的文字。然后不停的刷新网页。出现奇怪的现象: 我用如下代码连接数据库:

var mongodb=require('mongodb')

, mongoserver=new mongodb.Server('localhost',27017)
,dbtest=new mongodb.Db('test',mongoserver,{w:-1});

如果我将这些语句写在路由的响应函数里就没问题。但是如果我将以上代码写在一个js文件中,封装成模块,然后引用,即一下这样:

模块文件:

var mongodb=require('mongodb')
, mongoserver=new mongodb.Server('localhost',27017)
,dbtest=new mongodb.Db('test',mongoserver,{w:-1});

module.exports=dbtest;

路由响应函数中调用:

var dbtest=require('dbcon');

(其他代码都相同)就会出现 Error: db object already connecting, open cannot be called multiple times 错误。

请问这是怎么回事呢,封装到模块和写在响应函数里不一样吗?代码都是一样的啊?


8 回复

在Node.js中,模块机制是一种重要的特性,它帮助我们组织和重用代码。当涉及到MongoDB这样的数据库时,如何正确地管理数据库连接变得尤为重要。在您的例子中,问题主要出在对数据库连接对象的重复初始化上。

问题分析

在您的代码中,您尝试将数据库连接逻辑封装到一个单独的模块中。然而,数据库连接对象(如Db实例)不应该被多次初始化。一旦连接创建并打开,再次尝试打开同一个连接会导致错误。

解决方案

为了解决这个问题,您需要确保数据库连接只被初始化一次,并且在整个应用生命周期内保持该连接。您可以使用单例模式来实现这一点。

示例代码

数据库连接模块 (dbcon.js)

// dbcon.js
let db;

module.exports = {
  connect: async () => {
    if (db) return db; // 如果已经连接,则直接返回

    const MongoClient = require('mongodb').MongoClient;
    const url = 'mongodb://localhost:27017';
    const dbName = 'test';

    try {
      const client = new MongoClient(url, { useNewUrlParser: true, useUnifiedTopology: true });
      await client.connect();
      console.log("Connected successfully to server");
      db = client.db(dbName);
      return db;
    } catch (err) {
      console.error("Failed to connect", err);
    }
  }
};

路由响应函数 (app.js 或类似文件)

// app.js
const express = require('express');
const dbcon = require('./dbcon');

const app = express();

app.get('/', async (req, res) => {
  try {
    const db = await dbcon.connect();
    const collection = db.collection('yourCollectionName');
    const doc = await collection.findOne({ /* 查询条件 */ });
    res.send(doc.text); // 假设文档有一个名为text的字段
  } catch (err) {
    res.status(500).send("Database error");
  }
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

解释

  • 单例模式:通过检查db变量是否已定义,我们避免了重复连接数据库。
  • 异步操作:使用async/await语法处理异步操作,使代码更易读。
  • 错误处理:添加了基本的错误处理逻辑,以确保在发生错误时能够适当地处理它们。

通过这种方式,您可以确保数据库连接只被初始化一次,并且在整个应用程序运行期间保持连接。


贴一下你的出错 stack

每个业务方法都有个基础类,在基础类里面 var dbtest=require(‘dbcon’); 一次就行

用MongoClient, 采用异步将数据库赋给宿主的方式连接数据库

‘use strict’;
var MongoClient = require(‘mongodb’).MongoClient;
function db(app) {
console.log(‘db required’);
MongoClient.connect(“mongodb://localhost:27017/mydb”, function (err, database) {
if (err) {
console.log(err);
} else {
app.db = database; // 将数据库对象赋值给宿主对象
if (app.onDatabaseReady) {
app.onDatabaseReady(database); //触发宿主对象中需要在数据库连接成功后运行的代码
}
console.log(“Connected to Database”);
}
});
}

module.exports = db;

主程序: var db = require(’…/path/to/db’);
db(app);

app.db.xxxx

Error: db object already connecting, open cannot be called multiple times
at Db.open (X:\BYSJ\Project\simulat\node_modules\mongodb\lib\mongodb\db.js:224:11)
at exports.index (X:\BYSJ\Project\simulat\routes\index.js:16:12)
at callbacks (X:\BYSJ\Project\simulat\node_modules\express\lib\router\index.js:161:37)
at param (X:\BYSJ\Project\simulat\node_modules\express\lib\router\index.js:135:11)
at pass (X:\BYSJ\Project\simulat\node_modules\express\lib\router\index.js:142:5)
at Router._dispatch (X:\BYSJ\Project\simulat\node_modules\express\lib\router\index.js:170:5)
at Object.router (X:\BYSJ\Project\simulat\node_modules\express\lib\router\index.js:33:10)
at next (X:\BYSJ\Project\simulat\node_modules\express\node_modules\connect\lib\proto.js:190:15)
at Object.methodOverride [as handle] (X:\BYSJ\Project\simulat\node_modules\express\node_modules\connect\lib\middleware\methodOverride.js:37:5)
at next (X:\BYSJ\Project\simulat\node_modules\express\node_modules\connect\lib\proto.js:190:15)

我是只var dbtest=require(‘dbcon’);了一次,但是在并发访问的时候就出错了?

这个MongoClient是一个数据库驱动还是一种连接方法?

这个问题涉及到MongoDB和Node.js中的模块机制以及数据库连接对象的生命周期管理。

当你在路由响应函数内部创建数据库连接时,每次请求都会创建一个新的连接对象。这虽然会消耗更多的资源,但在这种情况下不会导致任何问题,因为每次请求都是独立的。

然而,当你将数据库连接代码封装成一个模块并将其导出时,模块在应用启动时只初始化一次。这意味着dbtest对象在整个应用生命周期中只会被创建一次,并且只允许建立一次连接。如果你在多个请求中尝试多次使用这个对象,就会导致错误,因为数据库连接对象不能重复打开。

为了修复这个问题,你应该在模块中使用单一的数据库连接,并在需要时打开它。以下是一个改进的版本:

模块文件 (db.js)

const { MongoClient } = require('mongodb');

let dbConnection;

exports.connect = async () => {
    if (!dbConnection) {
        try {
            const client = new MongoClient('mongodb://localhost:27017', { useNewUrlParser: true, useUnifiedTopology: true });
            await client.connect();
            console.log("Connected to MongoDB");
            dbConnection = client.db('test');
        } catch (err) {
            console.error("Failed to connect to MongoDB", err);
        }
    }
    return dbConnection;
};

路由响应函数中调用

const express = require('express');
const router = express.Router();
const { connect } = require('./db');

router.get('/', async (req, res) => {
    try {
        const db = await connect();
        const collection = db.collection('yourCollectionName');
        const data = await collection.findOne({}); // 假设你有一个文档来获取文字
        res.render('index', { text: data.text }); // 将数据传递给视图
    } catch (err) {
        console.error("Error fetching data from MongoDB", err);
        res.status(500).send("Internal Server Error");
    }
});

module.exports = router;

在这个例子中,connect函数确保只有一个数据库连接存在,并且在每次请求中都可以安全地使用该连接来执行查询。

回到顶部