Node.js——操作MySQL数据库

为了使用MySQL数据库,首先需要使用npm包安装工具安装MySQL客户端开发包,命令如下所示。

npm install mysql

在安装了MySQL客户端开发包之后,可以通过如下所示的表达式引用mysql模块。

const mysql = require('mysql');

1、建立连接与关闭连接

  1. 在mysql模块中,可以使用createConnection方法创建一个表示与MySQL数据库服务器之间的连接的Connection对象,方法如下所示。
let connection = mysql.createConnection(options);

在createConnection方法中,使用一个参数,参数值为一个对象或一个URL字符串,用于指定该连接所用的各种选项。当参数值指定为一个对象时,可以使用的属性如下所示。

  • host:属性值为一个字符串,用于指定数据库服务器地址(可以为IP地址或主机名)​,默认属性值为“localhost”​。
  • port:属性值为一个整数值,用于指定数据库服务器使用的端口号,默认属性值为3306。
  • socketPath:属性值为一个字符串,用于指定一个数据库服务器使用的unix端口路径。当使用了host属性与port属性时,socketPath属性值被忽略。
  • user:属性值为一个字符串,用于指定连接MySQL数据库时使用的用户名。
  • password:属性值为一个字符串,用于指定连接MySQL数据库时使用的密码。
  • charset:属性值为一个字符串,用于指定该连接使用的字符集。默认属性值为“UTF8_GENERAL_CI”​,属性值需要全部使用大写字母。
  • timezone:属性值为一个字符串,用于指定存储本地日期时使用的时区,默认属性值为“local”​。
  • stringifyObjects:属性值为一个布尔值,用于指定在存储对象时将对象转换为字符串,默认属性值为false。
  • insecureAuth:属性值为一个布尔值,用于指定是否允许连接使用老的(不安全的)认证方法,默认属性值为false。
  • typeCast:属性值为一个布尔值,用于指定是否将字段值转换为本地JavaScript类型。默认属性值为true。
  • queryFormat:属性值为一个函数,用于格式化查询字符串。
  • supportBigNumbers:属性值为一个布尔值,用于指定是否支持使用大数值(BIGINT类型与DECIMAL类型)​。默认属性值为false。
  • bigNumbersStrings:属性值为一个布尔值,用于指定在处理大数值(BIGINT类型与DECIMAL类型)时是否将大数值强制转换为一个JavaScript中的String(代表字符串)对象。默认属性值为false。如果将supportBigNumbers属性值指定为true,但是将bigNumbersStrings属性值指定为false,那么,只有在大数值不能被转换为JavaScript中的Number(代表数值)对象时(当这些数值不在-253~+253的范围内时)​,才将该数值强制转换为String类型,默认属性值为false。当supportBigNumbers属性值指定为false时该属性值被忽略。
  • debug:属性值为一个布尔值,用于指定是否将协议细节进行标准输出,默认属性值为false。
  • multipleStatements:属性值为一个布尔值,用于指定是否允许在一次查询中执行多条MySQL表达式,默认属性值为false。请小心使用该属性,因为将属性值设定为true时存在受到SQL注入攻击的可能性。

也可以以URL字符串的形式来指定这些选项,代码如下所示。

let connection = mysql.createConnection('mysql:// user:pass@host/db?debug=true&charset=BIG5_CHINESE_CI');
  1. 当Connection对象被创建之后,可以使用该对象的connect方法建立与MySQL数据库服务器之间的连接,方法如下所示。
connection.connect(function(err) {
    // 回调函数代码略
})
  • err:连接失败时触发的错误对象
  1. 在不需要使用该数据库时,可以使用Connection对象的end方法或destroy方法关闭与MySQL数据库服务器之间的连接。end方法的使用方式如下所示。
connection.end(function(err) {
    // 回调函数代码略
});
  • err:关闭连接失败时触发的错误对象

end方法将在向MySQL数据库服务器发送一个用于关闭连接的COM_QUIT数据包前将所有被挂起的查询操作执行完毕,如果在执行这些查询操作时触发了一个致命错误,那么将为执行这些查询语句的回调函数提供一个错误对象参数,但是连接仍然将被关闭。

  1. 也可以使用Connection对象的destroy方法关闭与数据库之间的连接,该方法同时销毁与数据库服务器之间建立连接用的端口对象。该方法不执行被挂起的查询操作,使用方式如下所示。
