nodejs操作mongodb資料庫(mongoose)
準備
在上一篇的基礎上,通過npm安裝mongoose。
關於mongoose
Mongoose是MongoDB的一個物件模型工具,是基於node-mongodb-native開發的MongoDB nodejs驅動,可以在非同步的環境下執行。同時它也是針對MongoDB操作的一個物件模型庫,封裝了MongoDB對文件的的一些增刪改查等常用方法,讓NodeJS操作Mongodb資料庫變得更加靈活簡單。
Schema : 一種以檔案形式儲存的資料庫模型骨架,不具備資料庫的操作能力
Model : 由Schema釋出生成的模型,具有抽象屬性和行為的資料庫操作對
Entity : 由Model建立的實體,他的操作也會影響資料庫
它們之間的關係是Schema生成Model,Model創造Entity,Model和Entity都可對資料庫操作造成影響,但Model比Entity更具操作性。Model對應collection,Entity對應docment。
CRUD操作
1.增加
如果是Entity,使用save方法,如果是Model,使用create方法
// mongoose 連結
var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://127.0.0.1:27017/chm');
// 連結錯誤
db.connection.on('error' , function(error) {
console.log(error);
});
db.connection.on("open", function () {
console.log("——資料庫連線成功!——");
});
// Schema 結構
var vipSchema = new mongoose.Schema({
name : {type : String, default : 'java'},
addr : {type : String},
addTime : {type : Date, default: Date.now},
age : {type : Number }
});
//中介軟體
//save之前觸發
vipSchema.pre('save', function(next){
console.log("pre save");
next();
});
vipSchema.post('save', function (doc) {
//doc是當前插入的文件
console.log(doc);
});
// model
var vipModel = db.model('vip', vipSchema);
// 增加記錄 基於 entity 操作
var doc = {name : 'java', age:20, addr:"shanghai"};
var vipEntity= new vipModel(doc);
vipEntity.save(function(error) {
if(error) {
console.log(error);
} else {
console.log('saved OK!');
}
// 關閉資料庫連結
db.disconnect();
});
//增加記錄 基於model操作
vipModel.create(doc, function(error){
if(error) {
console.log(error);
} else {
console.log('save ok');
}
// 關閉資料庫連結
db.disconnect();
});
這裡有個問題,當執行完上面的程式碼後,到資料庫中執行db.vip.find();命令你會發現查不到剛新增的那條資料,再執行show collections你會發現多了一個vips集合,資料在這個集合裡面。這裡Mongoose在模型名至資料庫集合名的命名轉換上做了文章,下面會介紹。
2.查詢
var mongoose = require("mongoose");
var db = mongoose.connect('mongodb://localhost:27017/chm');
db.connection.on("error", function (error) {
console.log("資料庫連線失敗:" + error);
});
db.connection.on("open", function () {
console.log("——資料庫連線成功!——");
});
var Schema = mongoose.Schema;
//模板
var vipSchema = new Schema({
name:String,
age:Number,
addr:String,
addTime:Date
});
// 新增 mongoose 例項方法
vipSchema.methods.findByName = function(hello, callback) {
return this.model('vips').find({name: hello}, callback);
}
// 新增 mongoose 靜態方法,靜態方法在Model層就能使用
vipSchema.statics.findbyage = function(age, callback) {
return this.model('vips').find({age: age}, callback);
}
//模型
var vipModel = mongoose.model('vips', vipSchema);
vipModel.find({name:"java"},function(error, result){
if(error) {
console.log(error);
} else {
console.log(result);
}
//關閉資料庫連結
db.disconnect();
});
//基於例項方法的查詢
var entity = new vipModel({"name":"java"});
entity.findByName("java",function(error, result){
if(error) {
console.log(error);
} else {
console.log(result);
}
//關閉資料庫連結
db.disconnect();
});
//基於靜態方法的查詢
vipModel.findbyage(20, function(error, result){
if(error) {
console.log(error);
} else {
console.log(result);
}
//關閉資料庫連結
db.disconnect();;
});
另一種查詢:查詢時不帶回調
vipModel
.find({ occupation: /host/ })
.where('name.last').equals('Ghost')
.where('age').gt(17).lt(66)
.where('likes').in(['vaporizing', 'talking'])
.limit(10)
.sort('-occupation')
.select('name occupation')
.exec(callback);
如果不帶callback,則返回query,query沒有執行的預編譯查詢語句,該query物件執行的方法都將返回自己,只有在執行exec方法時才執行查詢,而且必須有回撥。
3.修改
//省略上面相同程式碼
......
// 修改記錄
var conditions = {name : 'java'};
var update = {$set : {age : 27}};
var options = {upsert : true};
vipModel.update(conditions, update, options, function(error){
if(error) {
console.log(error);
} else {
console.log('update ok!');
}
//關閉資料庫連結
db.disconnect();
});
4.刪除
刪除有2種方式,Entity和Model都使用remove方法
//省略上面相同程式碼
......
var conditions = {username: 'java'};
vipModel.remove(conditions, function(error){
if(error) {
console.log(error);
} else {
console.log('delete ok!');
}
//關閉資料庫連結
db.disconnect();
});
Sub Docs
如同SQL資料庫中2張表有主外關係,Mongoose將2個Document的巢狀叫做Sub-Docs(子文件)
簡單的說就是一個Document巢狀另外一個Document或者Documents:
var ChildSchema1 = new Schema({name:String});
var ChildSchema2 = new Schema({name:String});
var ParentSchema = new Schema({
children1:ChildSchema1, //巢狀Document
children2:[ChildSchema2] //巢狀Documents
});
Sub-Docs享受和Documents一樣的操作,但是Sub-Docs的操作都由父類去執行
var ParentModel = db.model('Parent',parentSchema);
var parent = new ParentModel({
children2:[{name:'c1'},{name:'c2'}]
});
parent.children2[0].name = 'd';
parent.save(callback);
parent在執行儲存時,由於包含children2,他是一個數據庫模型物件,因此會先儲存chilren2[0]和chilren2[1]。
如果子文件在更新時出現錯誤,將直接報在父類文件中,可以這樣處理:
ChildrenSchema.pre('save',function(next){
if('x' === this.name)
return next(new Error('#err:not-x'));
next();
});
var parent = new ParentModel({children1:{name:'not-x'}});
parent.save(function(err){
console.log(err.message); //#err:not-x
});
4.1 查詢子文件
如果children是parent的子文件,可以通過如下方法查詢到children
var child = parent.children.id(id);
4.2 新增、刪除、更新
子文件是父文件的一個屬性,因此按照屬性的操作即可,不同的是在新增父類的時候,子文件是會被先加入進去的。
如果ChildrenSchema是臨時的一個子文件,不作為資料庫對映集合,可以這樣:
var ParentSchema = new Schema({
children:{
name:String
}
});
//其實就是匿名混合模式
資料驗證
資料的儲存是需要驗證的,不是什麼資料都能往資料庫裡丟或者顯示到客戶端的,資料的驗證需要記住以下規則:
- 驗證始終定義在SchemaType中
- 驗證是一個內部中介軟體
- 驗證是在一個Document被儲存時預設啟用的,除非你關閉驗證
- 驗證是非同步遞迴的,如果你的SubDoc驗證失敗,Document也將無法儲存
- 驗證並不關心錯誤型別,而通過ValidationError這個物件可以訪問
7.1 驗證器 - required 非空驗證
- min/max 範圍驗證(邊值驗證)
- enum/match 列舉驗證/匹配驗證
- validate 自定義驗證規則
以下是綜合案例:
var PersonSchema = new Schema({
name:{
type:'String',
required:true //姓名非空
},
age:{
type:'Nunmer',
min:18, //年齡最小18
max:120 //年齡最大120
},
city:{
type:'String',
enum:['北京','上海'] //只能是北京、上海人
},
other:{
type:'String',
validate:[validator,err] //validator是一個驗證函式,err是驗證失敗的錯誤資訊
}
});
7.2 驗證失敗
如果驗證失敗,則會返回err資訊,err是一個物件該物件屬性如下
err.errors //錯誤集合(物件)
err.errors.color //錯誤屬性(Schema的color屬性)
err.errors.color.message //錯誤屬性資訊
err.errors.path //錯誤屬性路徑
err.errors.type //錯誤型別
err.name //錯誤名稱
err.message //錯誤訊息
一旦驗證失敗,Model和Entity都將具有和err一樣的errors屬性。
Model至Collection的命名策略
mongoose/lib/util.js模組中如下程式碼片段是集合命名的根源。
function pluralize (str) {
var rule, found;
if (!~uncountables.indexOf(str.toLowerCase())){
found = rules.filter(function(rule){
return str.match(rule[0]);
});
if (found[0]) return str.replace(found[0][0], found[0][1]);
}
return str;
};
1.判斷模型名是否是不可數的,如果是直接返回模型名;否則進行復數轉化正則匹配;
2.返回複數轉化正則匹配結果(一個複數轉化正則匹配是一個數組,有兩個物件,[0]正則表示式,[1]匹配後處理結果);
3.如果複數轉化正則匹配結果不存在,直接返回模型名;否則取匹配結果第一個,對模型名進行處理。(需要說明的是,rules是按特殊到一般的順序排列的)