设为首页 收藏本站
查看: 1157|回复: 0

[经验分享] 应用Mongoose开发MongoDB(2)模型(models)

[复制链接]

尚未签到

发表于 2017-12-16 09:30:43 | 显示全部楼层 |阅读模式
  数据模型及基础操作模板
  为了使工程结构清晰,将数据模型(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()函数时的某些情况下需要进行必要的输出验证,否则会报错引起程序崩溃。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-424613-1-1.html 上篇帖子: [Mongodb] 借mongodb被入侵勒索事件,谈下Linux服务器端口安全问题 下篇帖子: Mongodb查询引用
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表