一、第三方模块的使用和数据库访问的通用步骤
1、第三方模块的使用步骤
Node.js 的第三方模块的使用基本上是两个步骤:
第一步:安装三方模块: npm install 模块名 或者 cnpm install 模块名;
第二步:加载模块。
其实,Node.js 对 NoSql (Not only sql 不仅仅是SQL,非关系型数据库)数据库支持度比较好。非关系型数据库集成JS的执行环境,BOSN数据类型进行存放。典型的非关系型数据库是MongoDB等。然而我没有接触过非关系型数据库,这里就使用MySQL了。
2、访问数据库的通用操作
第一步:创建数据库的连接并建立连接
第二步:执行 sql 语句
第三步:处理结果集
第四步:关闭数据库连接
二、第三方模块 MySQL 模块
1、简单的使用
1.1 创建数据库的连接对象 : createConnection( )
const conn = mysql.createConnection( opt )
opt 是数据库连接信息的配置,
let opt = { host:"127.0.0.1", port:"3306", user:"root", password:"", database:"数据库名" };
1.2 执行 sql 语句 : query( )
conn.query(sql [,params] , callback(error , result , fields) )
let sql = “select * from 表名”;
回调函数callback有三个参数:
error:执行错误信息,执行成功返回null;
result: 返回执行sql语句的结果。受影响的行数和执行时间,以及执行查询操作的查询结果。
fields: 表的结构和字段类型
query( ) 方法会自动执行建立连接的方法
conn.query(sql ,(error , result , fields )=>{ ...... });
1.3 处理结果集
conn.query(sql ,(error , result , fields )=>{ 处理结果集 });
1.4 关闭连接
方法一:conn.end( )
延迟等待关闭:接受到关闭信号之后,不立即结束和数据库的连接,等待所有的sql执行完成后再释放资源。
方法二:conn.destroy( )
立即关闭:接收到关闭信号后,立即释放资源,不关心是否存在尚未完成的sql。
通常我们使用 conn.end( ) 关闭连接,需要重启或者关闭服务器时使用conn.destroy( )。
代码事例
// 第三方模块使用
// 下载第三方模块
// 加载三方模块
const mysql = require("mysql");
// 访问数据库操作
// 1. 创建数据库
// 1.1 配置数据库信息
let opt = {
host: "127.0.0.1",
port:"3306",
user:"root",
password:"",
database:"数据库名"
};
// 1.2 创建数据库连接对象
const conn = mysql.createConnection(opt);
// 1.3 连接数据库,用于测试连接
// conn.connect((error)=>{
// console.log(error);
// })
// 2 执行 sql 语句
// 2.1 sql 语句
let sql =" select * from t_user";
// 2.2 执行sql语句,query()方法中自带建立连接方法,所以不需要再连接数据库
conn.query(sql,(error,result,fields)=>{
// 3 处理结果集
// 简单的打印出结果
console.log(error);
console.log(result);
});
// 4 关闭数据库连接
conn.end();
2、数据库连接参数
mysql.createConnection(opt)中的opt是数据库的连接参数,具体配置信息如下:
// opt 的配置项
const mysql = require("mysql");
let opt = {
host:"127.0.0.1",
port:"3306",
user:"root",
password:"",
database:"数据库名"
// 参数项很多,注意常用选项
// 参数的解释
/*
host: 数据库连接地址 (Default: localhost) HTTP 协议连接
port: 数据了连接端口 (Default: 3306)
user: 数据库连接用户名
password: 数据库连接密码
database: 默认连接库的指定
charset: 连接数据的默认字符集 (Default: 'UTF8_GENERAL_CI') sql :set names utf8;
timezone: 设置数据的仿佛闻时区,当前程序运行的本地时区 +HH:MM or -HH:MM. (Default: 'local')
localAddress: 集群服务器数据库连接 (TCP/IP)
socketPath: 套接字通信地址 广域网IP连接方式 (连接UNIX通信连接地址)
connectTimeout: 连接超时时间 (Default: 10000ms)
stringifyObjects: 是否将对象强制装换为字符串格式 (date time datetime year ) (Default: false)
dateStrings: 是否开启时间和字符串的自动转换,(Default: false)
typeCast: 是否开启数据的格式转换 (int,float……)=>number varchar=>string date=>Date …… (Default: true)
bigNumberStrings: 高精度数值存储转换 将超过js Number 可以存放的数值转换为字符串进行转换,以保持精度
debug: 是否开启调试模式 (Default: false)
insecureAuth: 是否可以使用旧连接信息进行认证登录(Default: false)
queryFormat: 查询语句占位符定义(查询占位符)
supportBigNumbers: 是否可以直接使用数据库大数据存储模式 (Default: false).
trace: 是否可以消息跟踪模式 (Default: true)
multipleStatements: 是否可以在一个query方法中执行 多条 sql (Default: false)
flags: 数据库的连接标识符
ssl: 加密连接信息
*/
};
3、占位符 ?和 ??
在conn.query(sql [,params] , callback(error , result , fields) )方法中有个参数 params,用来携带sql语句中的占位符(?或者 ??)所代表的变量。
如:let sql = "select * from t_user where userTel = ? ";
let params = 电话号码;
conn.query(sql , params ,callback(error , result ,fields) )。
这样在执行sql语句时,等价于 let sql = "select * from t_user where userTel = 电话号码 "。
params 参数的 sql 拼接方式取决于 params 的数据类型,常见的是 Number、String、Boolean、Array、Object物种使用方法。具体字母使用,见下:
3.1 占位符 ‘?’:值替换符
/*
Numbers 不做任何操作直接替换一个 ?
let no = 1;
select * from table where id = ?
select * from table where id = 1
Strings 将值 包裹在 ' ' 之间进行 一个 ? 替换
let name = "tom";
select * from table where name = ?
select * from table where name = 'tom'
Booleans 会被替换成 true/false 替换一个 ?
数据库会将 true 替换为 1
false 替换为 0
Date 被转化为 'YYYY-mm-dd HH:ii:ss' 字符串
数据库会根据类型自动识别
Buffers 将缓存对象转换为 HEX 编码 替换一个 ?
会在 十六进制前 增加 X ==> 数据库会根据类型自动转换
undefined 或 null 转换为 NULL
NaN 和 Infinity 不转换直接报错
NaN (值) ==> 意思叫做不是数值 类型 Number
Infinity (值) ==> 意思是无穷 类型 Number
Arrays 一维数组会被转换为 固定格式 '值' '值' '值' …… 值得个数替换多个 ?
其中 '' 取决于元素值得类型
let arr = ["a",2,new Date(),true];
let sql = insert into table (c1,c2,c3,c4) values (?,?,?,?);
insert into table (c1,c2,c3,c4) values ('a',2,'YYYY-MM-DD HH:ii:ss',true);
多维数组 (二维) ['值','值','值'…… ] ['值','值','值'…… ] 以一维数组的元素个数为基准替换对应的 ?个数
let arr = [[1,2,3],[4,5,6]];
1,2,3 ==> ?
4,5,6 ==> ?
in(v,v,v)
not in(v,v,v)
Objects {key:value} ==> 存放具有关联性的数据 ==> {name:"aaa",nikeName:"bbbb",loginName:"ccc",pwd:"dddd"}
转换规则 {key1:value1,key2:value2} ==> 一个整体 key1=value1,key2=value2 ==> 用于替换一个 ?
update table set cName=value,cName=value,……
let user = {nikeName:"tom",userTel:"123456",password:"123",userImgUrl:"ssss.png"}
--- insert into table (……) values (?,?,?,?);
insert into table set 列名=值,列名=值,……
==> insert into table set ?
ArraysAndObjects [{},{}]
{} => 替换一个 ? ==> key1=value1,key2=value2
{} => 替换一个 ? ==> key1=value1,key2=value2
ObjectsAndArrays {key1=[a,b],key2=[c,d]}
key1=[a,b],key2=[c,d] ==> 替换一个 ?
例如
select * from t_user where id in( ? ) and userTel = ?;
let params = [[],String]
update t_user set ? where id = ?;
let params = [{},number]
……
*/
3.2 占位符 ‘??’:列替换符
在用 “?” 替换时,会多出 ‘ ’,但是在 sql 语句中列名和表名没有单引号,若用 “?” 替换时,会报错。此时就会用到 ‘??’。
4、数据库连接池
4.1 连接池介绍
百度百科:连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
个人理解:在服务器启动的时候,建立多个数据库连接放入连接池。每当用户访问数据库时,直接从连接池中取出一个已建立的空闲连接对象,对数据库进行操作。使用完成后,该连接转为空闲状态,等待用户再次使用(不会关闭连接)。若用户访问数据库时连接池中没有空闲连接,则该用户进入等待队列等待空闲的连接对象(队列先进先出原则,栈先进后出原则)。
4.2 连接池的使用方法
第一步:创建连接池。先配置数据库连接参数设置,然后再创建连接池:const pool = mysql.createPool(opt);
第二步:获取连接池中空闲对象: pool.getConnection((error,conn)=>{...});
第三步:执行 sql 语句:conn.query(sql ,callback);
第四步:处理结果集
第五步:释放当前连接:pool.releaseConnection(conn);
具体使用到代码如下:
const mysql = require("mysql");
// 1. 创建连接池
// 1.1 数据库连接参数设置
let opt = {
// 数据库连接池参数 包含 数据库创建方法createConnection中的所有参数
host: "127.0.0.1",
port: "3306",
user: "root",
password: "",
database:"tournote"
// 连接池特有参数,包括 初始化时空闲的连接对象个数、队列等待方式、连接池超时时间等
// acquireTimeout: 获取连接时的 超时时间 (Default: 10000ms)
// waitForConnections: 用户请求连接时,如果没有空闲连接或者连接上限处理方式 (Default: true)
// true:用户继续等待
// false:直接拒绝用户请求
// connectionLimit: 在连接创建时 该属性只被执行一次:初始化连接池空闲连接数量. (Default: 10)
// queueLimit: 队列允许 最大的等待用户数 (Default: 0)
// 0 ==> 无上限值
}
// 1.2 创建连接池
const pool = mysql.createPool(opt);
// 2. 获取连接池中空闲对象
// pool.getConnection(callback(error,conn));
// error 表示获取对象是否成功,成功时返回 null
// conn 成功后返回的连接对象
pool.getConnection((error,conn)=>{
if(error){//获得到空闲连接对象时,返回null,所以error不为null时表示未获得空闲连接对象
console.log("获取连接池中空闲连接失败"+error.message);
return;
}
// 3. 执行 sql 语句
let sql = "select * from t_user";
conn.query(sql,(error,result,fields)=>{
// 4. 处理结果集
if(error){
console.log("执行失败");
return;
}
console.log(result);
});
// 5. 释放当前连接
pool.releaseConnection(conn);
});
5、封装工具模块
封装数据库连接工具模块(类似于java语言对工具类),可以简化在项目对于同一个数据库进行操作时 重复定义代码。
作用:不需要重复创建、配置参数和重复获取数据库连接,将结果交给用户自己处理。
具体封装的工具模块如下:其中用到了连接池技术和es6 中 promise。
注意:下属代码中,exec = function (sql ,params) {....}应该改为exec = function (sql="select 1",params=[]) {....}。这里之所以写错,是为了代码显示颜色,可读性好。这里应该是编译器到漏洞。若我正确写,下述代码将变成全白一片。
// 获取 node.js 的第三方模块 mysql
const mysql = require("mysql");
// 数据库连接参数设置
let opt = {
host:"127.0.0.1",
port:"3306",
user:"root",
password:"",
database:"数据库名"
}
// 创建连接池
const pool = mysql.createPool(opt);
// 定义执行函数
//这里注意了,注意!注意!这里参数sql和params应该有默认值,但是开源中国博客这里的编译器有点问题,加上参数代码代码高亮显示就没了,阅读性太差
const exec = function(sql,params){//这里参数sql和params应该有默认值,像这样sql="select 1",params=[]
return new Promise((resolve,reject)=>{
// 执行sql 获取结果
pool.query(sql,params,(error,result,fields)=>{
if(error){
reject(error);
return;
}
let obj = {
"result":result,
"fields":fields
}
// Node.js 中 Promise 的resolve 方法只能接受一个参数
resolve(obj);
// resolve({result,fields})
});
});
}
module.exports = {
exec
}
本工具类的使用:
// 1、加载工具模块
const { exec } = require("./dbUtil");
// 2、编写sql
let sql1 = "select * from t_user where nikeName = ? ";
let sql2 = "select * from t_tour where userId = ? ";
let nikeName = ["haha"];
// 3、执行sql(调用exec()方法), promise的方法,成功执行.then()方法,失败执行.catch()方法
exec(sql1,nikeName).then(({result,fields})=>{
console.log(result[0].id);
return exec(sql2,result[0].id);
}).then(({result,fields})=>{
console.log(result);
}).catch((error)=>{
console.log(error.message);
});
--本文结束,感谢您的阅读。