1. 程式人生 > >應用Mongoose開發MongoDB(2)模型(models)

應用Mongoose開發MongoDB(2)模型(models)

length 輸出 ror highlight unit required opts nbsp 在一起

數據模型及基礎操作模板

為了使工程結構清晰,將數據模型(Schema, Model)的建立與增刪查改的基礎操作模板寫在一起,命名為數據庫設計中的Collection(對應於關系型數據庫中的表定義)名,並存儲在models文件夾中。

Schema與Model的建立:

Schema是Mongoose裏的數據模式,可以理解為表結構定義;每個Schema會映射到MongoDB中的一個Collection,不具備操作數據庫的能力。

考慮以下代碼:

//引入mongoose模塊
var mongoose = require(‘mongoose‘);
//以json對象形式定義Schema
var taskSchema = new mongoose.Schema({
         userId: String,
         invalidFlag:Number,
         task: [
             {
                _id:0,
                type: {type:String},
                details:[{
                      startTime : Date,
                      frequencyTimes : Number,
                      frequencyUnits : String,
                      status:Number
                }]
             }
         ], 
         revisionInfo:{
                   operationTime:Date,
                   userId:String
         }
}); 

//導出Model
var taskModel = mongoose.model(‘task‘, taskSchema);

  


這就定義了一個Schema和Model,映射到MongoDB中的一個Collection。實際操作過程中,需要註意以下幾點:

1. 命名規範:首字母小寫,如果命名中有多個單詞,第一個單詞首字母小寫,其他單詞首字母大寫。關於這一點,是本文這一系列的默認習慣規範,不同開發者有不同習慣。

2. 定義Schema時以json對象形式定義,鍵為屬性,值為屬性說明,關於屬性說明,至少需要定義屬性的類型(即type),如果有其他需要說明的,同樣以json的形式說明,鍵為屬性,值為說明。

3. Schema.Types: 可用的Schema Types有8種,其中String, Number, Date, Buffer, Boolean直接定義即可;Mixed, ObjectId需要引入mongoose模塊後定義;array使用中括號加元素Type定義,不說明也可以。Mixed類型可以看做嵌套類型,可以不指定內部元素的鍵,若需要指定內部元素的鍵,可以直接使用大括號聲明(如上的’revisionInfo’)

//引入mongoose模塊 
var mongoose = require(‘mongoose‘); //以json對象形式定義Schema var taskSchema = new mongoose.Schema({ _id: mongoose.Schema.Types.ObjectId, //主鍵 doctor_id: {type: mongoose.Schema.Types.ObjectId, ref:’doctor’}, //外鍵鏈接到“doctor” content: mongoose.Schema.Types.Mixed //混合或嵌套類型 });

  

4. 在定義Schema時的其他操作:

a) 對於全部Type有效:

required: boolean或function. 如果布爾值為真則會對模型進行驗證。

default: 設置屬性的默認值,可以是value或者function。

select: boolean 查詢時默認輸出該屬性。

validate: function, 對屬性進行自定義驗證器。

get, set: function, 自定義屬性的值

//get, set使用例子 
//參考: http://mongoosejs.com/docs/schematypes.html var numberSchema = new Schema({ integerOnly: { type: Number, get: v => Math.round(v), set: v => Math.round(v) } }); var Number = mongoose.model(‘Number‘, numberSchema); var doc = new Number(); doc.integerOnly = 2.001; doc.integerOnly; // 2

  

b) 索引Indexes

index: Boolean 屬性是否索引

unique: Boolean 是否唯一索引

sparse: Boolean 是否稀疏索引:稀疏索引,如果索引鍵中存儲值為null,就跳過這個文檔,這些文檔將不會被索引到。不過查詢時默認是不使用稀疏索引的,需要使用hint()指定使用在模型中建立的稀疏索引。

c) 對字符串String有效

lowercase: Boolean 轉成小寫,即對值調用.toLowerCase()

uppercase: Boolean 轉成大寫,即對值調用.toUpperCase()

trim: Boolean 去掉開頭和結尾的空格,即對值調用.trim()

match: 正則表達式,生成驗證器判斷值是否符合給定的正則表達式

enum: 數組,生成驗證器判斷值是否在給定的數組中

d) 對數字Number或時間Date有效

min, max: Number或Date 生成驗證器判斷是否符合給定條件

5. 註意:

聲明Mixed類型時,以下幾種方式是等價的:

