将js进行到底:node学习9
node.js数据库篇——Mongoose ODM
介绍mongoose
几乎所有的语言都有原生数据库连接驱动,这个我们上一回已经了解了,比如java的jdbc,mysql-connector,但是实际的开发中为了追求效率都不会使用原生的连接方式这么简单,至少我们也应该封装一个数据库连接文件,不应该每次操作都连接吧。为此,ORM,ODM诞生了,他们简化了数据库操作,将操作打包成简单的方法,并提供了数据的验证规则,安全保障等重要特性。
什么是ORM?
Object Relational Mapping缩写,是对象关系映射,面向对象语言的web开发中重要概念,产品上比如微软的ADO.NET的EntityFramework框架就是提供给ASP.NET开发的关系映射工具,还有笔者之前J2EE开发中SSH框架里的hibernate也是ORM产品,其实说白了就是将面向对象语言中的类(POJO类,那种只有属性和get,set的类)匹配到数据库的表,将实体化后的POJO对象匹配到数据库的一行数据,这样开发者只需要通过get,set对象里的属性,然后save,update一下,像hibernate这样的框架就自动使之对应到了数据库中的具体行,那么数据库就被修改了!这实现了强大的解耦合(开发语言中无需出现SQL语句),使开发者不必去做数据库管理员的事情(甚至不需要知道数据库语言),而专注于代码开发。ORM最大的缺点就是性能远远落后于原生SQL语句,因为像hibernate做的事情实际上就是封装了将对象映射为数据库的SQL语句罢了,永远是逃不过SQL语句操作数据库的。
什么是Mini ORM?
笔者接触mini orm是2015年在为自己创业公司开发ASP.NET平台接触到的,当时使用的产品是Petapoco。Mini orm的出现主要是弥补了ORM低性能的问题,可以理解为精简版的ORM,去除了一些复杂的映射方法,保留了基本的数据库操作方法,并且要求一些复杂的连接语句需要自己手动写SQL,也就是说开发时用基本方法+手动SQL混合式写法,这种ORM对开发者的要求要更高一些,但相比纯SQL还是要简单方便不少,可以说既保留了ORM的功能,同时提高了性能,介于SQL和ORM之间。
什么是ODM?
额,我也是第一次听说这个名词,Object Document Mapper的缩写,以前我只知道ORM,这里我探索了StackOverflow:
以上是别人询问ORM和ODM的区别的采纳回答,大意是:
mysql是关系型数据库,所以你需要用ORM工具将代码中的对象映射为数据库的数据
工具有:hibernate,entity framework,dapper等等
mongodb是文档类数据库,你需要用ODM来将你的代码中的对象翻译为数据库的数据。
mandango就是一个ODM工具
至于这个mandango ODM我看了下github官页说是专为php连接mongodb提供的ODM工具,就对应于node.js下的mongoose了。
什么是mongoose?
相比于原生的mongodb驱动器,mongoose做的第一件简化的事情就是假定了网站使用的是一个数据库,这在mysql中也是很常见的,我们一般一个网站只用一个库!这样整个应用只需要一次连接,处处可用,不必每次都去写连接方法!
然后,使用Mongoose无需担心数据库是否连接上了,他是先把数据库操作指令缓存起来,这就很像那些ORM产品一样,最后一次提交才算数,然后等到提交的时候一次连接数据库,将指令写入数据库。减少了连接次数,提高性能,简化操作验证。这样我们无需监听每一次的connection回掉函数了。
简单测试
package.json
{
"name":"mongoose-test",
"version":"0.0.1",
"description":"a mongoose example",
"dependencies":{
"mongoose":"latest"
}
}
查询数据
//引入mongoose模块
mongoose= require("mongoose");
//连接数据库,需要选择一个固定的库,之后不必再有连接操作
mongoose.connect('mongodb://localhost/test');
//定义模型样板
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var dataSchema = new Schema({
email:String,
password:String
});
//生成对象
//第一个参数就是集合名
var User = mongoose.model('User',dataSchema);
var name = {email:'devil'};
//根据name字段查询
User.find(name,function(err,data){
console.log(data);
console.log("\n")
});
var password = {password:'123456'};
//根据password字段查询
User.find(password,function(err,data){
console.log(data);
console.log("\n")
});
本次查询的数据就是上一章中构建的登录注册模块所使用的数据库集合User,第一次是查询email为“devil”的数据,第二次是查询密码为“123456”的数据。
新增数据
//引入mongoose模块
mongoose= require("mongoose");
//连接数据库,需要选择一个固定的库,之后不必再有连接操作
mongoose.connect('mongodb://localhost/test');
//定义模型样板
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var dataSchema = new Schema({
email:String,
password:String
});
//生成对象
//第一个参数就是集合名
var User = mongoose.model('User',dataSchema);
var save = {email:"devilyouwei@foxmail.com",password:"zzzz"};
var user = new User(save);//生成新对象,就是对应数据集合中一条新数据
//保存这个新对象,就是插入操作
user.save(function(err){
console.log("already saved");
})
本次操作通过模型的save方法存储了一行数据
更新操作
//引入mongoose模块
mongoose= require("mongoose");
//连接数据库,需要选择一个固定的库,之后不必再有连接操作
mongoose.connect('mongodb://localhost/test');
//定义模型样板
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var dataSchema = new Schema({
email:String,
password:String
});
//生成对象
//第一个参数就是集合名
var User = mongoose.model('User',dataSchema);
User.update({email:"devil"},{password:"hello"},function(err){
console.log("password already changed");
})
本次操作将email为“devil”的用户,密码修改为“hello”。
删除操作
//引入mongoose模块
mongoose= require("mongoose");
//连接数据库,需要选择一个固定的库,之后不必再有连接操作
mongoose.connect('mongodb://localhost/test');
//定义模型样板
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var dataSchema = new Schema({
email:String,
password:String
});
//生成对象
//第一个参数就是集合名
var User = mongoose.model('User',dataSchema);
User.remove({email:"devilyouwei@gmail.com"},function(err){
console.log("devilyouwei@gmail.com already removed");
})
这里我们删除email为“devilyouwei@gmail.com”的数据
解析套路
使用mongoose对数据库进行增删改查的基本套路是:
- require mongoose模块进来,返回mongoose对象
- 使用mongoose对象connect进行连接操作,参数为连接套接字默认是:mongodb://localhost/数据库
- 定义模型样本——schema
- 根据schema获得对应集合的model,这一步很关键,获得了模型
- 使用find,remove,save,update对数据库进行基本操作
注意1
注:其中的save方法需要单独解释,我们发现,除了save其他三个没有new Model()操作,也就是不需要生成对象,使用的是静态方法(java中称类方法),比如find:
//生成对象
//第一个参数就是集合名
var User = mongoose.model('User',dataSchema);
var name = {email:'devil'};
//根据name字段查询
User.find(name,function(err,data){
console.log(data);
console.log("\n")
});
因为他们(find,remove,update)都是对集合中已经有的数据进行改删查操作,唯独,save是新增操作,必须生成新的对象,故而,save方法是基于新对象下的方法,需要new对象这一步操作:
var save = {email:"devilyouwei@foxmail.com",password:"zzzz"};
var user = new User(save); //重点:生成新对象,就是对应数据集合中一条新数据
//保存这个新对象,就是插入操作
user.save(function(err){
console.log("already saved");
})
注意到上述代码使用了new 模型这种骚操作,并设置的对应属性,这有点像java hibernate下的new对象然后使用set方法,或者直接构造方法传入数据,最后save一下就保存到数据库的操作,ODM和ORM的基本操作十分类似,可以说ODM是专为mongo这样的数据库而生的“特殊ORM”。
注意2
关于schema是个什么?
记住,他并不是模型,要理解schema,可以把它想象成模型的原型(js中叫做prototype),就是model的model(模型的模板),这个schema应当和数据库集合的属性一一对应,集合(表)中有name,那么schema在规定的时候就要有name,否则回误以为是新的集合或者报错。schema用于描述模型的样子以及模型是如何工作的,数据库的操作是发生在模型上的而不是schema上的。
注意3
关于model与数据库集合的对应关系。
首先要获得model,需要通过mongoose.model方法,并且传入集合名,schema。例如:
var dataSchema = new Schema({
email:String,
password:String
});
var User = mongoose.model('User',dataSchema);
User就是一个模型,model方法传入第一个是模型名称,如果model方法没有第三个参数,那么默认第一个参数与数据库集合名对应,注意,默认指定会全部被转换为小写+复数形式,比如这里是User,数据库中的集合叫做users!
注意4
设定默认值,防止空字符串
var dataSchema = new Schema({
email:{type:String,default:"example@gmail.com"},
password:{type:String,default:"123456"}
});
更多常用操作方法
SQL语句丰富多彩,有很多关键词,where,limit,like等等。我以为mongodb不是关系数据库可能不存在或者不太一样,事实上mongoose也有这些关键词,并且是使用类似Thinkphp那样的链式操作。
使用链式操作查询数据
//引入mongoose模块
mongoose= require("mongoose");
//连接数据库,需要选择一个固定的库,之后不必再有连接操作
mongoose.connect('mongodb://localhost/test');
//定义模型样板
var Schema = mongoose.Schema;
var userSchema = new Schema({
email:String,
password:String
});
//生成对象
//第一个参数就是集合名
var User = mongoose.model('User',userSchema);
User.where('email').eq('devil').exec(function(err,data){
console.log(data);
});
前面的步骤都一样,只是利用了链式操作,把条件依次链接起来,最终使用exec执行整个链。
注意上述代码where和eq组合可以只用find代替:
var User = mongoose.model('User',userSchema);
User.find({email:"devil"},function(err,data){
console.log(data);
}
这段链更加简单,只需要一个find即可
一些常用的链式方法
链式操作的方法名和SQL语言的关键词很相似,基本就是同名同义了
大于小于
var data = User.where('email').gt(1).find(function(err,data){
console.log(data);
})
gt大于,lt小于,eq等于,neq不等于
注意:使用find,exec的效果测试下来效果一样
Like语句
var data = User.where('email').regex(/mail/).find(function(err,data){
console.log(data);
})
在mongoose中并没有找到Like关键词,要模糊查询需要用到正则表达式。regex方法里面可以输入正则表达式。
以上代码用于查找email中带有“mail”字样的行。
count计数
User.find({'email':/mail/}).count(function(err,data){
console.log(data);
})
统计带有“mail”的email的行数,在count中传入回掉函数即可
limit限制
User.find().limit(1).exec(function(err,data){
console.log(data);
})
由于limit必须是基于已经查询好的数据行中限制行数,所以必须在find之后,而limit本身不能传入回调函数,故而可以在最后使用exec执行,exec代表最终执行之前所有的链式方法。否则也没有别的办法取得链式之后想要的数据了。
注:对于那些(比如上述limit)不能提供回调函数,那么直到调用exec才会执行
select选择字段
User.find({email:"devil"}).select("email").exec(function(err,data){
console.log(data);
})
以上代码代表只看email字段,并且select也必须在find之后执行。
sort排序
首先搞清楚SQL中,asc升序,desc降序
//desc
User.find().sort("-email").exec(function(err,data){
console.log(data);
})
//asc
User.find().sort("email").exec(function(err,data){
console.log(data);
})
上面是按照email排序,前面加了负号就是降序,不加负号就是升序。
skip跳过就不说了。
总结:find是找到数据,select,limit,sort,skip等都是筛选操作,都必须在find之后执行,否则要报找不到func错误!
另外select,limit,sort,skip并不能传入回调函数,所以最终获取数据都必须依靠最后一次exec传入回调函数获取!