解决原生NodeJS 操作MongoDB 数据库的性能问题,封装成更小、更灵活的操作MongoDB库:
Config.js 将所要连接的数据的配置信息封装成一个模块:
const Config = { dbUrl:'mongodb://admin:123@localhost:27017/', dbName:'hello' } module.exports=Config;
封装数据库时,引入数据库配置模块就好,首先通过 promise 封装:
const MongoClient = require('mongodb').MongoClient; const Config = require('./config.js'); class Db { constructor() { } //连接数据库 connect() { console.time('time_connect') return new Promise((resolve, reject) => { MongoClient.connect(Config.dbUrl, { useUnifiedTopology: true },(err, client) => { if (err) { console.log("连接数据库失败") reject(err) return } const db = client.db(Config.dbName); console.log("连接数据库成功") console.timeEnd('time_connect') resolve(db) }) }) } //查询数据 find(collectionName, json) { return new Promise((resolve, reject) => { this.connect().then((db) => { //连接数据库返回的db对象 db.collection(collectionName).find(json).toArray(function (err, result) { if (err) { reject(err); return; } resolve(result); }) }) }) } } console.time('time_find1') const myDb1 = new Db() myDb1.find('user', {}).then(data=> { //find返回的是promise console.log("查询数据1"); console.timeEnd('time_find1') }) console.time('time_find2') const myDb2 = new Db() myDb2.find('user', {"name":"jack3"}).then(data => { //find返回的是promise console.log("查询数据2"); console.timeEnd('time_find2') }) /** * 连接数据库成功 * time_connect: 49.488ms * 连接数据库成功 * (node:12552) Warning: No such label 'time_connect' for console.timeEnd() * 查询数据1 * time_find1: 65.757ms * 查询数据2 * time_find2: 53.866ms */
发生了两个问题,一是每次查询都需要连接数据库(查询时连接数据库获得 client 对象 ),而是每次查询都需要重新创建一个实例(myDb1,myDb2 )
首先解决多次连接数据库的问题:
可以在构造函数中定义一个 dbClient 的属性,通过这个属性值判断是否成功连接了数据库:
测试:
const MongoClient = require('mongodb').MongoClient; const Config = require('./config.js'); class Db { constructor() { this.dbClient=""; } //连接数据库 connect() { console.time('time_connect') return new Promise((resolve, reject) => { if(!this.dbClient){ MongoClient.connect(Config.dbUrl, { useUnifiedTopology: true }, (err, client) => { if (err) { console.log("连接数据库失败") reject(err) return } this.dbClient = client.db(Config.dbName); console.log("连接数据库成功") console.timeEnd('time_connect') resolve(this.dbClient) }) } else{ resolve(this.dbClient) } }) } //查询数据 find(collectionName, json) { return new Promise((resolve, reject) => { this.connect().then(db => { //连接数据库连接返回的db对象 db.collection(collectionName).find(json).toArray(function (err, result) { if (err) { reject(err); return; } resolve(result); }) }) }) } } const myDb = new Db() console.time('time_find1') myDb.find('user', {}).then(data => { console.log("查询数据1"); console.timeEnd('time_find1') }) setTimeout(()=>{ console.time('time_find2') myDb.find('user', {}).then(data => { console.log("查询数据2"); console.timeEnd('time_find2') }) },3000) /** * 连接数据库成功 * time_connect: 47.374ms * 查询数据1 * time_find1: 70.183ms * 查询数据2 * time_find2: 2.112ms */
但是多次实例化时,dbClient 重新为"",还是会再次去连接数据库:
const MongoClient = require('mongodb').MongoClient; const Config = require('./config.js'); class Db { constructor() { this.dbClient=""; } //连接数据库 connect() { console.time('time_connect') return new Promise((resolve, reject) => { if(!this.dbClient){ MongoClient.connect(Config.dbUrl, { useUnifiedTopology: true }, (err, client) => { if (err) { console.log("连接数据库失败") reject(err) return } this.dbClient = client.db(Config.dbName); console.log("连接数据库成功") console.timeEnd('time_connect') resolve(this.dbClient) }) } else{ resolve(this.dbClient) } }) } //查询数据 find(collectionName, json) { return new Promise((resolve, reject) => { this.connect().then(db => { //连接数据库连接返回的db对象 db.collection(collectionName).find(json).toArray(function (err, result) { if (err) { reject(err); return; } resolve(result); }) }) }) } } const myDb1 = new Db() console.time('time_find1') myDb1.find('user', {}).then(data => { console.log("查询数据1"); console.timeEnd('time_find1') }) const myDb2 = new Db() setTimeout(()=>{ console.time('time_find2') myDb2.find('user', { "name": "jack5" }).then(data => { console.log("查询数据2"); console.timeEnd('time_find2') }) },2000) /** * 连接数据库成功 * time_connect: 42.574ms * 查询数据1 * time_find1: 55.639ms * 连接数据库成功 * time_connect: 5.675ms * 查询数据2 * time_find2: 16.233ms */
通过单例模式解决这个问题:
const MongoClient = require('mongodb').MongoClient; const Config = require('./config.js'); class Db { static getInstance(){ if (!Db.instance) { //单例 解决多次实例化实例不共享的问题 Db.instance=new Db() } return Db.instance } constructor() { this.dbClient=""; } //连接数据库 connect() { console.time('time_connect') return new Promise((resolve, reject) => { if (!this.dbClient) { //解决数据库多次连接的问题 MongoClient.connect(Config.dbUrl, { useUnifiedTopology: true }, (err, client) => { if (err) { console.log("连接数据库失败") reject(err) return } this.dbClient = client.db(Config.dbName); console.log("连接数据库成功") console.timeEnd('time_connect') resolve(this.dbClient) }) } else{ resolve(this.dbClient) } }) } //查询数据 find(collectionName, json) { return new Promise((resolve, reject) => { this.connect().then(db => { //连接数据库连接返回的db对象 db.collection(collectionName).find(json).toArray(function (err, result) { if (err) { reject(err); return; } resolve(result); }) }) }) } } const myDb1 = Db.getInstance() //相当于 myDb1 = new Db() console.time('time_find1') myDb1.find('user', {}).then(data => { console.log("查询数据1"); console.timeEnd('time_find1') }) const myDb2 = Db.getInstance() //相当于 myDb2 = Db.instance setTimeout(()=>{ console.time('time_find2') myDb2.find('user', { "name": "jack5" }).then(data => { console.log("查询数据2"); console.timeEnd('time_find2') }) },2000) /** * 连接数据库成功 * time_connect: 41.880ms * 查询数据1 * time_find1: 55.679ms * 查询数据2 * time_find2: 2.045ms */
最后封装好的 db库将它暴露出去,这里还可以在初始化的时候就连接数据库,第一次操作数据库在初始化就连接上,而不是执行具体操作的时候连接数据库:
const MongoClient = require('mongodb').MongoClient; const Config = require('./config.js'); class Db { static getInstance() { if (!Db.instance) { //单例 解决多次实例化实例不共享的问题 Db.instance = new Db() } return Db.instance } constructor() { this.dbClient = ""; this.connect() } //连接数据库 connect() { //console.time('time_connect') return new Promise((resolve, reject) => { if (!this.dbClient) { //解决数据库多次连接的问题 MongoClient.connect(Config.dbUrl, { useUnifiedTopology: true }, (err, client) => { if (err) { console.log("连接数据库失败") reject(err) return } this.dbClient = client.db(Config.dbName); console.log("连接数据库成功") //console.timeEnd('time_connect') resolve(this.dbClient) }) } else { resolve(this.dbClient) } }) } //查询数据 find(collectionName, json) { return new Promise((resolve, reject) => { this.connect().then(db => { //连接数据库连接返回的db对象 db.collection(collectionName).find(json).toArray(function (err, result) { if (err) { reject(err); return; } resolve(result); }) }) }) } //修改数据(一条) update(collectionName, oldData, newData) { return new Promise((resolve, reject) => { this.connect().then((db) => { db.collection(collectionName).updateOne(oldData, { $set: newData }, (err, result) => { if (err) { console.log("修改数据失败") reject(err) return; } console.log("修改数据成功") resolve(result) }) }) }) } //添加数据(一条) insert(collectionName, addData) { return new Promise((resolve, reject) => { this.connect().then(db => { db.collection(collectionName).insertOne(addData, (err, result) => { if (err) { console.log("添加数据失败") reject(err) return } console.log("添加数据成功") resolve(result) }) }) }) } //删除数据(一条) remove(collectionName, deleteData) { return new Promise((resolve, reject) => { this.connect().then(db => { db.collection(collectionName).removeOne(deleteData, (err, result) => { if (err) { console.log("删除数据失败") reject(err) return } console.log("删除数据成功") resolve(result) }) }) }) } } module.exports = Db.getInstance()
引入封装好的 db 库,通过 kob 操作:
const Koa = require('koa') const app = new Koa() const router = require('koa-router')() const render = require('koa-art-template') const views = require('koa-views') const path = require('path') const Db = require('./util/db') //配置art-template render(app, { root: path.join(__dirname, 'views'), // 视图的位置 extname: '.html', // 后缀名 debug: process.env.NODE_ENV !== 'production' //是否开启调试模式 }) router.get('/', async (ctx)=>{ let res = await Db.find('user', { "name": "李华" }) //console.log(res) //{ _id: 5eef338f7701003ebcbbafa3, name: '李华', age: 18 } await ctx.render('index', { name: res[0].name, age: res[0].age }) }) app.use(router.routes()); app.use(router.allowedMethods()); app.listen(3000);