从零学习node.js之mysql数据库的操作(五)


Posted in Javascript onFebruary 24, 2017

准备工作

在使用node操作mysql数据库时,需要先下载mysql模块:

npm install mysql --save-dev

在引入mysql模块后,就可以进行数据库的连接和其他的操作了。

// test.js
var mysql = require('mysql');

一、连接数据库

首先保证本地已经安装数据库,并已正常启动,然后开始进行连接:

// test.js
var mysql = require('mysql');

// 创建连接
var conn = mysql.createConnection({
 host : '127.0.0.1',
 user : 'root',
 password : '123',
 database : 'test'
});

// 创建连接后不论是否成功都会调用
conn.connect(function(err){
 if(err) throw err;
 console.log('connect success!');
});

// 其他的数据库操作,位置预留

// 关闭连接时调用
conn.end(function(err){
 if(err) throw err;
 console.log('connect end');
})

执行node test.js后,就会输出:

$ node test.js
connect success!
connect end

连接成功,然后连接关闭。这就说明程序可以正常连接数据库了,然后就开始进行增删改查的操作。

二、CURD

比如我们有这样的一个user表,里面有4个字段,其中uid是自增字段:

uid username password email
1 meizi meizi 123@qq.com
2 test test 456@qq.com

我们就对这个表进行增删改查的操作。

mysql中有个query方法可以用来执行任意正确的sql语句,然后在回调函数里给出执行sql语句后的结果。query方法是异步执行的,若并列书写多个query方法的话,是不能按照书写顺序依次阻塞式执行的。

2.1 查询

使用最普遍最多的就是查询操作了。

conn.query('SELECT * FROM `user`', function(err, result, fields){
 if(err) throw err;
 console.log(result);
});
console.log( 'select ended!' );

输出的结果:

select ended! // 先输出

[
 RowDataPacket {   
 uid: 1,   
 username: 'meizi',  
 password: 'meizi
 email: '123@qq.com'
 }, 
 RowDataPacket {   
 uid: 2,   
 username: 'test',  
 password: 'test
 email: '456@qq.com'
 }
]

可以看到,结果集是一个数组,数组中的每条数据都是一个RowDataPacket对象,在使用时,可以像json对象一样获取数据,也可以使用JSON.stringify把result转换为json字符串,但是,result并不是JSON数据。而且,即使结果集中只有一条数据,也是以数组的形式返回的。

console.log(result[0].username); // meizi

输出即为字符串类型的数据。

2.2 添加

向数据库中添加数据使用的是INSERT,INSERT语句有两种形式都可以使用:

第1种,先列好要插入的数据对应的字段,然后跟上数据(如果要给所有的字段都插入数据,可以省略字段不写,但是数据的书写顺序要跟数据表里的字段一一对应):

INSERT INTO table_name ( field1, field2,...fieldN ) VALUES ( value1, value2,...valueN );

第2种,可以像update操作一样书写,将field与value对应的更紧密:

INSERT INTO table_name SET field1=value1, field2=value2, ... fieldN=valueN;

我更加喜欢第2种方式,这种方式更能看出操作了哪些字段,看出字段和数据的对应关系。在node中插入数据:

conn.query("INSERT INTO `user` SET `username`='qwerty', `password`='741', `email`='qwerty@qq.com'", function(err, result){
 if(err) throw err;
 console.log(result);
});

插入数据后返回的结果是:

OkPacket {    
 fieldCount: 0,  
 affectedRows: 1,
 insertId: 4, // 数据插入成功时,对应的主键id
 serverStatus: 2,  
 warningCount: 0,  
 message: '',   
 protocol41: true,  
 changedRows: 0
 }

affectedRows表示数据表中受影响的行数,数据插入成功则为1,失败则为0;在主键自增的情况下,insertId是数据插入成功后对应的主键id,如果主键不自增,则insertId为0。

2.3 更新

使用update语句更新数据:

// 更新uid的密码
conn.query('UPDATE `user` SET `password`="123456" WHERE `uid`=4', function(err, result){
 if(err) throw err;
 console.log(result);
});