connection.destroy();

示例:建立与本地MySQL数据库服务器中的mysql数据库之间的连接,当连接失败时,在控制台中输出“与MySQL数据库建立连接失败。​”字符串。当连接成功时,在控制台中输出“与MySQL数据库建立连接成功。​”字符串,然后关闭与数据库之间的连接,当关闭数据库操作失败时,在控制台中输出“关闭MySQL数据库操作失败。​”字符串,而当关闭数据库操作成功时,在控制台中输出“关闭MySQL数据库操作成功。​”字符串。

const mysql = require('mysql');
let connection = mysql.createConnection({
    host     : 'localhost',
    port     : 3306,
    database : 'test',
    user     : 'root',
    password : '123456',
});
connection.connect((err)=>{
    if(err) {
        console.log('与MySQL数据库建立连接失败。');
    } else{
        console.log('与MySQL数据库建立连接成功。');
        connection.end((err)=>{
            if(err) {
                console.log('关闭MySQL数据库操作失败。');
            } else{
                console.log('关闭MySQL数据库操作成功。');
            }
        });
    }
});
与MySQL数据库建立连接成功。
关闭MySQL数据库操作成功。

有时,也许会因为网络连接中断以及数据库服务器断电、崩溃或重启等原因而丢失与数据库服务器之间的连接,在这种情况下,触发一个错误,可以通过监听Connection对象的error事件并指定事件回调函数的方法捕捉该错误,在error事件回调函数中,可以使用一个参数,参数值代表被触发的错误对象。当与数据库服务器之间的连接丢失时,该错误对象的code属性值(代表错误代码)为“PROTOCOL_CONNECTION_LOST”​。

示例:与数据库服务器之间的连接丢失时,在控制台中输出“与MySQL数据库之间的连接被丢失。​”字符串,在10秒钟后尝试重新连接数据库服务器,如果连接失败,那么在控制台中输出“与MySQL数据库建立连接失败。​”字符串。

const mysql = require('mysql');
let connection = mysql.createConnection({
    host     : 'localhost',
    port     : 3306,
    database : 'test',
    user     : 'root',
    password : '123456', 
});
function handleDisconnect(){
    connection.connect((err)=>{
        if(err) {
            console.log('与MySQL数据库建立连接失败。');
        } else{
            console.log('与MySQL数据库建立连接成功。');
        }
    });
}
connection.on('error', (err)=>{
    if(err.code === 'PROTOCOL_CONNECTION_LOST') {
        console.log('与MySQL数据库之间的连接被丢失。');
        setTimeout(()=>{
            handleDisconnect();
        },10000);
    } else {
        throw err;
    }
});
handleDisconnect();

运行该文件。为了测试本代码示例,停止MySQL服务,命令行窗口中显示“与MySQL数据库之间的连接被丢失。​”字符串,在10秒钟后命令行窗口中显示“与MySQL数据库建立连接失败。​”字符串。

2、执行数据的基本处理

  1. 在mysql模块中,可以通过Connection对象的query方法统一执行数据的增加、删除、查询及修改等基本处理,如下所示。
connection.query(sql,[parameters][callback]);
  • sql:一个字符串,用于指定需要用来执行的SQL表达式
  • parameters:可选,一个数组或一个对象,用于存放sql参数值字符串中使用到的所有参数的参数值
  • callback:一个函数,用于指定执行数据的增加、删除、查询及修改操作结束时所需执行的回调函数。该回调函数的指定方法如下所示。
function(err,results){
    // 回调函数代码略
}
  • err:操作失败时触发的错误对象
  • results:一个对象,代表操作的执行结果
  1. 为了防止SQL注入攻击,需要使用Connection对象的escape方法对所有用户输入数据进行escape编码处理,该方法的使用方式如下所示。
connection.escape(data);

escape方法的代码示例如下所示。

let query = "SELECT * FROM posts WHERE title=" +
connection.escape("Hello MySQL");
console.log(query); 
// SELECT * FROM posts WHERE title='Hello MySQL'

