春節前閒來無事,整理了一下以前學習的Node.js和NoSql數據庫的資料,總結了自己一路踩過來的坑,希望能對初學者有幫助。
在開發環境搭建和實例編寫之前,還是需要先了解一下基本概念。
參考文章:
2. 深入瞭解MongoDB的mmap(內存映射文件)的原理
3. MongoDB數據庫命令
4. Node.js的概念
長篇大論的概念我就不復述了,用幾句簡單的話來概括一下重點:
1. NoSql數據庫
NoSql=not only sql,與傳統關係型數據庫的最大區別在於,不能JOIN,且保存的數據沒有固定的格式,常見的如MongoDB以key-value形式進行存儲。
與傳統數據庫如MySql是硬盤數據庫+關係型數據庫相反,NoSql是內存數據庫+非關係型數據庫。
硬盤數據庫的數據可靠性高,因爲數據是直接寫入硬盤。內存數據庫讀寫速度更快,因爲是直接通過內存進行存取,但是需要定期寫回到硬盤,這就導致在存入內存到寫入硬盤的時間內,如果系統崩潰,會導致數據丟失,可靠性會降低,雖然有通過日誌在啓動後進行恢復的方法,但是多少還是會影響體驗。
所以到底是用關係型數據庫還是NoSql數據庫,還是要根據場景進行分析,不能一概而論。
2. MongoDB
內存映射文件是OS通過mmap在內存中創建一個文件,並直接映射到虛擬內存。在數據操作的時候,OS會把需要操作的數據直接映射到物理內存中。
MongoDB通過journal進行故障恢復和持久化,系統在內存中分配一塊區域給journal使用,稱爲private view。每隔100ms會刷新privateview到journal。因此發生故障時,丟失的也只是100ms的數據而已。但是開啓journal後,虛擬內存的使用也會倍增。
3. Node.js
Node是服務器程序。本身運行的是Google的V8引擎,用於解釋Javascript。它可以直接用Javascript寫後臺程序,使前臺開發者也能快速開發後臺代碼,即HTML+Javascript+MongoDB,而無需像Tomcat的前臺(HTML+Javascript)+後臺(Java)+數據庫(MySql)。
與Java等不同的是它連接到服務器的方式:
Java是多線程的,一個連接就是一個新線程,每個線程需要2M的配套內存的話,8G內存就只能維持4000個用戶,想要達到更高的連接數只能增加增加服務器。
Node.js是單線程的,它支持數萬的併發,在處理非計算型高密集I/O請求時,可以有更好的表現。
爲什麼是非計算型?
因爲Node.js雖然稱爲單線程,但並不意味着只有一個線程在進行處理,單的是主線程,還有其他如Ajax線程,MongoDB線程進行其他的處理。
如當主線程調用Ajax線程時,主線程並不是等待Ajax線程返回結果,而是直接跳到下一步操作,Ajax線程結束後會去執行它的回調函數,這是同步和異步的差異。
此時,主線程可以去做其他的請求。
借一張圖來說明處理的流程:
但是如果主線程的計算量很大,由於是單線程的關係,其他請求就無法進入主線程了。
爲什麼用內存數據庫(MongoDB)?
用Node.js就是追求性能和併發,而傳統的關係型數據庫(如MySql)在讀寫性能方面不如內存數據庫,如果是Node.js+關係型數據庫的話,數據庫這邊勢必將稱爲I/O的瓶頸。
--------------------------------------------------------------------------------分割線-----------------------------------------------------------------------------------
接下來是環境搭建和實例
一. MongoDB環境搭建:
1. MongoDB下載地址:http://dl.mongodb.org/dl/win32/x86_64
2. 解壓在任意路徑, D:\mongodb
3. 在mongodb目錄下創建data文件夾,在data文件夾下創建db和log文件夾,在log文件夾下創建MongoDB.log文件。
目錄結構如下:
4. 在cmd下進行安裝:
d:\mongodb\bin>mongod -dbpath "d:\mongodb\data\db"
看到
It looks like you are trying to access MongoDB over HTTP on the native driver port.
說明已經安裝成功了。
6.DB操作:
雙擊bin下的mongod.exe(啓動mongodb)
然後運行mongo.exe,界面如下:
7. MongoDB中的collection相當於table,與關係型數據庫的db和table不同的是,mongodb的db和collection如果不存在的話,會自動創建,而無需create table。
簡單介紹幾個命令:
7.1 指定當前DB:use {dbName};
如:use sun;(指定sun爲當前db)
7.2 查詢collection下的所有數據:db.{collection}.find();
如:db.test.find();(顯示test這個collection下所有的數據)
7.3 刪除當前數據庫:db.dropDatabase();(注意是當前庫哦!)
7.4 刪除指定collection:db.{collection}.drop();
7.5 顯示所有db:show dbs;
7.6 顯示當前db下的所有collection:show collections;
7.7 插入數據:db.{collection}.insert({'{key1}':'{value1}', '{key2}':'{value2}'});
如:db.test.insert({'name':'aaa', 'age':10});
7.8 更新數據:db.{collection}.update({'{type1}':'{value1}'}, {$set:{'{type2}':'{value2}'}});
如:db.test.update({'name':'aaa'}, {$set, {'age':20}});
7.9 刪除數據:db.{collection}.remove({'{key1}':'{value1}', '{key2}':'{value2}'});
如:db.test.remove({'name':'aaa'});
7.10 查詢指定條件的數據:db.{collection}.find({'{key1}':'{value1}', '{key2}':'{value2}'});
如:db.test.find({'name':'aaa'});
其他命令可查詢Mongo教程
例:
二. 開發環境WebStorm搭建:
去官網下載最新版的WebStorm並安裝
三. Node.js環境搭建:
去官網下載最新的Node.js並安裝
四. 創建Node.js+Express項目並啓動服務器:
1. 啓動WebStorm,File->New->Project,選擇Node.js Express App,Location是項目路徑,Template是頁面模板和解析引擎。
2. 創建後的項目結構如下(下圖有我增加的文件和文件夾,但是整體結構改動不大),可直接在左下角Run窗口按綠三角啓動,顯示如下圖的文字則表示服務啓動成功。
3. 輸入http://localhost:3000/,顯示如下(顯示內容我也改成自己的了):
4. 至此,簡單的Express項目創建成功,但是有些文件的內容還是需要說明一下。
bin\www:
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app'); // 服務啓動後,需要用到的Module在app下面定義
var debug = require('debug')('nodejsproject:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000'); // 端口3000就是在這裏定義的
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
app.js:(裏面有些代碼是後面例子中用到的,先放着)
var express = require('express'); // require類似於Java的import,需要用到其他包裏面的東西,就把它引用過來
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
///=======路由信息 (接口地址)開始 存放在./routes目錄下===========//
var index = require('./routes/index'); //home page接口
var users = require('./routes/users'); //用戶接口
var sun = require('./routes/sun'); //sun接口
var mongoDelete = require('./routes/mongodb/delete'); //mongo接口
var mongoFind = require('./routes/mongodb/find'); //mongo接口
var mongoInsert = require('./routes/mongodb/insert'); //mongo接口
var mongoUpdate = require('./routes/mongodb/update'); //mongo接口
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index); //在app中註冊routes該接口,當輸入根目錄(http://localhost:3000)的時候,調用index.js
app.use('/users', users); //在app中註冊users接口
app.use('/sun', sun); //在app中註冊sun接口
app.use('/mongo', mongoDelete); //在app中註冊mongo接口
app.use('/mongo', mongoFind); //在app中註冊mongo接口
app.use('/mongo', mongoInsert); //在app中註冊mongo接口
app.use('/mongo', mongoUpdate); //在app中註冊mongo接口
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
package.json:這個文件定義了引用的模塊的信息,比如如果引用到了mongodb則這裏必須添加,否則會報錯。
{
"name": "nodejsproject",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"assert": "^1.4.1",
"async": "^2.6.0",
"body-parser": "~1.18.2",
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"ejs": "~2.5.7",
"express": "~4.15.5",
"mongodb": "~3.0.2",
"morgan": "~1.9.0",
"serve-favicon": "~2.4.5"
}
}
既然說到了引用Module,那就順便把模塊的添加也說一下,比如添加mongodb模塊,命令:
npm install --save -dev mongodb
“--save -dev”的用處是把引用模塊的信息在安裝成功後自動寫入package.json,如果你想自己寫,那麼只要用
npm install mongodb
即可。
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
index.js:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Sun test Express' }); // 打開index.ejs頁面,並將指定內容賦予變量title
});
module.exports = router;
五. 創建一個Node.js的接口:
1. 創建user.js,定義user對象:
function User() {
this.name;
this.city;
this.age;
}
module.exports = User;
2. users.js:
var URL = require('url');
var User = require('./user'); // 用到其他文件的時候,必須require引用過來
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
//http://localhost:3000/users/getUserInfo?id=1
router.get('/getUserInfo', function(req, res, next){
var user = new User();
var params = URL.parse(req.url, true).query;
if (params.id = '1'){
user.name = "test";
user.age = "10";
user.city = "上海";
}else{
user.name = "sun";
user.age = "20";
user.city = "東京";
}
var response = {status:1, data:user};
res.send(JSON.stringify(response));
})
module.exports = router;
3. 在app.js中,不要忘了聲明用到的模塊:
var users = require('./routes/users'); //用戶接口
app.use('/users', users); //在app中註冊users接口
4. 在index.ejs下,添加:
後臺接口調用:<a href="http://localhost:3000/users/getUserInfo?id=1">getUserInfo</a>
5. 在畫面中點擊getUserInfo鏈接,可以看到返回結果:
六. 使用Node.js+MongoDB,實現增刪改查:
這個例子是基於上面的接口實例,通過前臺頁面使用jquery異步調用node.js接口的方式實現的。
1. 項目結構說明:
先說明一下MongoDB的結構,
DB名:sun
Collection有兩個,test和other,test中保存name和age,other中保存name和job。
把他們分開是爲了說明async.map的時候更方便。
這裏會詳細說明一下查詢find的實現,增刪改相對容易。
在創建Collection的時候,可以指定唯一索引。默認是隻有id唯一。
db.collection.createIndex( <key and index type specification>, { unique: true } )
例:(將test中的name設爲唯一索引)
db.test.createIndex({name:1},{unique:true})
下面是兩個Collection裏面準備的數據,在新增做好以後就可以自己插入了。
項目結構如下:
新增:
routes/mongodb/find.js <- 查詢
routes/mongodb/insert.js <- 新增
routes/mongodb/update.js <-更新
routes/mongodb/delete.js <-刪除
routes/mongodb/collectionTest.js <-Collection對象
routest/sun.js <-sun.ejs對應的js文件(類似於index.js)
views/sun.ejs <-頁面(類似於index.ejs)
2. collectionTest.js:
function CollectionTest() {
this.id;
this.name;
this.age;
this.job;
}
module.exports = CollectionTest;
3. find.js:
var mongodb = require('mongodb')
var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/sun';
var URL = require('url');
var CollectionTest = require('./collectionTest');
var express = require('express');
var router = express.Router();
var async = require('async');
var selectData = function(client, params, callback) {
//連接到表
var db = client.db('sun');
var collectionTest = db.collection('test');
var collectionOther = db.collection('other');
//查詢數據
var whereStr;
if (params.name) whereStr = {"name" : params.name};
collectionTest.find(whereStr, function(error, results){
if (error) throw error;
results.toArray(function(err, arr){
if (error) throw error;
// async.map會將循環處理完以後,統一執行callback而不像其他異步調用執行分別執行
async.map(arr, function(item, callback){
var whereStr = {"name": item.name};
collectionOther.find(whereStr, function(error, result){
result.toArray(function(err, arrJob){
console.log(arrJob);
console.log("------------------------");
if (arrJob){
item.job = arrJob[0].job;
console.log(item);
console.log("*************************");
}
callback(null, item);
});
})
}, function(err, result){
// result是上表面item組成的數組
callback(result);
})
});
});
}
router.get('/find', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("連接成功!");
selectData(client, params, function(result) {
res.send(JSON.stringify(result));
client.close();
console.log("連接斷開!");
});
});
})
module.exports = router;
增刪改和頁面的jquery之類的沒啥好說的,跟前臺開發差不多,詳細說一下在find.js中遇到的問題。
初用nodes.js的時候可能對異步調用的處理順序有點不太習慣。
比如我在接口中,一開始是這樣寫的:
router.get('/find', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("連接成功!");
selectData(client, params, function(result) {
});
});
res.send(JSON.stringify(result));
client.close();
console.log("連接斷開!");
})
想象中的輸出結果:
連接成功!
連接斷開!
連接斷開!
連接成功!
由此想到,原來node.js在調用其他線程時,不是等待他們執行完,而是做自己的下一步,等到其他線程的處理完成後,再去調用回調函數。
簡單的處理當然沒問題,但是遇到複雜的循環調用其他線程時,就很麻煩了。
比方說在find.js中,我需要將test中的name和age取到以後,再循環所有數據,以name爲key,去檢索other並取出job後一起返回,不可能是每次檢索都去調用callback回調函數。
因此我用到了async中的map函數。
安裝async的命令:
npm install --save -dev async;
async庫的命令的解析可以參照這篇文章:http://blog.csdn.net/dai_jing/article/details/47058579
我這裏用到了map方法,map內循環的結果以數組形式保存後,一併返回給callback。
async.map的使用:
// async.map會將循環處理完以後,統一執行callback而不像其他異步調用執行分別執行
async.map(arr, function(item, callback){
var whereStr = {"name": item.name};
collectionOther.find(whereStr, function(error, result){
result.toArray(function(err, arrJob){
console.log(arrJob);
console.log("------------------------");
if (arrJob){
item.job = arrJob[0].job;
console.log(item);
console.log("*************************");
}
callback(null, item);
});
})
}, function(err, result){
// result是上表面item組成的數組
callback(result);
})
4. insert.js:
var mongodb = require('mongodb')
var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/sun';
var URL = require('url');
var express = require('express');
var router = express.Router();
function insertData(client, params, callback)
{
var db = client.db("sun");
var connectionTest = db.collection("test");
var testData = {"name":params.name,"age":params.age};
connectionTest.insert(testData,function(error, result){
if(error){
console.log('Error:'+ error);
}else{
console.log(result.result.n);
}
callback(result.result.n);
});
var connectionOther = db.collection("other");
var otherData = {"name":params.name,"job":params.job};
connectionOther.insert(otherData,function(error, result){
if(error){
console.log('Error:'+ error);
}else{
console.log(result.result.n);
}
});
}
router.get('/insert', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("連接成功!");
insertData(client, params, function(result) {
res.send("Delete Success:" + result);
client.close();
console.log("連接斷開!");
});
});
})
module.exports = router;
5. update.js:
var mongodb = require('mongodb')
var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/sun';
var URL = require('url');
var express = require('express');
var router = express.Router();
function updateData(client, params, callback)
{
var db = client.db('sun');
var connectionTest = db.collection('test');
var whereData = {"name":params.oldName}
var updateDat = {$set: {"name":params.newName}}; //如果不用$set,替換整條數據
connectionTest.update(whereData, updateDat, function(error, result){
if (error) {
console.log('Error:'+ error);
}else{
console.log(result);
}
callback(result.result.n);
});
var connectionOther = db.collection('other');
connectionOther.update(whereData, updateDat, function(error, result){
if (error) {
console.log('Error:'+ error);
}else{
console.log(result);
}
});
}
router.get('/update', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("連接成功!");
updateData(client, params, function(result) {
res.send("Delete Success:" + result);
client.close();
console.log("連接斷開!");
});
});
})
module.exports = router;
6. delete.js:
var mongodb = require('mongodb')
var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/sun';
var URL = require('url');
var express = require('express');
var router = express.Router();
function deleteData(client, params, callback)
{
var db = client.db('sun');
var devices = db.collection('test');
var data = {"name":params.name};
devices.remove(data, function(error, result){
if (error) {
console.log('Error:'+ error);
}else{
console.log(result.result.n);
}
callback(result.result.n);
})
}
router.get('/delete', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("連接成功!");
deleteData(client, params, function(result) {
res.send("Delete Success:" + result);
client.close();
console.log("連接斷開!");
});
});
})
module.exports = router;
7. sun.js:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('sun', { title: 'Sun'});
});
module.exports = router;
8. sun.ejs:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
<script src="https://cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
function mongoInsert() {
$.ajax({
url: "http://localhost:3000/mongo/insert",//接口服務器地址
dataType:"text",
type:"get",
data:{"name":document.getElementById("insertName").value,
"age":document.getElementById("insertAge").value,
"job":document.getElementById("insertJob").value},//請求數據
success:function(data){
alert("插入成功");
},
error:function(e){
alert("Error:" + e.status+','+ e.statusText);
}
})
}
function mongoUpdate() {
$.ajax({
url: "http://localhost:3000/mongo/update",//接口服務器地址
dataType:"text",
type:"get",
data:{"oldName":document.getElementById("oldName").value,
"newName":document.getElementById("newName").value},//請求數據
success:function(data){
alert("更新成功");
},
error:function(e){
alert("Error:" + e.status+','+ e.statusText);
}
})
}
function mongoDelete() {
$.ajax({
url: "http://localhost:3000/mongo/delete",//接口服務器地址
dataType:"text",
type:"get",
data:{"name":document.getElementById("deleteName").value},//請求數據
success:function(data){
alert("刪除成功");
},
error:function(e){
alert("Error:" + e.status+','+ e.statusText);
}
})
}
function mongoFind() {
$.ajax({
url: "http://localhost:3000/mongo/find",//接口服務器地址
dataType:"text",
type:"get",
data:{"name":document.getElementById("findName").value},//請求數據
success:function(data){
var jsonData = $.parseJSON(data);
document.getElementById("divList").innerHTML = "<br>Result:<br>----------------<br>";
for (i=0;i<jsonData.length;i++){
document.getElementById("divList").innerHTML += "Name:" + jsonData[i].name
+ "<br>Age:" + jsonData[i].age
+ "<br>Job:" + jsonData[i].job
+ "<br>----------------<br>";
}
},
error:function(e){
document.getElementById("divList").innerHTML = "Error:" + e.status+','+ e.statusText;
}
})
}
</script>
</head>
<body>
<h1><%= title %>'s Test for nodeJS + MongoDB + Express + Async</h1>
<p>Welcome to <%= title %></p>
<ul>
<li style="margin-bottom:10px">
後臺接口調用:<a href="http://localhost:3000/users/getUserInfo?id=1">getUserInfo</a>
</li>
<li style="margin-bottom:10px">
MongoDB新增數據:Name:<input id="insertName"> Age:<input id="insertAge"> Job:<input id="insertJob"> <button id="btnInsert" onclick="mongoInsert();">新增</button>
</li>
<li style="margin-bottom:10px">
MongoDB修改數據:NameOld:<input id="oldName"> NameNew:<input id="newName"> <button id="btnUpdate" onclick="mongoUpdate();">修改</button>
</li>
<li style="margin-bottom:10px">
MongoDB刪除數據:Name:<input id="deleteName"> <button id="btnDelete" onclick="mongoDelete();">刪除</button>
</li>
<li>
從MongoDB檢索數據:Name:<input id="findName"> <button id="btnFind" onclick="mongoFind();">檢索</button><a href="http://localhost:3000/mongo/find?name=sun">Find</a>
<div id="divList">
</div>
</li>
</ul>
</body>
</html>
9. 畫面效果如下:
整個實例在這裏下載:NodeJS+MongoDB實例