使用Node操作MongoDB
1. 连接MongoDB
使用Node MongoDB driver模块来配置Node应用对MongoDB server交互。
新建index.js
const MongoClient = require('mongodb').MongoClient
const assert = require('assert')
// MongoDB server的url
const url = 'mongodb://localhost:27017/'
const dbname = 'demo'
// 创建一个新的MongoClient
const client = new MongoClient(url)
client.connect((err) => {
// 抛出连接错误
assert.equal(err, null)
console.log('Connected correctly to server')
// 创建名为demo的数据库
const db = client.db(dbname)
client.close()
})
2. 实现数据的增删改查
在MongoDB中,数据层级为 db -> collection -> document -> 字段
2.1 Collection
修改index.js
client.connect((err) => {
assert.equal(err, null)
console.log('Connected correctly to server')
const db = client.db(dbname)
// 得到countries collection
const collection = db.collection('countries')
// 插入一个country
collection.insertOne({"name": "China", "description": "test"}, (err, result) => {
assert.equal(err, null)
console.log("After Insert:
")
console.log(result.ops)
// 查找collection中的所有country
collection.find({}).toArray((err, docs) => {
assert.equal(err, null)
// 输出结果
console.log("Found:
")
console.log(docs)
// 删除countries collection
db.dropCollection("countries", (err, result) => {
assert.equal(err, null)
client.close()
})
})
})
}
2.2 Document
新建operations.js
const assert = require('assert')
exports.insertDocument = (db, document, collection, callback) => {
const coll = db.collection(collection)
coll.insert(document, (err, result) => {
assert.equal(err, null)
console.log("Inserted " + result.result.n + " documents into the collection " + collection)
callback(result)
})
}
exports.findDocuments = (db, collection, callback) => {
const coll = db.collection(collection)
coll.find({}).toArray((err, docs) => {
assert.equal(err, null)
callback(docs)
})
}
exports.removeDocument = (db, document, collection, callback) => {
const coll = db.collection(collection)
coll.deleteOne(document, (err, result) => {
assert.equal(err, null)
console.log("Removed the document ", document)
callback(result)
})
}
exports.updateDocument = (db, document, update, collection, callback) => {
const coll = db.collection(collection)
coll.updateOne(document, { $set: update }, null, (err, result) => {
assert.equal(err, null)
console.log("Updated the document with ", update)
callback(result)
})
}
// result的结构
{
result: { n: 1, ok: 1 },
connection: Connection {...},
message: Response { ... },
ops: [
{
name: '...',
description: '...',
_id: 5e4a6143b2d40e52789c3aab
}
],
insertedCount: 1,
insertedId: 5e4a6143b2d40e52789c3aab
}
增改index.js
...
const dboper = require('./operations')
...
dboper.insertDocument(db, { name: "earth", description: "Test"}, "countries", (result) => {
console.log("Insert Document:
", result.ops)
dboper.findDocument(db, "countries", (docs) => {
console.log("Found Documents:
", docs)
dboper.updateDocument(db, { name: "earth", description: "Updated Test" }, "countries", (result) => {
console.log("Updated Document:
", result.result)
dboper.findDocument(db, "countries", (docs) => {
console.log("Found Updated Documents:
", docs)
db.dropCollection("countries", (result) => {
console.log("Dropped Collection: ", result)
client.close()
})
})
})
})
})
...
回调地狱
上面的操作中我们的增删改查之后的异步操作都是基于回调函数来实现,最后形成了像金字塔一样的嵌套结构。如果有嵌套的操作,像jQuery中的异步回调一样,就会形成造成的情况,代码可读性大大降低,也不好维护。我们需要解决这一问题,Promise这一异步机制,支持我们以链式来书写我们的异步代码,也复合直观上的执行顺序。
下面使用Pomise重写上面的代码(MongoDB Driver模块支持promise形式的异步方法):
1. 更改operations.js
exports.insertDocument = (db, document, collection, callback) => {
const coll = db.collection(collection);
return coll.insert(document);
};
exports.findDocuments = (db, collection, callback) => {
const coll = db.collection(collection);
return coll.find({}).toArray();
};
exports.removeDocument = (db, document, collection, callback) => {
const coll = db.collection(collection);
return coll.deleteOne(document);
};
exports.updateDocument = (db, document, update, collection, callback) => {
const coll = db.collection(collection);
return coll.updateOne(document, { $set: update }, null);
};
2. 基于Promise
更改index.js
...
MongoClient.connect(url).then((client) => {
console.log('Connected correctly to server')
const db = client.db(dbname)
dboper.insertDocument(db, { name: "earth", description: "Test"}, "countries")
.then((result) => {
console.log("Insert Document:
", result.ops)
return dboper.findDocuments(db, "countries")
})
.then((docs) => {
console.log("Found Documents:
", docs)
return dboper.updateDocument(db, { name: "earth", description: "Updated Test"}, "countries")
})
.then((result) => {
console.log("Updated Document:
", result.result)
return dboper.findDocuments(db, "countries")
})
.then((docs) => {
console.log("Found Dpcuments:
", docs)
return dboper.dropCollection("countries")
})
.then((result) => {
console.log("Dropped Collection: ", result)
return client.close()
})
.catch((err) => console.log(err))
}).catch((err) => console.log(err))
...
3. 结构化数据
Schema
documents存储到数据库后,数据库内部并没有给documents规定结构,需要使用Mongoose
模块来定义数据的结构。Mongoose
通过使用Schema
来给某个collection
的document
规定结构。Schema可以定义document的字段以及字段的类型。
定义了一个schema
后,在Mongoose中就创建了一个model
函数,一个model的实例就是一个document
。
用countries
这个collection
来说明,每一个country会包含一组属性,这些属性是客户端发来的数据,以JSON字符串形式存储;countries是一个country类型的数组,每一个country包含一个名字,人口数量,子行政区域,等等。在每个country document内,又包含一个子行政区域的数组,每个子行政区域有自己的名称,人口,等等。所以每一个数据库内的country document都有一个清晰的结构,多个country就以一个collection的形式存储在数据库中。
[
{
"name": "China",
"population": 1400050000,
"province": [
"Beijing": {
"name": "Beijing",
"population": 20000000
},
"Shanghai": {
"icon": "Shanghai",
"population": 20000000
},
...
]
},
Russia: { ... },
...
]
使用Schema
定义数据结构,创建countries.js
文件
const mongoose = require('mongoose')
const Schema = mongoose.Schema
// 定义Mongoose Schema
const countrySchema = new Schema({
name: {
type: String, // name字段为String类型
required: true, // 每一条数据,必须包含name字段
unique: true // name字段不能重复
},
population: {
type: Number,
required: true
}
}, {
timestamps: true
})
// 创建一个model
var Countries = mongoose.model('Country', countrySchema)
module.exports = Countries
现在再来看什么是Mongoose ODM
(Object Document Mapping )?
Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB collection and defines the shape of the documents within that collection .
countrySchema
中的每一个键都定义了dument中的一个属性,该属性将转换为它关联的SchemaType.在上面的例子中,定义了一个name
属性,转换为String SchemaType
,population转换为Number SchemaType
.
Mongoose
中document
的增删改查
新建文件夹node-mongoose下新建index.js
文件
const mongoose = require('mongoose')
const Countries = require('./models/countries')
const url = 'mongodb://localhost:27017/demo'
const connect = mongoose.connect(url)
connect.then((db) => {
console.log('Connected correctly to server')
var newCountry = Countries({
name: 'hahha',
population: 10
})
// 增加newCountry数据
newCountry.save()
.then((country) => {
console.log(country)
return Countries.find({})
})
.then((countries) => {
console.log(countries)
return Countries.remove({})
})
.then(() => {
return mongoose.connection.close()
})
.catch((err) => {
console.log(err)
})
})
sub-documents
为countrySchema
添加sub-documents,更改countries.js
文件
var provinceSchema = new Schema({
name: {
type: String,
required: true
},
population: {
type: Number,
required: true
}
}, {
timestamps: true
})
const countrySchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
population: {
type: Number,
required: true
},
provinces: [provinceSchema]
}, {
timestamps: true
})
更改index.js
文件
...
// 增加一条document到数据库中
Countries.create({
name: 'hengha',
population: 10000
})
.then((country) => {
console.log(country)
return Countries.findByIdAndUpdate(country._id, {
$set: { population: 20000 }
}, {
new: true // 返回改变后的document
})
.exec()
})
.then((country) => {
console.log(country)
country.provinces.push({
name: "test",
population: 20
})
return country.save()
})
.then((country) => {
console.log(country)
return country.remove({})
})
.then(() => {
return countries.connection.close()
})
.catch((err) => {
console.log(err)
})
...