//引入mongoose模塊 
var mongoose = require(‘mongoose‘); //聲明Mixed類型 var Any = new Schema({ any: {} }); var Any = new Schema({ any: Object }); var Any = new Schema({ any: mongoose.Schema.Types.Mixed});

  

關於數組(Array):

a) 聲明:

//引入mongoose模塊
var mongoose = require(‘mongoose‘);
 
//聲明類型為Mixed的空數組
var Empty1 = new Schema({ any: [] });
var Empty2 = new Schema({ any: Array });
var Empty3 = new Schema({ any: [mongoose.Schema.Types.Mixed] });
var Empty4 = new Schema({ any: [{}] });

  


b) 默認屬性:

數組會隱式地含有默認值(default: []),要將這個默認值去掉,需要設定默認值(default: undefined)

如果數組被標記為(required: true),存入數據時該數組必須含有一個元素,否則會報錯。

6. 自定義Schema Type:

從mongoose.SchemaType繼承而來,加入相應的屬性到mongoose.Schema.Type中,可以使用cast()函數實現,具體例子參見:

http://mongoosejs.com/docs/customschematypes.html

7. Schema Options:對Schema進行的一系列操作,因為我沒有驗證過,就不細說了。

參考 http://mongoosejs.com/docs/guide.html

=========================================================================

在這個文件中,除了導出和編譯數據模型外,另外建立了數據庫增刪查改的基礎方法,生成函數,導出模塊供其他文件調用。

仍然以上文中的../models/task.js文件作為示例:

//設置collection同名函數,並導出模塊
function Task(task) {
         this.task = task;
}

//添加基本的增刪查改操作函數模板
//...

module.exports = Task;

  

增:

Task.prototype.save = function(callback) {
         var task = this.task;
         var newTask = new taskModel(task);
         newTask.save(function(err, taskItem) {
                   if (err) {
                            return callback(err);
                   }
                   callback(null, taskItem);
         });
}

  

需要註意的是,數據庫文檔存儲方法是在Task原型鏈上修改,使用save()函數實現。在進行數據存儲的操作過程中,首先從原型對象生成實例,這裏原型對象就是所要存儲的文檔。完成從原型對象生成實例的操作,使用new運算符實現,然而new運算符無法共享屬性和方法,save()函數恰恰是需要共享的方法,因此使用prototype來設置一個名為save()的函數作為文檔的通用方法。

刪:

與增加方法不同,刪除、查找及修改方法直接在Task增加方法,因為這些方法是對模型進行操作,而模型的方法已在node_modules/mongoose/lib/model.js內定義。

與刪除有關的方法:

//刪除第一個匹配conditions的文檔,要刪除所有,設置‘justOne‘ = false

remove(conditions, [callback]);

//刪除第一個匹配conditions的文檔,會忽略justOne操作符

deleteOne(conditions, [callback]);

//刪除所有匹配conditions的文檔,會忽略justOne操作符

deleteMany(conditions, [callback]);

//實現MongoDB中的findAndModify remove命令,並將找到的文檔傳入callback中

//options: ‘sort‘, ‘maxTimeMS‘, ‘select‘

findOneAndRemove(conditions, [options], [callback]);

//以主鍵作為查詢條件刪除文檔,並將找到的文檔傳入callback中

findByIdAndRemove(id, [options], [callback]);

Task.removeOne = function(query, callback, opts) { 
var options = opts || {}; taskModel .findOneAndRemove(query, options, function(err, task) { if (err) { return callback(err); } callback(null, task); }); };

  

這個例子中,將導出的函數取名為Task.removeOne(), 在傳入參數時,將[option]放到了最後,這樣做的本意,是因為實際應用時,options往往是空的,不需要傳入,這樣做就可以在寫controller時直接省略而不用空字符串占位。但事實上,在model.js中定義時,已經做了處理:conditions必須傳入,且不能為function, 當第二個參數options是function時,將這個function認為是callback, 並將options設置為undefined

if (arguments.length === 1 && typeof conditions === ‘function‘) { 
var msg = ‘Model.findOneAndRemove(): First argument must not be a function.\n\n‘ + ‘ ‘ + this.modelName + ‘.findOneAndRemove(conditions, callback)\n‘ + ‘ ‘ + this.modelName + ‘.findOneAndRemove(conditions)\n‘ + ‘ ‘ + this.modelName + ‘.findOneAndRemove()\n‘; throw new TypeError(msg); } if (typeof options === ‘function‘) { callback = options; options = undefined; }

  

