需求还是基于前面的演示程序,只不过,我们把mysql换成了mongodb,因此,我们把以前的orm2插件更换成mongoose插件。
首先得下载mongodb文件,并启动服务器,这个过程大家可以参考我前面的spring data操作mongodb的文章。
在node.js项目中,我们需要先定义对象结构,建议大家把系统所有的对象结构定义到一个文件中,如果系统过大,可考虑按功能模块定义到多个文件中,我把对象结构定义到一个文件中,models.js,代码如下:
/**
* 定义所有的对象结构,并通过exports向外导出.
* User: ljh
* Date: 13-8-13
* Time: 下午4:44
*/
var mongoose = require('mongoose');
//连接是如何管理的,是否在这连接,有待深入研究???
mongoose.connect('mongodb://localhost/mydb');
var Schema = mongoose.Schema;
//定义对象结构
var userSchema = new Schema({
loginCode:{type:String},
userName:{type:String},
password:{type:String},
picPath:{type:String},
birthDay:{type:Date}
});
//定义集合,其中myUser是数据集的名称
exports.User = mongoose.model("myUser", userSchema);
系统中需要操作user的地方,都需要引入models.js文件,例如:
var models = require("../commons/models");
var User = models.User;
下面是userAction.js中的核心CRUD代码,相对于orm2的api函数,mongoose的函数设计更为简洁、强大。
//首页
exports.index = function(req, res){
//无缓存,应尽量避免以这种方式发回文件
res.sendfile("public\\login.html");
};
//添加用户
exports.addUser = function(req, res) {
var picPath = req.body.pic;
if (!picPath || picPath.trim()=="") {
picPath = "upload/photo.jpg";
}
var user = new User({
userName:req.body.name,
loginCode:req.body.loginCode,
birthDay:req.body.birthday,
password:req.body.password,
picPath:picPath
});
user.save(function(err, u) {
if (err) {
var result = {'code':0,'msg':'添加用户失败:' + err};
res.json(result);
} else {
var result = {'code':1,'msg':'添加用户成功!'};
res.json(result);
}
});
}
//批量删除用户 (uids为id字串,用-拼接,比如:1-3-4-5 )
exports.deleteUsers = function(req, res) {
User.remove({ _id: { $in: req.params.uids.split("-")}}, function (err) {
if (err) {
var result = {'code':0,'msg':'批量删除用户失败:' + err};
res.json(result);
} else {
var result = {'code':1,'msg':'批量删除用户成功!'};
res.json(result);
}
});
}
//删除用户
exports.deleteUser = function(req, res) {
User.remove({_id:req.params.uid}, function(err) {
if (err) {
var result = {'code':0,'msg':'删除用户失败:' + err};
res.json(result);
} else {
var result = {'code':1,'msg':'删除用户成功!'};
res.json(result);
}
}) ;
}
//修改用户
exports.modifyUser = function(req, res) {
console.log("uid:" + req.params.uid);
User.findById(req.params.uid, function(err, user){
if (err) {
commonsFun.handleError(err,"修改用户时查询用户失败!", res);
return;
}
if (user) {
user.userName = req.body.userName;
if (req.body.password && req.body.password != "***" && req.body.password.trim() != "") {
user.password = req.body.password;
}
user.birthDay = req.body.birthday;
user.picPath = req.body.pic;
console.log(user);
user.save(function(err){
if (err) {
var result = {'code':0,'msg':'修改用户失败:' + err};
res.json(result);
} else {
var result = {'code':1,'msg':'修改用户成功!'};
res.json(result);
}
});
} else {
var result = {'code':0,'msg':'要修改的用户可能不存在!'};
res.json(result);
}
});
}
//根据id查询用户
exports.getUserById = function(req, res) {
var uid = req.params.uid;
var result = {'code':0,'msg':'查询用户失败!'};
User.findById(uid, "_id loginCode userName picPath birthDay" ,function(err, user){
if (err) {
commonsFun.handleError(err,"查询用户失败!", res);
return;
}
if (user) {
var result = {'code':1,'data':user};
res.json(result);
} else {
res.json(result);
}
})
}
//显示用户分页列表
exports.user_list = function(req, res){
var pno = req.params.pno;
var queryName = req.query.q_name;
var pageSize = 3;
var queryParm = {};
//进行多条件的拼装
if (queryName && queryName.trim() != "") {
//如果用户输入了姓名
queryParm.userName = new RegExp(queryName);
}
User.find(queryParm).count(function(err, userCount){
if (err) {
commonsFun.handleError(err, '取用户分页数据失败!' , res);
return;
};
if (userCount > 0 ) {
//查到了数据
User.find(queryParm)
.limit(pageSize)
.skip((pno - 1)*pageSize)
.select("_id loginCode userName picPath birthDay")
.sort("loginCode")
.exec(function(err, users) {
if (err) {
commonsFun.handleError(err, '取用户分页数据失败!' , res);
return;
};
var p = new Page(pno, pageSize, userCount, users);
var result = {'code':1,'page':p};
res.json(result);
});
} else {
//没有查到数据
var p = new Page(pno, pageSize, 0, []);
var result = {'code':1,'page':p};
res.json(result);
}
});
};
//得到登录用户状态
exports.getState = function(req, res){
var user = req.session.user_key;
var resultJson ;
if (user) {
resultJson = {code:1,name:user.userName};
} else {
resultJson = {code:0,msg:'对不起,您没有登录!'};
}
res.json(resultJson);
};
//退出系统(这个方法应该返回json,我这仅仅为演示,直接返回登录页面了)
exports.logout = function(req, res){
req.session.user_key = null;
res.redirect("/")
}
//登录系统
exports.login = function(req, res){
//express中得到参数req.body.uname或req.query.uname,其中,req.body可支持post,delete,put等方式
console.log("user login, name is:" + req.query.uname + ",pwd is:" + req.query.pwd);
var loginUser;
User.findOne({loginCode:req.query.uname},function(err, user) {
if (err) {commonsFun.handleError(err)};
if (user && user.password == req.query.pwd) {
req.session.user_key = user;
res.send(JSON.stringify({code:1,name:user.userName}));
return ;
}
res.send(JSON.stringify({code:0,msg:'用户名或密码错误,请核对!'}));
});
};
基本代码和之前的orm2中基本类似,前台的html及js代码,没有做改动,大家在使用mongoose时,应注意下面一些常用的操作:
1、建立表空间,其中myUser是表空间名称
var User = mongoose.model("myUser", userSchema);
userSchema是对象结构,通过如下方式定义:
var userSchema = new Schema({
userName:{type:String},
birthDay:{type:Date}
});
但也可以这样定义:
var User = mongoose.model("myUser",{userName:{type:String},birthDay:{type:Date}});
建立大家采用单独定义的方式,以增强可维护性。
2、关于查询
A、查单对象
User.findOne({userName:'张三'})
.exec(function(err, user){
....
或者根据id来查询:
User.findById("52089d9a10986c140b000001", "_id loginCode userName picPath birthDay" ,function(err, user){....
其中,第二个参数是需要的对象属性,这比起orm2来说,方便了许多。
你也可以对查询出来的对象直接操作,findByIdAndUpdate,findByIdAndRemove都可以做这样的操作。
B、条件查询
User.find({password:"123",userName:"张三"}).exec....
上面的代码,是按给出的条件查询数据。
当然,你也可以用另一种方式来实现,比如:
.where("userName").equals("张三2")
如果想使用模糊查询,或其它更复杂的条件查询,可以使用正则,示例代码如下:
User.find({userName:new RegExp("^" + reg_str)})
如果不需要条件拼接,可以简单写成如下:
User.find({userName:{ $regex: /张/ },picPath:{$regex:/18/} })
在正则中:/^表示以什么开头,$/表示以什么结尾
mongoose中提供的另一个正则方法,我没有测试出效果,知道的同学可以告诉我一声:
.regex({userName:{$regex: /^张.*/ }}),这可怎么也运行不出来数据呢?呵呵
C、排序、分页、统计
排序:
.sort({userName:"desc"}),可以添加多个字段,asc为升序。
分页:
.skip(100).limit(20),其中skip的下标从0开始。
统计总记录条数:
count(function(err, rowCounts){})
详细实现,请大家参考上面的用户分页实现代码。
D、只需要部分属性
如果只需要部分属性,返回给前端,我们可以使用的两种方式,第一种是根据前端需要的字段手工构造这样的对象,另一种是在查询的时候,指定所需要的字段。比如:.select("userName birthDay __v"),或都某些查询函数可以指定所需要的属性。
3、新增、修改、删除
新增:
var user = new User({
userName:req.body.name
});
user.save(function(err, u)
修改:
User.update({password:"123"},{password:"888"},{ multi: true },function(err, counts){
if (!err) {
console.log("modify success, rows:" + counts) ;
} else {
console.log(err);
}
});
请注意如果要进行批量操作,设置multi为ture,否则,只会对第一条数据进行操作。
查是,对一个对象实例进行update,mongoose会报错,_id don't allow modify,原因是什么我没有深入研究,如果遇到这样的错误,大家可直接用save保存即可,具体请参阅上面的修改用户代码。
删除按api操作即可,没有太多要讲的。
4、对象关系的保存
有两种设计方案:
A、将对象直接保存到聚合根对象中
B、采用类似于关于数据库中的外键方式保存
我更偏向于第二种方案,这样做似乎更有利于查询的构建,当然,如果子对象是一个值对象,就可以用第一种方案了。示例代码如下,客户一对多订单:
A方案是这样定义的:
var customerSchema = new Schema({
name:String,
gender:Boolean,
orders:[{
orderNo:String,
price:Number,
amount:Number,
customer:{type:Schema.Types.ObjectId, ref:"customer"}
}]
});
var Customer = mongoose.model("customer",customerSchema);
B方案定义:
var customerSchema = new Schema({
name:String,
gender:Boolean,
orders:[{type:Schema.Types.ObjectId, ref:"order"}]
});
var orderSchema = new Schema({
orderNo:String,
price:Number,
amount:Number,
customer:{type:Schema.Types.ObjectId, ref:"customer"}
});
var Customer = mongoose.model("customer",customerSchema);
var Order = mongoose.model("order",orderSchema);
然而,在B方案中,当对象关系发生变化后,需要我们手工去更改属性,并保存,这点比起java世界中的hibernate要逊色很多。
如果在查询时,把关联对象也实例化,要用到populate,比如:
Order.findOne({"customer":"5209e14f38b0a28003000001"})
.populate("customer", "name gender").exec...
5、关于乐观锁
mongoose会自动为每一个对象生成__v字段,用来保存版本号,但实际操作时,并没有发现对版本号字段进行自动累加,知道的同学请告诉我,目前只有暂时用下面的办法去做:
关于version锁:
User.findById("52089d9a10986c140b000001",function(err, user){
user.password = "222";
user.increment();
user.save(function(err){
console.log("save ok!");
});
console.log(user);
});
注意红色那句代码,这而似乎只有手工去增加,但不知道后面在update的时候,是否会做为where条件加上去。
最全的api,大家请去http://mongoosejs.com/docs/api.html查看,好了,这一版本的demo先到些,下一版本,我将把前台的展示更换为angularjs。