在Connection对象的escape方法中,使用一个参数,参数值为Node.js中可以使用的任何类型。在执行escape编码处理时,使用如下所示的规则。

  • 如果data参数值为数值,保留原参数值不变。
  • 如果data参数值为布尔值,将其转换为“true”或“false”字符串。
  • 如果data参数值为Date对象,将其转换为“YYYY-mm-dd HH:ii:ss”日期格式字符串。
  • 如果data参数值为Buffer对象,将其转换为十六进制数值格式字符串,例如X’0fa5’。
  • 如果data参数值为字符串,使用JavaScript脚本代码内部使用的escape编码处理。
  • 如果data参数值为数组,将数组转换为列表,例如将[‘a’,‘b’]转换为’a’,‘b’。
  • 如果data参数值为多维数组,将多维数组转换为分组列表,例如将[​[‘a’,‘b’],[‘c’,‘d’]​]转换为(‘a’,‘b’),(‘c’,‘d’)。
  • 如果data参数值为其他对象,将对象转换为形如key='val’的键名/键值对,如果对象中含有子对象,将子对象转换为字符串。
  • 如果data参数值为undefined或null,将其转换为NULL。
  • 如果data参数值为NaN/Infinity,保留原参数值不变。由于MySQL数据库不支持这种数据类型,在数据库中写入这些值将在数据库内部抛出一个错误。
  1. 也可以通过在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]);
  1. 在有些情况下,程序中使用的SQL标识符(数据库名、数据表名或字段名等)也可能由用户来指定,可以使用Connection对象的escapeId方法对所有用户输入的SQL标识符进行escape编码处理,该方法的使用方式如下所示。
connection.escapeId(identifier);

在Connection对象的escapeId方法中,使用一个参数,参数值为一个字符串,用于指定需要进行escape编码处理的字符串。

escapeId方法的代码示例如下所示。

let sorter = 'date';
let query = 'SELECT * FROM posts ORDER BY ' + connection.escapeId(sorter);
console.log(query); // SELECT * FROM posts ORDER BY `date`

// 可以在identifier参数值中使用限定符
let sorter = 'date';
let query = 'SELECT * FROM posts ORDER BY ' +
connection.escapeId('posts.' + sorter);
console.log(query); // SELECT * FROM posts ORDER BY `posts`.`date`

可以在查询语句中使用“??”占位符来为SQL标识符使用参数,代码示例如下所示。

connection.query('SELECT * FROM ?? WHERE id = ?',['users', userId]);

2.1、新增

示例:在MySQL数据库的users表中插入一条数据,插入数据成功后查询users表中所有数据。users表中拥有三个字段,分别为自增整数值类型的ID主键字段、varchar类型的username字段与varchar类型的firstname字段。

const mysql = require('mysql');
let connection = mysql.createConnection({
    host     : 'localhost',
    port     : 3306,
    database : 'test',
    user     : 'root',
    password : '123456',
});
connection.connect((err)=>{
    if(err) {
        console.log('与MySQL数据库建立连接失败。');
    } else{
        console.log('与MySQL数据库建立连接成功。');
        connection.query('INSERT INTO users SET ?',
            {username:'凌牛',firstname:'陆'},
            (err,result)=>{
                if(err) {
                    console.log('插入数据失败。');
                } else{
                connection.query(
                    'SELECT * FROM ??',
                    ['users'],
                    (err,result)=>{
                        if(err) {
                            console.log('查询数据失败。');
                        } else{
                            console.log(result);
                            connection.end();
                        }
                });
            }
        });
    }
});
与MySQL数据库建立连接成功。
[ RowDataPacket { username: '凌牛', firstname: '陆' } ]

在一个具有自增主键字段的数据表中插入一条数据后,可以在回调函数中使用result参数值对象的insertId属性值获取该数据的主键值。

2.2、新增并返回自增id

示例:在MySQL数据库的users表中插入一条数据,插入数据成功后在控制台中输出该数据的ID字段值。