输出的结果:

OkPacket {
 fieldCount: 0,
 affectedRows: 1,
 insertId: 0,
 serverStatus: 2,
 warningCount: 0,
 message: '(Rows matched: 1 Changed: 1 Warnings: 0',
 protocol41: true,
 changedRows: 1
 }

可以看到输出结果的类型与插入数据时输出结果的类型是一样的。我们分析一下,执行sql语句后,有3种结果:

  1. 成功修改数据: affectedRows:1, changedRows:1
  2. 要修改的数据与原数据相同: affectedRows:1, changedRows:0
  3. 未找到需要修改的数据: affectedRows:0, changedRows:0

因此可以根据这两个字段输出相应的结果。

2.4 删除

使用delete语句删除语句:

conn.query('DELETE FROM `user` WHERE `uid`=4', function(err, result, fields){
 if(err) throw err;
 console.log(result);
});

输出的结果:

OkPacket {
 fieldCount: 0,
 affectedRows: 1,
 insertId: 0,
 serverStatus: 2,
 warningCount: 0,
 message: '',
 protocol41: true,
 changedRows: 0
 }

删除成功,则affectedRows为1,删除的数据不存在,则为0。

三、连接池

数据库连接是一种有限的,能够显著影响到整个应用程序的伸缩性和健壮性的资源,在多用户的网页应用程序中体现得尤为突出。

数据库连接池正是针对这个问题提出来的,它会负责分配、管理和释放数据库连接,允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个连接,释放空闲时间超过最大允许空闲时间的数据库连接以避免因为连接未释放而引起的数据库连接遗漏。

3.1 创建连接池

使用mysql.createPool()可创建连接池:

// test.js
var mysql = require('mysql');

var pool = mysql.createPool({
 host : '127.0.0.1',
 user : 'root',
 password : '123',
 database : 'test'
})

pool.query('SELECT * FROM `user`', function(err, result){
 if(err) throw err;

 console.log(result);

 pool.end(function(err){
  if(err) throw err;
  console.log('connection ended');
 })
});

getConnection()可以共享一个连接,或管理多个连接。

// test.js
var mysql = require('mysql');

var pool = mysql.createPool({
 host : '127.0.0.1',
 user : 'root',
 password : '123',
 database : 'test'
})

pool.getConnection(function(err, connection){
 if(err) throw err;

 connection.query('SELECT * FROM `user`', function(err, result){
  if(err) throw err;

  console.log(result);
 })
});

连接使用完后通过调用connection.release()方法可以将连接返回到连接池中,这个连接可以被其它人重复使用:

pool.getConnection(function(err, connection){
 if(err) throw err;

 connection.query('SELECT * FROM `user`', function(err, result){
  if(err) throw err;

  console.log(result);
  connection.release();
  // 接下来connection已经无法使用,它已经被返回到连接池中 
 })
});

可以使用connection.destroy()彻底销毁连接。

3.2 连接池事件

createPool()方法会返回一个连接池实例对象,这个对象中有一些事件。

connection

连接池中产生新连接时会发送'connection'事件:

pool.on('connection', function (connection) {
 console.log('new connection');
});

3.3 QUERY与GETCONNECTION的区别

这两个方法都能进行操作,那么这两者有什么区别呢?

pool.getConnection中的connection在其回调函数里是一直的,可以保证这一系列的操作都是在同一个connection中执行的;pool.query则每次执行时可能会在不同的connection中执行,可能会得到意想不到的结果。

比如SQL_CALC_FOUND_ROWSFOUND_ROWS这需要两个sql语句完成,是获取检索行的数目。

pool.query('SELECT SQL_CALC_FOUND_ROWS * FROM `user`');
pool.query('SELECT FOUND_ROWS()');

这两个可能在不同的connection中执行,第2个sql语句返回的就不是上一个sql语句的结果了。

四、sql防注入

sql防注入的关键就是不能直接把数据拼接到sql语句中,必须得对数据进行转义,或者使用提供的方法拼接sql语句。这里主要有四种方法可以使用。

