Node.js应用程序应该具有通过数据库的访问来存取数据的能力
安装MongoDB数据库
使用npm包安装MongoDB本地驱动包
增删改查
使用Mongoose类库为MongoDB数据库中的数据集合定制数据架构
npm安装MySQL数据库客户端开发包
增删改查
Node.js中调用MySQL数据库中的存储过程
数据流的方式读取从MySQL数据库查询到的数据
Node.js中为多个数据库连接创建并维护一个可缓存数据连接的连接池
1 在MongoDB数据库存取数据
由于MongoDB数据库支持在查询语句内部使用JavaScript函数,也大大加强了它读取数据的能力
MongoDB数据库中,支持的数据类型:
- Array,数组,
- Boolean,布尔类型,true, false
- Code,数据库内部可运行的一段JS脚本代码
- Date,当前日期和时间
- DBRef,数据库引用
- Intger,整数值
- Long,长整数值
- Hash,一个"键值/键名"形式的数据字典
- Null,null值
- ObjectID,MongoDB数据库中用于索引对象的一个12字节的代码,其表现形式就是一个24位的十六进制字符串
- String,字符串
在node.js应用程序中在执行MongoDB数据库连接时
npm install mongodb
var mongo = require('mongodb');
连接MongoDB数据库
先创建一个代表MongoDB数据库所在服务器的server对象,用于指定需要连接的MongoDB数据库所在服务器
创建Server对象方法:
var server = new mongo.Server(host, port, [options]);
- 参数解析:
- host: 服务器地址
- port: 端口
- options: 参数值为一个对象,用于指定该服务器需要使用的一些选项,该对象中包含的属性及属性值如下:
- ssl: 属性值为一个Boolean,属性值为true时,客户端与服务器之间建立一个基于ssl安全协议的连接,服务器端需启用ssl安全协议。默认属性值为false。
- sslValidate: 属性值为一个布尔值,用于指定服务器是否验证客户端所提交的证书。当该属性值为true时,服务器端需要使用ssl安全协议2.4版以上。默认属性值为false。
- sslCA: 属性值为一个数组,数组中每一个元素值为一个Buffer对象或一个字符串,用于指定一组供服务器端验证时使用的证书(服务器端需要使用ssl安全协议2.4版以上),默认属性值为null。
- sslCert: 属性值为一个Buffer对象或一个字符串,用于指定一个供服务器端验证时使用的证书(服务器端需要使用ssl安全协议2.4版以上),默认属性值为null。
- sslKey: 属性值为一个Buffer对象或一个字符串,用于指定一个供服务器端验证时使用的私钥(服务器端需要使用ssl安全协议2.4版以上),默认属性值为null。
- sslPass: 属性值为一个Buffer对象或一个字符串,用于指定一个供服务器端验证时使用的证书密码(服务器端需要使用ssl安全协议2.4版以上),默认属性值为null。
- poolSize: 属性值为一个整数,用于指定连接池中的最大连接数量,默认属性为5.
- socketOptions: 属性值为一个对象,用于指定与服务器建立连接的端口使用的选项,默认属性值为null。属性值对象中可使用的属性如下:
- keepAlive: 属性值为一个整数,单位为毫秒,用于指定客户端每隔多久向服务器端发送一次keepAlive探测包
- connectTimeoutMS
- socketTimeoutMS
- logger
- auto_reconnect
- disableDriverBSONSizeCheck
MongoDB服务器对象创建成功后,需要创建一个代表MongoDB数据库的Db对象:
var db = new mongo.Db(databaseName, server, [options])
- 参数解析:
- databaseName: 需要连接的数据库名
- server: 指定该数据库所在服务器
- options: 用于指定该数据库需要使用的一些选项,该对象中包含的属性及属性值:
- w: 性值为一个大于-1的整数或一个字符串。该属性值用于设置写数据操作时MongoDB数据库内部的write concern机制,MongoDB数据库内部使用write concern机制来报告一条数据的写入操作是否成功。当w属性值为小于1的整数值时,writeconcern机制不承认一条数据被写入。当w属性值为大于或等于1的整数值或字符串时,write concern机制承认一条数据被写入。
- wtimeout: 属性值为一个整数值,用于指定写数据操作的超时时间,单位为毫秒。
- fsync: 属性值为一个布尔值,用于指定在写数据操作的方法返回前是否要等待MongoDB数据库内部使用的fsync操作(该操作将剩余的被挂起数据全部写入数据库)结束。默认属性值为false。
- journal: 属性值为一个布尔值,用于指定在写数据操作的方法返回前是否要等待MongoDB数据库内部使用的journal操作(该操作在数据库中写入执行日志)结束。默认属性值为false。
- ......
Db对象创建后,需要使用该对象的open方法执行数据库连接操作
db.open(callback)
与数据库之间建立连接及关闭数据库的代码示例
var mongo = require('mongodb');
var host = "localhost";
var port = mongo.Connection.DEFAULT_PORT;
var server = new mongo.Server(host, port, {auto_reconnect: true});
var db = new mongo.Db('node-mongo-example', server, {safe: true});
db.open (function (err, db) {
if (err) throw err;
else {
console.log('成功建立数据库连接');
db.close();
}
});
db.on('close', function(err, db) {
if (err) throw err;
else console.log('成功关闭数据库。');
});
在MongoDB数据库中插入数据
在MongoDB数据库中,数据存储在许多数据集合中,可以使用数据库对象的collection方法访问一个集合。
db.collection(collectionName, [options], [callback])
- 参数解析:
- collectionName:指定需要访问的集合名。
- options:对象,用于指定访问该集合时使用得选项
可以使用Collection对象得insert方法向该集合中插入一个数据文档,
MongoDB是一个面向文档得NoSQL数据库,因此在该数据库中得每一条数据都是一个数据文档,而不是像关系型数据库中那样将一条数据称为数据记录
collection.insert (docs, [options], [callback])
- 参数解析:
- docs:参数值为一个JSON对象或一个由JSON对象构成得数组,用于指定需要插入得数据文档
- options:参数为一个对象,用于指定插入数据时使用的选项。
将user对象插入到MongoDB数据库中的示例
var mongo = require('mongodb'); var host = "localhost"; var port = mongo.Connection.DEFAULT_PORT; var db = new mongo.Db('node-mongo-examples', new mongo.Server(host, port, {auto_reconnect:true}), {safe:true}); db.open (function (err, db) { db.collection('users', function(err, collection) { collection.insert({username:'kebi', firstname: 'sui'}, function (err, docs) { console.log(docs); db.close(); }); }); });
- forceClose参数值指定为true后不可使用open方法重新打开该数据库
在MongoDB数据库中查询数据
可以使用Collection对象的find方法从一个集合中查询多个数据文档
collection.find(selector, [options])
查询user集合中的所有数据
var mongo = require('mongodb'); var host = "localhost"; var port = mongo.Connection.DEFAULT_PORT; var db = new mongo.Db('node-mongo-example', new mongo.Server(host, port, {auto_reconnect: true}), {safe: true}); db.open(function (err, db) { db.collection('users', function(err, collection) { if (err) throw err; else { collection.find({}).toArray(function (err, docs) { if (err) throw err; else { console.log(docs); db.close(); } }); } }); });
- 多字段值的查询条件示例
var mongo = require('mongodb'); var host = "localhost"; var port = mongo.Connection.DEFAULT_PORT; var db = new mongo.Db('node-mongo-examples', new mongo.Server(host, port, {auto_reconnect:true}), {safe:true}); var docs = [{type: 'food', price:11}, {type: 'food', price:10}, {type: 'food', price:9}, {type: 'book', price:9}]; db.open (function (err, db) { db.collection('goods', function (err, collection) { collection.insert(docs, function(err, docs) { if(err) throw err; else { collection.find({type: 'food', price:{$lt:10}}).toArray(function (err, docs) { if (err) throw err; else { console.log(docs); db.close(); } }); } }); }); });
- 在一个复杂的查询条件中,可能需要使用“或”关系指定多个字段值的查询条件,如下:
collection.find({$or: [ {type: 'food'}, {price: {$lt: 10}} ]})
- 在查询条件中指定子文档
var mongo = require('mongodb'); var host = "localhost"; var port = mongo.Connection.DEFAULT_PORT;var db = new mongo.Db('node-mongo-examples', new mongo.Server(host, port, {auto_reconnect:true}), {safe:true}); var store1 = {name: 'store1', goods:{type: 'food', price: 11}}; var store2 = {name: 'store2', goods:{type: 'food', price: 10}}; var store3 = {name: 'store3', goods:{type: 'food', price: 9}}; var store4 = {name: 'store'4, goods:{type: 'book', price: 8}}; var docs = [store1, store2, store3, store4]; db.open(function(err, db) { db.collection('store', function(err, collection) { collection.insert(docs, function(err, docs) { if(err) throw err; else { collection.find({goods: {type: 'food', price: 8}}).toArray(function (err, docs) { if (err) throw err; else { console.log(docs); db.close(); } }); } }); }); });
- 在查询条件中指定一个数组的完整内容的示例:
var mongo = require('mongo'); var host = "localhost"; var port = mongo.Connection.DEFAULT_PORT; var db = new mongo.Db('node-mondo-examples', new mongo.Server(host, port, {auto_reconnect: true}), {safe: true}); var aricle1={name:'TV',tags:['device','electric equipment']}; var aricle2={name:'apple',tags:['fruit','food','citrus']}; var article3={name:'Node.js',tags:['language','web','computer']}; var docs=[aricle1,aricle2,article3]; db.open(function (err, db) { db.collection('articales', function(err, collection) { collection.insert(docs, function(err, docs) { if(err) throw err; else { collection.find({tags: ['fruit','food','citrus'] }).toArray(function(err, docs) { if(err) throw err; else { console.log(docs); db.close(); } }); } }); }); });
指定某个子数据文档的某个元素的查询条件
js collection.find({'goods.0.type': 'book'}).toArray(function(err, docs){})
可以使用collection对象的update方法更新集合中的数据文档
collection.update(selector, document, [options], [callback])
参数解析:
- selector:参数值为一个对象,用于查询需要更新的数据文档,该参数值的指定方法与Collection对象的find()里的selector参数值指定方法完全相同
- document: 参数值为一个对象,用于指定用来更新的数据文档
- options:参数值为一个对象,用于指定更新数据时使用的选项
update()的使用示例:
var mongo = require('mongodb'); var host = "localhost"; var port = mongo.Connection.DEFAULT_PORT; var db = new mongo.Db('node-mongo-examples', new mongo.Server(host, port, {auto_reconnect:true}), {safe:true}); db.open(function(err, db) { db.collection('users', function(err, collection){ if (err) throw err; else { collection.update({}, {username:'test', firstname: 'test'}, function (err, result) { if (err) throw err; else { console.log('成功更新%d条数据文档', result); collection.find({}).toArray(function(err, docs) { if (err) throw err; else { console.log('更新后的数据'); console.log(docs); db.close(); } }); } }); } }); });
- findAndModify()查询并更新一条数据文档
collection.findAndModify(selector, sort, document, [options], callback);
- remove()删除一条数据
- findAndRemove()查询并删除一条数据文档
使用Mongoose类库
通过Mongoose类库的使用,可以使MongoDB数据库在node.js中得到更全面的支持。
数据架构
- 在关系型数据库中,在存储数据之前,开发者必须首先为数据定义数据表,并且为该数据表定义一些字段,从而使数据模型得到了实现。因此,在关系型数据库中,一个数据表就是一个数据架构。所谓数据架构,是指数据库中的数据模型定义,它被独立于应用程序之外而单独定义,它预先定义开发者可以使用的数据模型。
使用Mongoose类库定义数据架构的示例:
var mongoose = require('mongoose'); Schema = mongoose.schema; mongoose.connect('mongodb:// localhost: 27027/node-mongo-examples', function(err) { if(err) { console.log('连接MongoDB数据库失败'); } }); var goodsSchema = new Schema ({ type: String, price: Number }); var storeSchema = new Schema({ name: String, goods: [goodsSchema] }); var store1={name:'store1',goods:[{type:'food',price:11}]}; var store2={name:'store2',goods:[{type:'food',price:10}]}; var store3={name:'store3',goods:[{type:'food',price:9}]}; var store4={name:'store4',goods:[{type:'food',price:8}]}; var store5={name:'store5',goods:[{type:'book',price:9}]}; var docs=[store1,store2,store3,store4,store5]; var Store = mongoose.model('store', storeSchema); Store.create(docs, function(err, docs) { if(err) throw err; else { Store.find(function (err, docs) { if (err) throw err; else { console.log(docs); mongoose.disconnect(); } }); } });
2 在MySQL数据库中存取数据
安装MySQL客户端开发包
npm install mysql
使用MySQL客户端开发包
var mysql = require('mysql');
2.1 建立连接与关闭连接
在MySQL模块中,可以使用createConnection()来创建一个表示与MySQL数据库服务器之间的连接的Connection对象,
var connection = mysql.createConnection(options);
参数解析:
- options:参数值为一个对象或一个URL字符串,用于指定该连接所用的各种选项,当参数值指定为一个对象时,可以使用的属性如下:
- host:属性值为一个字符串,用于指定数据库服务器地址,默认属性值为“localhost”
- port:端口号,默认3306
- socketPath:属性值为一个字符串,用于指定一个数据库服务器使用的unix端口路径
- user: 用于指定连接MySQL数据库时使用的用户名
- password:用于指定连接MySQL数据库时使用的密码
- database: 指定需要连接的MySQL数据库名
- charset:用于指定该连接使用的字符集,默认属性值为“UTF8_GENERAL_CI”,属性值需要全部使用大写字母
- timezone: 指定存储本地日期时使用的时区,默认属性值为local
- stringifyObjects: 属性值为一个布尔值,用于指定在存储对象时将对象转换为字符串,默认属性值为false。
- insecureAuth: 属性值为一个布尔值,用于指定是否允许连接使用老的(不安全的)认证方法,默认属性值为false
- ....
- options:参数值为一个对象或一个URL字符串,用于指定该连接所用的各种选项,当参数值指定为一个对象时,可以使用的属性如下:
也可以以URL字符串的形式来指定这些选项
var connection = mysql.createConnection('mysql:// user:pass@host/db?debug=true&charset=BIG%_CHINESE_CI');
与数据库之间建立连接与关闭连接的操作
var mysql = require('mysql'); var connection = mysql.createConnection({ host: 'localhost', port: 3306, database: 'mysql', user: 'root', password: 'root', }); connection.connect(function (err) { if (err) throw err; else { console.log('连接成功'); connection.end(function (err) { if (err) { throw err; } else { console.log('关闭MySQL数据库操作成功') } }); } })
- 指定与数据库服务器之间的连接丢失时的处理
var mysql = require('mysql'); var connection = mysql.createConnection({ host: 'localhost', port: 3306, database: 'mysql', user: 'root', password: 'root', }); var pool = mysql.createPool({ host: 'localhost', port: 3306, database: 'mysql', user: 'root', password: 'root', }); function handleDisconnect () { connection.connect(function (err) { if (err) throw err; else { console.log('与数据库建立连接成功'); } }); } connection.on('error', function(err) { if(err.code === 'PROTOCOL_CONNECTION_LOST') { console.log("与MySQL数据库之间的连接被丢失"); setTimeOut(function () { handleDisConnect(); }, 10000); } else { throw err } }); handleDisconnect(); }
2.2 执行数据的基本处理
在mysql模块中,可以通过connection对象的query()统一执行数据的增删改查
connection.query(sql, [parameters], [callback]);
参数解析:
- sql:参数值为一个字符串,用于指定需要用来执行的SQL表达式
- paramters:参数值为一个数组或一个对象,用于存放sql参数值字符串中使用到的所有参数的参数值
- callback:回调函数
为了防止SQL注入攻击,需要使用Connection对象的escape()对所有用户输入数据进行escape编码处理:
connection.escape(data)
- 示例:
var query = "SELECT * FROM posts WHERE title=" + connection.escape("Hello MySQL"); console.log(query) //输出:SELECT * FROM posts WHERE title='Hello MySQL
也可以通过在Connection对象的query方法所使用的查询语句中使用参数,在parameters参数值对象或数组中指定参数值的方法来执行escape编码处理
// parameters参数值可以为一个对象
connection.query('INSERT INTO posts SET ?',{id: 1, title: 'Hello MySQL'});
// 或
connection.query("UPDATE posts SET title = :title", { title: "Hello MySQL" });
// parameters参数值也可以为一个数组
connection.query('SELECT * FROM users WHERE id = ?', [userId]);
- 在MySQL数据库中插入与查询数据
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
port: 3306,
database: 'mysql',
user: 'root',
password: 'root',
});
connection.connect (function (err) {
if (err) console.log('与MySQL数据库建立连接失败');
else {
console.log('与MySQL数据库连接成功');
connection.query('INSERT INTO users SET ?', {username:'李三', firstname:'lii'}, function (err, result) {
if (err) throw err;
else {
connection.query('SELECT * FROM ??', ['users'], function(err, result) {
if(err) throw err;
else {
console.log(result);
connection.end();
}
})
}
})
}
})
基于安全, 在mysql模块中,默认禁止使用多语句查询,因为如果开发者没有对用户输入数据进行escape编码处理,应用程序有可能收到sql注入攻击。
要开启多语句查询功能,可以将createConnection()使用的options参数值对象中的multipleStatements属性设定为true
var connection = mysql.createConnection({multipleStatements: true});
- 使用multiplements属性值同时执行多条语句
var mysql = require('mysql'); var tableName = "users"; var connection = mysql.createConnection({ host: 'localhost', port: '3306', database: 'mysql', multipleStatements: true, user: 'root', password: 'root', }); connection.connect(function (err) { if (err) console.log('与MySQL数据库建立连接失败'); else { console.log('与数据库建立连接成功'); insertData(); } }); function insertData() { var sqlStr =""; for(var i=1; i < 4; i++) { sqlStr += "INSERT INTO " + tableName + "(username, firstname) vaelues(" + connection.escape(" 用户名" + i.toString()) + ");"; connection.query(sqlStr, function(err, reault) { if (err) console.log("插入数据失败"); else { updateData(); } }); } function updateDate () { connection.query("update" + tableName + "set firstname = ? where username=?", [" 姓100","用户名2" ], function (err, result) { if (err) { console.log("更新数据失败"); } else { deleteData(); } }); } function deleteData() { connection.query("delete from " + tableName + " where username=?", ["用户名3"], function(err, result){ if (err) throw err; else { queryData(); } }); } function queryData () { connection.query("SELECT * FROM " + tablename, function(err, result){ if(err) throw err; else { console.log(result); connection.end(); } }); } }
2.3 执行存储过程
可以使用multipleStatements属性来执行存储过程
successFlag: 表示是否插入成功
用于测试的存储过程代码
create procedure insertuser (
in usernam varchar(20),
in firstname varchar(20),
out successFlag smallint
)
begin
declare exit handler
for sqlexception
set successFlag=0;
insert into users
values (
null,
username,
firstname
);
set successFlag=1;
end
- 使用存储过程在users表中插入一条数据
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
port: '3306',
database: 'mysql',
multiplements: true,
user: 'root',
password: 'root',
});
connection.connect(function(err) {
if (err) console.log("与Mysql数据库建立连接失败!");
else {
console.log("连接成功!");
insertData();
}
});
function insertData() {
var sqlStr="call insertuser('lingniu', 'lu', @successFlag); select @successFlag;";
connection.query(sqlStr, function(err, result) {
if (err) throw err;
else {
if (result[1][0]["@successFlag"] == "1") console.log("插入数据成功");
else console.log("插入数据失败");
}
connection.end();
});
}
2.4 执行多表结合查询
在mysql模块中,可以在Connection对象的query()所使用的第一个参数值SQL语句中指定多张表的结合查询。
结合查询两张表中的所有数据
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
port: '3306',
database: 'mysql',
user: 'root',
password: 'root',
});
connection.connect(function (err) {
if (err) console.log('与数据库建立连接失败');
else {
console.log('与数据库建立连接成功');
connection.query('select genres.id, genres.name, books.id, books.genreid, books.name from genres inner join books on genres.id=books.genredid', function(err, result) {
if (err) console.log('查询数据失败!');
else {
console.log(result);
connection.end();
}
});
}
});
- 从结果看,genres表中的数据并没有被查出来,该表的id字段与name字段被books表的id字段与name字段覆盖。
解决方法:
- 为在SQL语句中为重复的字段使用别名
connection.query('select genres.id,genres.name,books.id id1,books.genreid,books.name name1 from genres inner join books on genres.id=books.genreid',function(err,result){ // 回调函数代码略});
- 第二种方法是为在query()使用nestTables属性并将属性值设定为true,这将使被结合的两张表中的数据以两个对象的形式进行输出
connection.query({ sql: 'select genres.id, genres.name, books.id, books.genreid, books.name from genres inner join books on genres.id=books.genreid', nestTables:true }, function(err, result){ //回调函数 }
- 第三种方法为在query方法中使用nestTables属性并将属性值设定为一个分隔字符,这将使被结合的两张表中的数据以一个对象的形式进行输出,该对象的属性名为字段所属表名+分割字符+字段名。
connection.query({sql:'select genres.id,genres.name,books.id,books.genreid,books.name from genres inner join books on genres.id=books.genreid',nestTables:'_'},function(err,result){ // 回调函数代码略 })
2.5 以数据流的方式处理查询数据
用户也许会查询大量数据并希望单独处理每一条查询到的数据。
在mysql模块中,Connection对象的query()返回一个可用于处理流数据的对象。
Connection 对象的query()方法返回对象使用实例
var mysql = require('mysql');
var fs = require('fs');
var connection = mysql.createConnection({
host: 'localhost',
port: '3306',
database: 'mysql',
user: 'root',
password: 'root',
});
var out = fs.createWriteStream('./message.txt');
out.on('error', function(err) {
console.log('写文件操作失败。错误信息', err.message);
process.exit();
});
connection.connect(function (err) {
if (err) console.log('与MySQL数据库建立连接失败');
else {
console.log('连接数据库成功');
var query = connect.query('select * from users');
query.on('error', function(err) {
console.log('读取数据失败: 错误信息:', err.message);
}).on('fileds', function(fileds) {
var str ="";
fileds.forEach(function(filed){
if(str!="") {
str += String.fromCharCode(9);
}
str += filed.name;
});
out.write(str+'\r\n');
}).on('result', function(row) {
connection.pause();
out.write(row.id + String.fromCharCode(9) + row.username + String.fromCharCode(9) + row.firstname + '\r\n', function(err) {
connection.resume();
});
}).on('end', function() {
console.log('数据全部写入完毕');
connection.end();
});
}
});
2.6 创建连接池
在开发一个web应用程序时,连接池是一个非常重要的概念,因为建立一个数据库连接所消耗的性能成本是比较高的。
在服务器应用程序中,若为每一个接收到客户端请求都建立一个多多个数据库连接将严重降低程序之性能。
因此,在服务器应用程序中,通常需要为多个数据库连接创建并维护一个连接池。
var pool = mysql.createPool(options);
- 创建并使用连接池对象
var mysql = require('mysql');
var pool = mysql.createPool({
host: 'localhost',
port: '3306',
database: 'mysql',
user: 'root',
password: 'root',
});
pool.getConnection(function(err, connection) {
if (err) console.log('与MySQL建立连接失败');
else {
connection.query('select * from users', function(err, rows) {
if (err) throw err;
else {
console.log(rows);
pool.end();
}
});
}
});