const mysql = require('mysql');
let connection = mysql.createConnection({
    host     : 'localhost',
    port     : 3306,
    database : 'test',
    user     : 'root',
    password : '123456',
});
connection.connect((err)=>{
    if(err) {
        console.log('与MySQL数据库建立连接失败。');
    } else{
        console.log('与MySQL数据库建立连接成功。');
        connection.query('INSERT INTO users SET ?',
            {username:'凌牛',firstname:'陆'},   
            (err,result)=>{
                if(err) {
                    console.log('插入数据失败。');
                } else{
                    console.log('插入数据的ID值为%d。',result.insertId);
                    connection.end();
                }
        });
    }
});
与MySQL数据库建立连接成功。
插入数据的ID值为1

2.3、多语句执行

基于安全原因,在mysql模块中,默认禁止使用多语句查询,因为如果开发者没有对用户输入数据进行escape编码处理,应用程序将有可能受到SQL注入攻击。如果要启用多语句查询功能,可以将createConnection方法所使用的options参数值对象中的multipleStatements属性值设定为true,代码如下所示。

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

示例:MySQL数据库的users表中同时插入三条数据,插入数据成功后修改第二条插入数据,插入成功后删除第三条插入数据,删除成功后查询users表中所有数据,并将这些数据输出在控制台中。

const mysql = require('mysql');
let tableName = "users";
let connection = mysql.createConnection({
    host     : 'localhost',
    port     : 3306,
    database : 'test',
    multipleStatements: true,
    user     : 'root',
    password : '123456',
});
connection.connect((err)=>{
    if(err) {
        console.log('与MySQL数据库建立连接失败。');
    } else{
        console.log('与MySQL数据库建立连接成功。');
        insertData();
    }
});
function insertData(){
    let sqlStr = "";
    for(let i=1;i<4;i++)
        sqlStr += "INSERT INTO " + tableName + "(username,firstname)"
            + "values(" + connection.escape("用户名" + i.toString()) + "," 
            + connection.escape("姓" + i.toString()) + ");";
        connection.query(sqlStr,(err,result)=>{
            if(err) {
                console.log("插入数据失败。");
            } else{
                updateData();
            }
    });
}
function updateData(){
   connection.query("update "+tableName+" set firstname = ? where username=?", 
        ["姓100","凌牛"],
        (err,result)=>{
            if(err) {
                console.log("更新数据失败。");
            } else{
            deleteData();
            }
    });
}
function deleteData(){
    connection.query("delete from "+tableName+" where username=?",
        ["用户名3"],
        (err,result)=>{
            if(err) {
                console.log("删除数据失败。");
            } else{
                queryData();
            }
    });
}
function queryData(){
    connection.query("SELECT * FROM "+tableName,(err,result)=>{
        if(err) {
            console.log('查询数据失败。');
        } else{
            console.log(result);
            connection.end();
        }
    });
}
与MySQL数据库建立连接成功。
[
  RowDataPacket { username: '凌牛', firstname: '姓100', id: 1 },
  RowDataPacket { username: '用户名1', firstname: '姓1', id: 2 },
  RowDataPacket { username: '用户名2', firstname: '姓2', id: 3 },
  RowDataPacket { username: '用户名1', firstname: '姓1', id: 5 },
  RowDataPacket { username: '用户名2', firstname: '姓2', id: 6 }
]

3、执行存储过程

由于可以通过使用multipleStatements属性值来同时执行多条语句,因此也可以利用该特性来执行存储过程。

接下来,我们在MySQL数据库中创建一个存储过程,该存储过程用于在users表中增加一条数据。该存储过程使用一个输入参数username、一个输入参数firstname与一个输出参数successFlag,其中username参数与firstname参数分别用于指定需要插入到users表中的username字段值与firstname字段值,successFlag参数为一个布尔类型的参数,当参数值为true时代表数据插入成功,参数值为false时代表数据插入失败。

create procedure insertuser(
	in username varchar(20),
	in firstname varchar(20),
	out successFlag smallint)
begin
    declare exit handler
    for sqlexception
    set successFlag=0;
    insert into users
    values(
        username,
        firstname,
				null
    );
    set successFlag=1;
end

示例:调用存储过程

