一、第三方模块的使用和数据库访问的通用步骤
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); });
--本文结束,感谢您的阅读。