准备
在上一篇的基础上,通过npm安装mongoose。
关于mongoose
Mongoose是MongoDB的一个对象模型工具,是基于node-mongodb-native开发的MongoDB nodejs驱动,可以在异步的环境下执行。同时它也是针对MongoDB操作的一个对象模型库,封装了MongoDB对文档的的一些增删改查等常用方法,让NodeJS操作Mongodb数据库变得更加灵活简单。
Schema : 一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力
Model : 由Schema发布生成的模型,具有抽象属性和行为的数据库操作对
Entity : 由Model创建的实体,他的操作也会影响数据库
它们之间的关系是Schema生成Model,Model创造Entity,Model和Entity都可对数据库操作造成影响,但Model比Entity更具操作性。Model对应collection,Entity对应docment。
Mongoose API:http://mongoosejs.com/docs/api.html
CRUD操作
1.增加
如果是Entity,使用save方法,如果是Model,使用create方法
// mongoose 链接
var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://127.0.0.1:27017/chm');
// 链接错误
db.connection.on('error', function(error) {
console.log(error);
});
db.connection.on("open", function () {
console.log("——数据库连接成功!——");
});
// Schema 结构
var vipSchema = new mongoose.Schema({
name : {type : String, default : 'java'},
addr : {type : String},
addTime : {type : Date, default: Date.now},
age : {type : Number}
});
//中间件
//save之前触发
vipSchema.pre('save', function(next){
console.log("pre save");
next();
});
vipSchema.post('save', function (doc) {
//doc是当前插入的文档
console.log(doc);
});
// model
var vipModel = db.model('vip', vipSchema);
// 增加记录 基于 entity 操作
var doc = {name : 'java', age:20, addr:"shanghai"};
var vipEntity= new vipModel(doc);
vipEntity.save(function(error) {
if(error) {
console.log(error);
} else {
console.log('saved OK!');
}
// 关闭数据库链接
db.disconnect();
});
//增加记录 基于model操作
vipModel.create(doc, function(error){
if(error) {
console.log(error);
} else {
console.log('save ok');
}
// 关闭数据库链接
db.disconnect();
});
这里有个问题,当执行完上面的代码后,到数据库中执行db.vip.find();命令你会发现查不到刚添加的那条数据,再执行show collections你会发现多了一个vips集合,数据在这个集合里面。这里Mongoose在模型名至数据库集合名的命名转换上做了文章,下面会介绍。
2.查找
var mongoose = require("mongoose");
var db = mongoose.connect('mongodb://localhost:27017/chm');
db.connection.on("error", function (error) {
console.log("数据库连接失败:" + error);
});
db.connection.on("open", function () {
console.log("——数据库连接成功!——");
});
var Schema = mongoose.Schema;
//模板
var vipSchema = new Schema({
name:String,
age:Number,
addr:String,
addTime:Date
});
// 添加 mongoose 实例方法
vipSchema.methods.findByName = function(hello, callback) {
return this.model('vips').find({name: hello}, callback);
}
// 添加 mongoose 静态方法,静态方法在Model层就能使用
vipSchema.statics.findbyage = function(age, callback) {
return this.model('vips').find({age: age}, callback);
}
//模型
var vipModel = mongoose.model('vips', vipSchema);
vipModel.find({name:"java"},function(error, result){
if(error) {
console.log(error);
} else {
console.log(result);
}
//关闭数据库链接
db.disconnect();
});
//基于实例方法的查询
var entity = new vipModel({"name":"java"});
entity.findByName("java",function(error, result){
if(error) {
console.log(error);
} else {
console.log(result);
}
//关闭数据库链接
db.disconnect();
});
//基于静态方法的查询
vipModel.findbyage(20, function(error, result){
if(error) {
console.log(error);
} else {
console.log(result);
}
//关闭数据库链接
db.disconnect();;
});
另一种查询:查询时不带回调
vipModel
.find({ occupation: /host/ })
.where('name.last').equals('Ghost')
.where('age').gt(17).lt(66)
.where('likes').in(['vaporizing', 'talking'])
.limit(10)
.sort('-occupation')
.select('name occupation')
.exec(callback);
如果不带callback,则返回query,query没有执行的预编译查询语句,该query对象执行的方法都将返回自己,只有在执行exec方法时才执行查询,而且必须有回调。
3.修改
//省略上面相同代码
......
// 修改记录
var conditions = {name : 'java'};
var update = {$set : {age : 27}};
var options = {upsert : true};
vipModel.update(conditions, update, options, function(error){
if(error) {
console.log(error);
} else {
console.log('update ok!');
}
//关闭数据库链接
db.disconnect();
});
4.删除
删除有2种方式,Entity和Model都使用remove方法
//省略上面相同代码
......
var conditions = {username: 'java'};
vipModel.remove(conditions, function(error){
if(error) {
console.log(error);
} else {
console.log('delete ok!');
}
//关闭数据库链接
db.disconnect();
});
Sub Docs
如同SQL数据库中2张表有主外关系,Mongoose将2个Document的嵌套叫做Sub-Docs(子文档)
简单的说就是一个Document嵌套另外一个Document或者Documents:
var ChildSchema1 = new Schema({name:String});
var ChildSchema2 = new Schema({name:String});
var ParentSchema = new Schema({
children1:ChildSchema1, //嵌套Document
children2:[ChildSchema2] //嵌套Documents
});
Sub-Docs享受和Documents一样的操作,但是Sub-Docs的操作都由父类去执行
var ParentModel = db.model('Parent',parentSchema);
var parent = new ParentModel({
children2:[{name:'c1'},{name:'c2'}]
});
parent.children2[0].name = 'd';
parent.save(callback);
parent在执行保存时,由于包含children2,他是一个数据库模型对象,因此会先保存chilren2[0]和chilren2[1]。
如果子文档在更新时出现错误,将直接报在父类文档中,可以这样处理:
ChildrenSchema.pre('save',function(next){
if('x' === this.name)
return next(new Error('#err:not-x'));
next();
});
var parent = new ParentModel({children1:{name:'not-x'}});
parent.save(function(err){
console.log(err.message); //#err:not-x
});
4.1 查询子文档
如果children是parent的子文档,可以通过如下方法查询到children
var child = parent.children.id(id);
4.2 新增、删除、更新
子文档是父文档的一个属性,因此按照属性的操作即可,不同的是在新增父类的时候,子文档是会被先加入进去的。
如果ChildrenSchema是临时的一个子文档,不作为数据库映射集合,可以这样:
var ParentSchema = new Schema({
children:{
name:String
}
});
//其实就是匿名混合模式
数据验证
数据的存储是需要验证的,不是什么数据都能往数据库里丢或者显示到客户端的,数据的验证需要记住以下规则:
- 验证始终定义在SchemaType中
- 验证是一个内部中间件
- 验证是在一个Document被保存时默认启用的,除非你关闭验证
- 验证是异步递归的,如果你的SubDoc验证失败,Document也将无法保存
- 验证并不关心错误类型,而通过ValidationError这个对象可以访问
7.1 验证器 - required 非空验证
- min/max 范围验证(边值验证)
- enum/match 枚举验证/匹配验证
- validate 自定义验证规则
以下是综合案例:
var PersonSchema = new Schema({
name:{
type:'String',
required:true //姓名非空
},
age:{
type:'Nunmer',
min:18, //年龄最小18
max:120 //年龄最大120
},
city:{
type:'String',
enum:['北京','上海'] //只能是北京、上海人
},
other:{
type:'String',
validate:[validator,err] //validator是一个验证函数,err是验证失败的错误信息
}
});
7.2 验证失败
如果验证失败,则会返回err信息,err是一个对象该对象属性如下
err.errors //错误集合(对象)
err.errors.color //错误属性(Schema的color属性)
err.errors.color.message //错误属性信息
err.errors.path //错误属性路径
err.errors.type //错误类型
err.name //错误名称
err.message //错误消息
一旦验证失败,Model和Entity都将具有和err一样的errors属性。
Model至Collection的命名策略
mongoose/lib/util.js模块中如下代码片段是集合命名的根源。
function pluralize (str) {
var rule, found;
if (!~uncountables.indexOf(str.toLowerCase())){
found = rules.filter(function(rule){
return str.match(rule[0]);
});
if (found[0]) return str.replace(found[0][0], found[0][1]);
}
return str;
};
1.判断模型名是否是不可数的,如果是直接返回模型名;否则进行复数转化正则匹配;
2.返回复数转化正则匹配结果(一个复数转化正则匹配是一个数组,有两个对象,[0]正则表达式,[1]匹配后处理结果);
3.如果复数转化正则匹配结果不存在,直接返回模型名;否则取匹配结果第一个,对模型名进行处理。(需要说明的是,rules是按特殊到一般的顺序排列的)