const mysql = require('mysql');
let connection = mysql.createConnection({
    host     : 'localhost',
    port     : 3306,
    database : 'test',
    multipleStatements: true,
    user     : 'root',
    password : '123456',
});
connection.connect((err)=>{
    if(err) {
        console.log('与MySQL数据库建立连接失败。');
    } else{
        console.log('与MySQL数据库建立连接成功。');
        insertData();
    }
});
function insertData(){
    let sqlStr="call insertuser('111','2',@successFlag); select @successFlag;";
    connection.query(sqlStr,(err,result)=>{
        console.log(result);
        if(err) {
            console.log("插入数据失败。");
        } else{
            if(result[1][0]["@successFlag"]=="1")
                console.log("插入数据成功。");
            else
                 console.log("插入数据失败。");
        }
        connection.end();
    });
}
与MySQL数据库建立连接成功。
[
  OkPacket {
    fieldCount: 0,
    affectedRows: 1,
    insertId: 0,
    serverStatus: 10,
    warningCount: 0,
    message: '',
    protocol41: true,
    changedRows: 0
  },
  [ RowDataPacket { '@successFlag': 1 } ]
]
插入数据成功。

如果在存储过程内部查询一些数据并将其返回,那么可以在Collection对象的query方法中使用的回调函数中捕获到这些查询数据。

create procedure insertuser(
in username varchar(20),
in firstname varchar(20))
begin
    insert into users
    values(
      username,
      firstname,
			null
    );
    select * from users;
end

insertData函数中的代码如下所示,在执行了测试用存储过程后在控制台中输出存储过程中返回的所有数据。

function insertData(){
    var sqlStr="call insertuser ('凌牛','陆');";
    connection.query(sqlStr,(err,result)=>{
        if(err) {
            console.log("插入数据失败。");
        } else{
            console.log(result[0]);
        }
        connection.end();
    });
}
与MySQL数据库建立连接成功。
[
  RowDataPacket { username: '凌牛', firstname: '姓100', id: 1 },
  RowDataPacket { username: '用户名1', firstname: '姓1', id: 2 },
  RowDataPacket { username: '用户名2', firstname: '姓2', id: 3 },
  RowDataPacket { username: '用户名1', firstname: '姓1', id: 5 },
  RowDataPacket { username: '用户名2', firstname: '姓2', id: 6 },
  RowDataPacket { username: '1', firstname: '2', id: 8 },
  RowDataPacket { username: '111', firstname: '2', id: 9 },
  RowDataPacket { username: '凌牛', firstname: '陆', id: 10 }
]

4、执行多表查询

在mysql模块中,可以在Connection对象的query方法所使用的第一个参数值SQL语句中指定多张表的结合查询。但是如果在SQL语句中指定了相同的字段时(虽然这些字段并不属于同一张表)​,在默认情况下按代码中书写的字段顺序,将后一个字段覆盖前一个字段,从而产生我们不想要的结果。

为了说明这个问题,我们在MySQL数据库中建立两张表,分别为genres与books,其中genres数据表中存在两个字段,一个为主键自增整数类型字段id,另一个为varchar类型字段name。在genres表中存放一条数据,该数据id字段值为1,name字段值为“程序设计”​。books数据表中存在三个字段,一个为主键自增整数类型字段id,一个为用于与genres数据表中id字段值相关联的genreid字段,一个为varchar类型字段name。

create table `genres`(
	id int(10) auto_increment primary key,
	name varchar(50) not null
);

insert into genres VALUES(1, '程序设计');

create table `books`(
	id int(10) auto_increment primary key,
	genreid int(10) not null,
	name varchar(50) not null
);

insert into books VALUES
(1, 1, 'HTML5与CSS3全为指南'),
(2, 1, 'HTML5揭秘'),
(3, 1, 'HTML5游戏开发'),
(4, 1, 'HTML5高级程序设计'),
(5, 1, 'HTML5&CSS完全手册');

使用Connection对象的query方法结合查询这两张表中的所有数据,查询语句如下所示。

select genres.id,genres.name,books.id,books.genreid,books.name 
from genres
inner join books
on genres.id=books.genreid

示例:结合查询两张表中的所有数据

