zoukankan      html  css  js  c++  java
  • 应用Mongoose开发MongoDB(2)模型(models)

    数据模型及基础操作模板

    为了使工程结构清晰,将数据模型(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. ' + ' ' + this.modelName + '.findOneAndRemove(conditions, callback) ' + ' ' + this.modelName + '.findOneAndRemove(conditions) ' + ' ' + this.modelName + '.findOneAndRemove() '; 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()函数时的某些情况下需要进行必要的输出验证,否则会报错引起程序崩溃。

  • 相关阅读:
    扫面线模板
    (动态规划、栈)leetcode 84. Largest Rectangle in Histogram, 85. Maximal Rectangle
    tmux 常见命令汇总
    leetcode 221
    leetcode 319 29
    (贪心)leetcode 392. Is Subsequence, 771. Jewels and Stones, 463. Island Perimeter
    leetcode 982 668
    Python import 同文件夹下的py文件的函数,pycharm报错
    Windows里Anaconda-Navigator无法打开的解决方案
    Windows下 gpu版 Tensorflow 安装
  • 原文地址:https://www.cnblogs.com/gyjerry/p/6860466.html
Copyright © 2011-2022 走看看