改:

與修改有關的方法:

//更新文檔而不返回他們

//option: ‘upsert’: if true, 如果沒有匹配條件的文檔則新建

//option: ‘multi’: if true, 更新多文檔

//option: ‘runValidators’, if true, 在更新之前進行模型驗證

//option: ‘setDefaultsOnInsert’, 如果此操作符與’upsert’同時為true, 將schema中的默認值新建到新文檔中

//註意不要使用已存在的實例作為更新子句,有可能導致死循環

//註意更新子句中不要存在_id字段,因為MongoDB不允許這樣做

//使用update時,值會轉換成對應type, 但是defaults, setters, validators, middleware不會應用,如果要應用這些,應使用findOne()然後在回調函數裏調用.save()函數

update(conditions, doc, [options], [callback]);

//忽略multi操作符,將所有符合conditions的文檔修改

updateMany(conditions, doc, [options], [callback]);

//忽略multi操作符,僅將第一個符合conditions的文檔修改

updateOne(conditions, doc, [options], [callback]);

//使用新文檔替換而不是修改

replaceOne(conditions, doc, [options], [callback]);

//找到匹配的文檔,並根據[update]更新文檔,將找到的文檔傳入[callback]

//option: ‘new’: if true,返回更新後的文檔

//’upsert’, ‘runValidators’, ‘setDefaultsOnInsert’, ’sort’, ‘select’等操作符也可用

findOneAndUpdate([conditions], [update], [options], [callback]);

//通過主鍵找到匹配的文檔,並根據[update]更新文檔,將找到的文檔傳入[callback]

findByIdAndUpdate(id, [update], [options], [callback]);

Task.updateOne = function(query, obj, callback, opts, populate) {
var options = opts || {}; var populate = populate || ‘‘; taskModel .findOneAndUpdate(query, obj, options) .populate(populate) .exec(function(err, uptask) { if(err){ return callback(err); } callback(null, uptask); }); }; Task.update = function(query, obj, callback, opts, populate) { var options = opts || {}; var populate = populate || ‘‘; taskModel .update(query, obj, options) .populate(populate) .exec(function(err, uptask) { if(err){ return callback(err); } callback(null, uptask); }); };

  

與刪除方法不同,callback不傳入.update()或.findOneAndUpdate()中,而在之後調用了.exec()中傳入了一個回調函數,如果err有內容則返回err, 否則返回uptask,也就是MongoDB的返回。這樣的處理,可以不需要等待MongoDB的響應。

populate是聯表查詢時使用的參數,將在之後的內容提到。

查:

與查詢有關的方法:

//conditions會在命令發送前自動被轉成對應的SchemaTypes

find(conditions, [projection], [options], [callback]);

//通過_id查詢到一條文檔

findById(id, [projection], [options], [callback]);

//查詢一條文檔,如果condition = null or undefined, 會返回任意一條文檔

findOne([conditions], [projection], [options], [callback]);

Task.getOne = function(query, callback, opts, fields, populate) { 
var options = opts || {}; var fields = fields || null; var populate = populate || ‘‘; taskModel .findOne(query, fields, opts) .populate(populate) .exec(function(err, taskInfo) { if(err){ return callback(err); } callback(null, taskInfo); }); }; Task.getSome = function(query, callback, opts, fields, populate) { var options = opts || {}; var fields = fields || null; var populate = populate || ‘‘; taskModel .find(query, fields, options) .populate(populate) .exec(function(err, tasks) { if(err) { return callback(err); } callback(null, tasks); }); };

  

在構造出的.getOne()和.getSome()函數的傳入參數中,可以看到option, field, populate在callback後面,因為最基本的情況是只有query和callback傳入,而後面的較少用到。而在一些要求復雜的查詢中,這三者是必不可少的。

雖然查詢最為復雜,不過都是通過.find()與.findOne()與各種操作符組合而成。同樣因為最基本的參數是condition與callback, 因此在導出函數時將這兩個參數放在最前面。值得註意的是,當查詢不到文檔時,.findOne()返回null, .find()返回空數組,這使得在調用getOne()函數時的某些情況下需要進行必要的輸出驗證,否則會報錯引起程序崩潰。

應用Mongoose開發MongoDB(2)模型(models)