const mysql = require('mysql');
let connection = mysql.createConnection({
    host     : 'localhost',
    port     : 3306,
    database : 'test',
    user     : 'root',
    password : '123456',
});
connection.connect((err)=>{
    if(err) {
        console.log('与MySQL数据库建立连接失败。');
    } else{
        console.log('与MySQL数据库建立连接成功。');
        connection.query('select genres.id,genres.name,books.id,books.genreid,books.name '
                + 'from genres inner join books '
                + 'on genres.id=books.genreid',(err,result)=>{
                    if(err) {
                        console.log(err);
                        console.log('查询数据失败。');
                    } else{
                        console.log(result);
                        connection.end();
                    }
        });
    }
});
与MySQL数据库建立连接成功。
[
  RowDataPacket { id: 1, name: 'HTML5与CSS3全为指南', genreid: 1 },
  RowDataPacket { id: 2, name: 'HTML5揭秘', genreid: 1 },
  RowDataPacket { id: 3, name: 'HTML5游戏开发', genreid: 1 },
  RowDataPacket { id: 4, name: 'HTML5高级程序设计', genreid: 1 },
  RowDataPacket { id: 5, name: 'HTML5&CSS完全手册', genreid: 1 }
]

从这个结果中可以看出,genres表中的数据并没有被查询出来,因为该表的id字段与name字段被books表的id字段与name字段覆盖。

可以使用三种方法来解决这个问题:

  1. 第一种方法为在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){
    // 回调函数代码略
});
与MySQL数据库建立连接成功。
[
  RowDataPacket {
    id: 1,
    name: '程序设计',
    id1: 1,
    genreid: 1,
    name1: 'HTML5与CSS3全为指南'
  },
  RowDataPacket {
    id: 1,
    name: '程序设计',
    id1: 2,
    genreid: 1,
    name1: 'HTML5揭秘'
  },
  RowDataPacket {
    id: 1,
    name: '程序设计',
    id1: 3,
    genreid: 1,
    name1: 'HTML5游戏开发'
  },
  RowDataPacket {
    id: 1,
    name: '程序设计',
    id1: 4,
    genreid: 1,
    name1: 'HTML5高级程序设计'
  },
  RowDataPacket {
    id: 1,
    name: '程序设计',
    id1: 5,
    genreid: 1,
    name1: 'HTML5&CSS完全手册'
  }
]
  1. 第二种方法为在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){
    // 回调函数代码略
});
与MySQL数据库建立连接成功。
[
  RowDataPacket {
    genres: { id: 1, name: '程序设计' },
    books: { id: 1, genreid: 1, name: 'HTML5与CSS3全为指南' }
  },
  RowDataPacket {
    genres: { id: 1, name: '程序设计' },
    books: { id: 2, genreid: 1, name: 'HTML5揭秘' }
  },
  RowDataPacket {
    genres: { id: 1, name: '程序设计' },
    books: { id: 3, genreid: 1, name: 'HTML5游戏开发' }
  },
  RowDataPacket {
    genres: { id: 1, name: '程序设计' },
    books: { id: 4, genreid: 1, name: 'HTML5高级程序设计' }
  },
  RowDataPacket {
    genres: { id: 1, name: '程序设计' },
    books: { id: 5, genreid: 1, name: 'HTML5&CSS完全手册' }
  }
]
  1. 第三种方法为在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){
    // 回调函数代码略
});
与MySQL数据库建立连接成功。
[
  RowDataPacket {
    genres_id: 1,
    genres_name: '程序设计',
    books_id: 1,
    books_genreid: 1,
    books_name: 'HTML5与CSS3全为指南'
  },
  RowDataPacket {
    genres_id: 1,
    genres_name: '程序设计',
    books_id: 2,
    books_genreid: 1,
    books_name: 'HTML5揭秘'
  },
  RowDataPacket {
    genres_id: 1,
    genres_name: '程序设计',
    books_id: 3,
    books_genreid: 1,
    books_name: 'HTML5游戏开发'
  },
  RowDataPacket {
    genres_id: 1,
    genres_name: '程序设计',
    books_id: 4,
    books_genreid: 1,
    books_name: 'HTML5高级程序设计'
  },
  RowDataPacket {
    genres_id: 1,
    genres_name: '程序设计',
    books_id: 5,
    books_genreid: 1,
    books_name: 'HTML5&CSS完全手册'
  }
]

