標籤:rtt 屬性 對象 同名 function modules one oms 模式
資料模型及基礎操作模板
為了使工程結構清晰,將資料模型(Schema, Model)的建立與增刪查改的基礎操作模板寫在一起,命名為資料庫設計中的Collection(對應於關係型資料庫中的表定義)名,並儲存在models檔案夾中。
Schema與Model的建立:
Schema是Mongoose裡的資料模式,可以理解為表結構定義;每個Schema會映射到MongoDB中的一個Collection,不具備操作資料庫的能力。
考慮以下代碼:
//引入mongoose模組var mongoose = require(‘mongoose‘);//以json對象形式定義Schemavar 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 }}); //匯出Modelvar 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對象形式定義Schemavar 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.htmlvar 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: Regex,產生驗證器判斷值是否符合給定的Regex
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時直接省略而不用Null 字元串佔位。但事實上,在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)