4.1 使用ESCAPE()对参数进行编码

参数编码方法有:mysql.escape()/connection.escape()/pool.escape() ,这三个方法可以在你需要的时候调用:

var sql = 'SELECT * FROM `user` WHERE `uid`='+connection.escape('"123";//--');
console.log(sql); // SELECT * FROM `user` WHERE `uid`='\"123\";//--'

connection.query(sql, function(err, result){
 if(err) throw err;

 console.log(result);
})

对双引号进行了安全转义。

escapeId()可以对不信任的表名,字段名进行转义。

var sql = 'SELECT * FROM '+connection.escapeId('user')+' WHERE `uid`=1';
connection.query(sql, function(err, result){
 console.log(result);
})
console.log(query.sql); // SELECT * FROM `user` WHERE `uid`=1

同时,escape()的编码规则如下:

  1. Numbers不进行转换
  2. Booleans转换为true/false
  3. Date对象转换为'YYYY-mm-dd HH:ii:ss'字符串
  4. Buffers转换为hex字符串,如X'0fa5'
  5. Strings进行安全转义
  6. Arrays转换为列表,如[‘a', ‘b']会转换为'a', ‘b'
  7. 多维数组转换为组列表,如[[‘a', ‘b'], [‘c', ‘d']]会转换为(‘a', ‘b'), (‘c', ‘d')
  8. Objects会转换为key=value键值对的形式。嵌套的对象转换为字符串
  9. undefined/null会转换为NULL
  10. MySQL不支持NaN/Infinity,并且会触发MySQL错误

4.2 占位符

可以使用?作为参数占位符。在使用查询参数占位符时,在其内部自动调用 connection.escape() 方法对传入参数进行编码。

var params = ['test', 'test'];
var query = connection.query('SELECT * FROM `user` WHERE `username`=? AND `password`=?', params, function(err, result){
 console.log(result);
});
console.log(query.sql); // SELECT * FROM `user` WHERE `username`='test' AND `password`='test'

同时,如果执行添加或更新操作时,还可以这样写:

var params = {username:'qwerty', password:'qwerty', email:'qwerty@qq.com'};
var query = connection.query('INSERT INTO `user` SET ?', params, function(err, result){
 if(err) throw err;
 console.log(result);
});
console.log(query.sql); // INSERT INTO `user` SET `username` = 'qwerty', `password` = 'qwerty', `email` = 'qwerty@qq.com'

数据库中的表明和字段名,可以使用??作为占位符,在拼接完成后会自动添加上``:

var params = ['user', 'username', 'test', 'password', 'test'];
var query = connection.query('SELECT * FROM ?? WHERE ??=? AND ??=?', params, function(err, result){
 console.log(result);
})
console.log(query.sql); // SELECT * FROM `user` WHERE `username`='test' AND `password`='test'

4.3 使用MYSQL.FORMAT()转义参数

不多说,样例如下:

var userId = 1;
var sql = "SELECT * FROM ?? WHERE ?? = ?";
var inserts = ['user', 'uid', userId];
sql = mysql.format(sql, inserts); // SELECT * FROM `user` WHERE `uid` = 1

五、多语句查询

出于安全考虑node-mysql默认禁止多语句查询(可以防止SQL注入),启用多语句查询可以将multipleStatements选项设置为true:

var connection = mysql.createConnection({multipleStatements: true});

启用后可以在一个query查询中执行多条语句:

connection.query('SELECT 1; SELECT 2', function(err, results) {
 if (err) throw err;

 // `results`是一个包含多个语句查询结果的数组
 console.log(results[0]);
 console.log(results[1]);
});

总结