5、以数据流的方式处理查询数据

有时,用户也许会查询大量数据并希望单独处理每一条查询到的数据。在mysql模块中,Connection对象的query方法返回一个可用于处理流数据的对象。如果要使用query方法返回的对象,那么在query方法中不能使用callback参数值指定回调函数。在接收查询数据的过程中,该对象可能触发下表中的4个事件。

事件触发时机回调函数使用参数
fields当接收到该表中的所有字段时触发参数值为该表中的所有字段
result当接收到该表中的一条数据时触发参数值为该表中的一条数据
end当接收完该表中的所有数据时触发该回调函数不使用参数
error当接收数据的过程中产生错误时触发参数值为被触发的错误对象

下面查看一个使用Connection对象的query方法返回对象读取数据库中的所有字段及所有数据并向文件中写入这些数据的代码示例。

在该示例代码中,我们监听query方法返回对象的error事件,当读取数据过程中发生错误时在控制台中输出“读取数据失败:错误信息为:​”字符串以及被触发的错误信息,然后退出进程。监听query方法返回对象的fields事件,当读取到该表中的所有字段后将所有字段写入应用程序根目录下的message.txt文件中,监听query方法返回对象的result事件,当读取到该表中的一条记录后首先使用Connection对象的pause方法暂停读取后续数据,然后将读取到的记录写入应用程序根目录下的message.txt文件中,然后使用Connection对象的resume方法恢复读取后续数据。在对读取到的记录执行一个耗时较长或消耗资源较多的I/O操作时,Connection对象的pause方法与resume方法可以帮助减轻读取大数据量时的压力。最后监听query方法返回对象的end事件,当所有数据被读取完毕时在控制台中输出“数据全部写入完毕。​”字符串。

const mysql = require('mysql');
const fs = require('fs');
let connection = mysql.createConnection({
    host     : 'localhost',
    port     : 3306,
    database : 'test',
    user     : 'root',
    password : '123456',
});
let out = fs.createWriteStream('./message.txt');
out.on('error',(err)=>{
    console.log('写文件操作失败。错误信息为:'+err.message);
    process.exit();
});
connection.connect((err)=>{
    if(err) {
        console.log('与MySQL数据库建立连接失败。');
    } else{
        console.log('与MySQL数据库建立连接成功。');
        let query = connection.query('select * from users');
        query.on('error', (err)=>{
            console.log('读取数据失败:错误信息为:'+err.message);
            process.exit();
        }).on('fields', (fields)=>{
            let str = "";
            fields.forEach((field)=>{
                if(str != "")
                    str += String.fromCharCode(9);
                str += field.name;
            });
            out.write(str+'\r\n');
        }).on('result', (row)=>{
            connection.pause();
            out.write(row.id + String.fromCharCode(9) + row.username +
                String.fromCharCode(9) + row.firstname + '\r\n',
                (err)=>{
                    connection.resume();
                });
        }).on('end', ()=>{
            console.log('数据全部写入完毕。');
            connection.end();
        });
    }
});
username	firstname	id
1	凌牛	姓100
2	用户名1	姓1
3	用户名2	姓2
5	用户名1	姓1
6	用户名2	姓2
8	1	2
9	111	2
10	凌牛	陆

6、创建连接池

在开发一个Web应用程序时,连接池是一个非常重要的概念,因为建立一个数据库连接所消耗的性能成本是比较高的。在服务器应用程序中,如果为每一个接收到的客户端请求都建立一个或多个数据库连接,将严重降低应用程序的性能。因此,在服务器应用程序中,通常需要为多个数据库连接创建并维护一个连接池,当连接不再需要使用时,这些连接可以缓存在连接池中,当接收到下一个客户端请求时,可以从连接池中取出连接并重新利用,而不需要再重新建立数据库连接。

在mysql模块中,可以使用createPool方法创建连接池,该方法的使用方式如下所示。

let pool = mysql.createPool(options);

