数据模型及基础操作模板

为了使工程结构清晰,将数据模型(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)的更多相关文章

  1. 应用Mongoose开发MongoDB(1)数据库连接

    最近因为项目,接触了MongoDB,因为是分工合作,我负责的部分主要是实现前端对数据库增删查改的需求,因此以下内容只着重于针对不同问题如何进行解决. 整个工程的最终目的是通过mongoose编写数据库 ...

  2. 应用Mongoose开发MongoDB(3)控制器(controllers)

    控制器的基本构成与如何通过路由调用 控制器中通过建立函数并导出,实现前端对数据库的查询.新建.删除与修改的需求,并使之可以在路由中调用,完成API的封装.本文着重于结构之间的关系,具体问题解决方法将在 ...

  3. Node.js开发——MongoDB与Mongoose

    为了保存网站的用户数据和业务数据,通常需要一个数据库.MongoDB和Node.js特别般配,因为MongoDB是基于文档的非关系型数据库,文档是按BSON(JSON的轻量化二进制格式)存储的,增删改 ...

  4. nodejs(一) 简单登录验证 使用mongoose 操作MongoDB

    ---恢复内容开始--- 开发使用webstorm 9  新建nodejs+express 项目 newfarmer 文章目录 配置Mongoose 创建目录及文件 插入数据,POST提交JSON增加 ...

  5. mongodb学习(3)--- NodeJs使用mongoose操作mongodb

    转载: https://cnodejs.org/topic/50c145ed637ffa4155c7eaee 首先对于以下错误说明(有写 db.close): Error: db object alr ...

  6. node-express项目的搭建并通过mongoose操作MongoDB实现增删改查分页排序(四)

    最近写了一个用node来操作MongoDB完成增.删.改.查.排序.分页功能的示例,并且已经放在了服务器上地址:http://39.105.32.180:3333. Mongoose是在node.js ...

  7. 在express中使用Mongoose连接MongoDB

    为何要学Mongoose? Mongoose是MongoDB的一个对象模型工具,封装了MongoDB对文档的的一些增删改查等常用方法,让NodeJS操作Mongodb数据库变得更加灵活简单. 0.安装 ...

  8. Backbone.js 为复杂Javascript应用程序提供模型(models)、集合(collections)、视图(views)的结构

    Backbone.js 为复杂Javascript应用程序提供模型(models).集合(collections).视图(views)的结构.其中模型用于绑定键值数据和 自定义事件:集合附有可枚举函数 ...

  9. Node使用Mongoose操作MongoDB数据库——增删改查的实现

    当初刚出社会时就规划了下自己的职业生涯:先成为一名优秀的前端工程师,再成为一名全栈工程师(精通前端开发.后台开发和客户端开发),最后成为一名优秀的系统架构师.转眼间已经工作快三年,是时候迈出关键性的一 ...

随机推荐

  1. 【uoj126】 NOI2013—快餐店

    http://uoj.ac/problem/126 (题目链接) 题意 求基环树直径. Solution zz选手迟早退役,唉,右转题解→_→:LCF 细节 拓扑排序的时候度数为0时入队.我在想什么w ...

  2. POJ 2135 Farm Tour (网络流,最小费用最大流)

    POJ 2135 Farm Tour (网络流,最小费用最大流) Description When FJ's friends visit him on the farm, he likes to sh ...

  3. 如何查去别人的ip,进行定位

  4. 创建 OpenStack云主机(十一)

    创建过程 创建虚拟网络 创建m1.nano规格的主机(相等于定义虚拟机的硬件配置) 生成一个密钥对(openstack的原理是不使用密码连接,而是使用密钥对进行连接) 增加安全组规则(用iptable ...

  5. 原始套接字-自定义IP首部和TCP首部

    /* ===================================================================================== * * Filenam ...

  6. javascript的单例/单体模式(Singleton)

    首先,单例模式是对象的创建模式之一,此外还包括工厂模式.单例模式的三个特点:1,该类只有一个实例2,该类自行创建该实例(在该类内部创建自身的实例对象)3,向整个系统公开这个实例接口 Java中大概是这 ...

  7. [BZOJ 1260][CQOI2007]涂色paint 题解(区间DP)

    [BZOJ 1260][CQOI2007]涂色paint Description 假设你有一条长度为5的木版,初始时没有涂过任何颜色.你希望把它的5个单位长度分别涂上红.绿.蓝.绿.红色,用一个长度为 ...

  8. 洛谷 P4838 P哥破解密码 题解

    矩阵乘法 + 快速幂优化递推: 看到这个题目我们不难想到递推,题干中说3个连续的A出现在序列中是不合法的,所以可以分为三种情况: (1):序列前只有一个A,如:BA,BBA,BABA. (2):序列前 ...

  9. Dream------Hbase--0.94版本和0.98/1.X版本api变动

    Dream------Hbase--0.94版本和0.98/1.X版本api变动 网上好多说getQualifier.getValue.getRow被..Array代替了,其实并不是的. 1. Int ...

  10. 春夏秋冬又一春之Redis持久化

    历史文章推荐: 一只准程序猿的唠叨 可能是最漂亮的Spring事务管理详解 Java多线程学习(八)线程池与Executor 框架 面试中关于Redis的问题看这篇就够了 非常感谢<redis实 ...