本节只是总结了node对mysql数据库的各种操作,但如果实际应用起来的话,还远远不够。努力学习中…希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Javascript 相关文章推荐
jQueryUI如何自定义组件实现代码
Nov 14 Javascript
capacityFixed 基于jquery的类似于新浪微博新消息提示的定位框
May 24 Javascript
JavaScript实现维吉尼亚(Vigenere)密码算法实例
Nov 22 Javascript
JS判断对象是否存在的10种方法总结
Dec 23 Javascript
浅谈JavaScript中运算符的优先级
Jul 07 Javascript
JS在一定时间内跳转页面及各种刷新页面的实现方法
May 26 Javascript
Javascript对象字面量的理解
Jun 22 Javascript
VUE 实现滚动监听 导航栏置顶的方法
Sep 11 Javascript
详解新手使用vue-router传参时注意事项
Jun 06 Javascript
javascript实现图片轮播代码
Jul 09 Javascript
JavaScript实现10秒后再次获取验证码
Dec 02 Javascript
【js设计模式】SOLID五大设计原则
Mar 24 Javascript
微信小程序 定位到当前城市实现实例代码
Feb 23 #Javascript
JavaScript中值类型和引用类型的区别
Feb 23 #Javascript
canvas绘制环形进度条
Feb 23 #Javascript
微信小程序 两种为对象属性赋值的方式详解
Feb 23 #Javascript
js实现文字跑马灯效果
Feb 23 #Javascript
微信小程序 出现错误:{"baseresponse":{"errcode":-80002,"errmsg":""}}解决办法
Feb 23 #Javascript
JS正则表达式验证密码格式的集中情况总结
Feb 23 #Javascript
You might like
用PHP和ACCESS写聊天室(八)
2006/10/09 PHP
深入密码加salt原理的分析
2013/06/06 PHP
ubuntu下编译安装xcache for php5.3 的具体操作步骤
2013/06/18 PHP
PHP图片上传代码
2013/11/04 PHP
ExtJS Ext.MessageBox.alert()弹出对话框详解
2010/04/02 Javascript
Javascript 面试题随笔
2011/03/31 Javascript
jQuery移动和复制dom节点实用DOM操作案例
2012/12/17 Javascript
100个不能错过的实用JS自定义函数
2014/03/05 Javascript
jQuery中:animated选择器用法实例
2014/12/29 Javascript
Javascript中实现trim()函数的两种方法
2015/02/04 Javascript
javascript实现检验的各种规则
2015/07/31 Javascript
jquery实现像栅栏一样左右滑出式二级菜单效果代码
2015/08/24 Javascript
第九章之路径分页标签与徽章组件
2016/04/25 Javascript
js获取元素的标签名实现方法
2016/10/08 Javascript
详解Vue中使用v-for语句抛出错误的解决方案
2017/05/04 Javascript
分析JS单线程异步io回调的特性
2017/12/01 Javascript
从零开始最小实现react服务器渲染详解
2018/01/26 Javascript
vue init webpack 建vue项目报错的解决方法
2018/09/29 Javascript
JavaScript实现世界各地时间显示
2020/09/07 Javascript
[01:29]2017 DOTA2国际邀请赛官方英雄手办展示
2017/03/18 DOTA
[01:16:12]完美世界DOTA2联赛PWL S2 FTD vs Inki 第一场 11.21
2020/11/23 DOTA
Python中py文件引用另一个py文件变量的方法
2018/04/29 Python
Python3内置模块pprint让打印比print更美观详解
2019/06/02 Python
Python学习笔记之函数的参数和返回值的使用
2019/11/20 Python
使用Tensorboard工具查看Loss损失率
2020/02/15 Python
python 画条形图(柱状图)实例
2020/04/24 Python
python中数字是否为可变类型
2020/07/08 Python
Django利用elasticsearch(搜索引擎)实现搜索功能
2020/11/26 Python
如何用css3实现switch组件开关的方法
2018/02/09 HTML / CSS
联想墨西哥官方网站:Lenovo墨西哥
2016/08/17 全球购物
创业计划书六个要素
2013/12/26 职场文书
县委常委班子专题民主生活会查摆问题及整改措施
2014/09/27 职场文书
大学生入党群众意见书
2015/06/02 职场文书
2015年中秋放假通知范文
2015/08/18 职场文书
十二月早安励志心语大全
2019/12/03 职场文书
配置nginx 重定向到系统维护页面
2021/06/08 Servers