在createPool方法中,使用一个参数,参数值为一个对象,用于指定该连接池中所有连接的统一使用的各种选项。可以使用的属性包含createConnection方法中可以使用的各种属性,除此之外,还可以使用如下所示的一些属性。

  • createConnection:在建立了连接池之后,不需要显式使用createConnection方法连接,可以直接使用连接池对象的getConnection方法从连接池中获取一个连接。如果连接池中没有可用连接且当前在用的连接数小于连接池的最大连接数时,将隐式地建立一个数据库连接。createConnection属性值为一个函数,用于指定建立连接时使用的函数,默认属性值为mysql.createConnection(即使用createConnection方法建立连接)​。
  • waitForConnections:该属性值为一个布尔值,用于指定当连接池中已经没有可用连接,且当前在用的连接数已等于连接池的最大连接数时所执行的处理。如果属性值指定为true,则挂起数据库连接请求,直到一个当前在用的数据库连接被释放时使用该数据库连接。如果属性值指定为false,将立即抛出一个错误。默认属性值为true。
  • connectionLimit:该属性值为一个整数值,用于指定连接池中的最大连接数,默认属性值为10。
  • queueLimit:该属性值为一个整数值,用于指定允许挂起的最大连接数,如果挂起的连接数超过该数值,将立即抛出一个错误。默认属性值为0,代表不指定允许被挂起的最大连接数。

在建立了连接池之后,可以直接使用连接池对象的getConnection方法从连接池中获取一个连接。如果连接池中没有可用连接,将隐式地建立一个数据库连接。getConnection方法的使用方式如下所示。

pool.getConnection(callback);

在连接池对象的getConnection方法中,使用一个参数,参数值为一个回调函数,用于指定获取连接操作结束时执行的处理,该回调函数的指定方法如下所示。

function(err, connection) {
    // 回调函数代码略
}

在该回调函数中,使用两个参数,其中第一个参数为获取连接操作失败时触发的错误对象,第二个参数值为一个对象,代表获取到的连接对象,当获取连接操作失败时,该参数值为undefined。

当一个连接不需要使用时,可以使用该连接对象的release方法将其归还到连接池中,该方法的使用方式如下所示。

connection.release();

当一个连接不需要使用且需要从连接池中移除时,可以使用该连接对象的destroy方法,该方法的使用方式如下所示。

connection.destroy();

当该连接移除后,连接池中的连接数减1。

当一个连接池不需要使用时,可以使用连接池对象的end方法关闭该连接池,该方法的使用方式如下所示。

pool.end();

示例:创建一个用于连接本地MySQL数据库服务器中mysql数据库的连接池,当连接池创建完毕后,使用连接池对象的getConnection方法隐式创建并返回一个数据库连接,当数据库连接返回后,使用该连接对象的query方法查询users表中的所有数据,当查询操作执行成功后在控制台中输出所有查询到的数据,然后使用连接池对象的end方法关闭连接池,以允许应用程序退出。

const mysql = require('mysql');
let pool = mysql.createPool({
    host     : 'localhost',
    port     : 3306,
    database : 'test',
    user     : 'root',
    password : '123456',
});
pool.getConnection((err, connection)=>{
    if(err) {
        console.log('与MySQL数据库建立连接失败。');
    } else{
        console.log('与MySQL数据库建立连接成功。');
        connection.query( 'select * from users', (err, rows)=>{
            if(err) {
                console.log('查询数据操作失败。');
            } else{
                console.log(rows);
                pool.end();
            }
        });
    }
});
与MySQL数据库建立连接成功。
[
  RowDataPacket { username: '凌牛', firstname: '姓100', id: 1 },
  RowDataPacket { username: '用户名1', firstname: '姓1', id: 2 },
  RowDataPacket { username: '用户名2', firstname: '姓2', id: 3 },
  RowDataPacket { username: '用户名1', firstname: '姓1', id: 5 },
  RowDataPacket { username: '用户名2', firstname: '姓2', id: 6 },
  RowDataPacket { username: '1', firstname: '2', id: 8 },
  RowDataPacket { username: '111', firstname: '2', id: 9 },
  RowDataPacket { username: '凌牛', firstname: '陆', id: 10 }
]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值