mysql2 的使用及对查询结果的转换
安装
pnpm add mysql2
连接数据库
安装完毕后即可导入使用,同在 Navicat 中一样,想要操作数据库的前提是得连接上数据库。可以通过 createConnection
方法,传入选项对象创建连接:
注意,如果端口号不是默认的 3306,选项对象中可以添加 port 配置。另外还有很多配置,可查看 mysql 这个库的使用说明。
查询
单表查询
连接上数据库后,就可以进行查询了,比如我们想查询表 members 的信息:
可以将 sql 语句作为第 1 个参数传给 connection.query()
,并通过第 2 个参数来接收查询结果:
connection.query('SELECT * FROM `members`', function (err, results, fields) {
if (err) {
console.log(err)
return
}
console.log(results) // 结果集
console.log(fields) // 额外的元数据(如果有的话)
})
查询结果中,results
是一个数组,其子项为表 members 的每一条数据转为的对象 :
fields
也是一个数组,子项是每个字段的一些信息:
多表查询
上面是对单张表的查询,如果查询���张表,执行的 sql 语句如下:
SELECT * FROM members
JOIN singer_and_city sac ON members.id = sac.singer_id
JOIN regions ON sac.city_id = regions.id WHERE members.id = 1;
则在 Navicat 中的结果如下:
通过 mysql2 在 node 程序中执行:
connection.query(
'SELECT * FROM members JOIN singer_and_city sac ON members.id = sac.singer_id JOIN regions ON sac.city_id = regions.id WHERE members.id = 1;',
function (err, results) {
console.log(results)
}
)
结果如下:
可以看到出问题了,比如有 3 个字段的名称都为 id,最终就展示了表 regions 的 id;有 2 个字段的名称都为 name,最终展示的是 regions 的 name。为了解决这个问题,我们可以通过给字段设置别名的方式解决,但其实关于地点的信息,也就是存储在 regions 表的信息,应该另外包裹成一个 region 对象,放置在每条数据对象中。
将查询结果转换为对象
如下,使用聚合函数 JSON_OBJECT()
,创建一个对象,通过 AS
命名为 region
,将 regions 的 id 信息赋值给 region
的 id
,将 regions 的 name 信息赋值给 region
的 name
:
SELECT
members.id,
members.`name`,
JSON_OBJECT('id', regions.id, 'name', regions.name) AS region
FROM members
JOIN singer_and_city sac ON members.id = sac.singer_id
JOIN regions ON sac.city_id = regions.id WHERE members.id = 1;
在 Navicat 执行上述语句得到的结果如下:
在 node 程序中运行:
connection.query(
"SELECT members.id, members.`name`, JSON_OBJECT('id', regions.id, 'name', regions.name) AS region FROM members JOIN singer_and_city sac ON members.id = sac.singer_id JOIN regions ON sac.city_id = regions.id WHERE members.id = 1;",
function (err, results, fields) {
console.log(results)
}
)
得到的结果如下:
将查询结果转换为数组
为了方便演示,所以上面的演示中我只查看了 Jay 的相关信息,如果去掉查询条件 WHERE members.id = 1
,得到的结果通过 node 执行将是:
在 Navicat 中查询则是:
可以看到,关于歌手的 id 和 name 信息是存在重复的,我们可以对查询结果进一步地组织,按照歌手 id 进行分组,然后对 region 字段使用聚合函数 JSON_ARRAYAGG()
处理,转成数组:
SELECT
members.id,
members.`name`,
JSON_ARRAYAGG(JSON_OBJECT('id', regions.id, 'name', regions.name)) AS regionList
FROM members
JOIN singer_and_city sac ON members.id = sac.singer_id
JOIN regions ON sac.city_id = regions.id
GROUP BY members.id;
这样在 Navicat 中得到的结果如下:
通过 node 执行得到的就是如下所示的数组:
通过接口的形式在浏览器中查看,可以看到结果是我们所希望的:
预处理语句(Prepared Statements)的使用
mysql2 的一个特性就是可以使用如下所示的,在查询条件部分使用了 ?
来代替具体值的预处理语句:
SELECT * FROM members WHERE `level` > ?;
在 mysql2 中,通过 connection.execute()
来执行预处理语句,在第 2 个参数中传入具体的查询参数,因为可能有多个条件,所以是个数组:
connection.execute(
'SELECT * FROM members WHERE `level` > ?;',
[1],
function (err, results, fields) {
console.log(results)
}
)
执行结果如下:
使用预处理语句的好处
提高性能
预处理语句会被 MySQL 编译并储存,当给 ?
提供了具体的值时再真正执行查询。所以如果多次执行同样的查询,可以有效地节省准备查询时间,获得更高的性能。
防止 SQL 注入
假设客户端网站有个输入框,只有输入正确的 name(比如 Jay)才能获取对应的信息,服务器端会使用如下语句进行查询:
SELECT * FROM members WHERE `name` = 'Jay';
那么法外狂徒张三就会开动他的小脑筋,尽管他不知道表 members 里到底存储了哪些 name,但他只要想办法让查询时的语句如下:
SELECT * FROM members WHERE `name` = 'Jay' OR 'a'!='b';
也就是在查询条件中多注入了一个判断条件 OR 'a'!='b'
,那么对于 members 中的每一条记录来说,'a'!='b'
都是成立的,他就可以轻松获取 members 里的所有数据了。
但是如果使用了预处理语句:
SELECT * FROM members WHERE `name` = ?;
MySQL 在解析时就知道查询只有一个条件,即 name 是否等于某值,张三想要再使用 OR
添加判断条件的企图就难以得逞。
连接池(connection pools)的使用
同一时间我们的服务器可能被多个客户端访问,如果使用 mysql.createConnection()
与数据库只建立一个连接,就可能导致某个请求发起时,连接正被占用。解决这一问题,我们可以使用 mysql2 提供的连接池:
连接池通过重用以前的连接来帮助减少连接到 MySQL 服务器所花费的时间,当你完成它们时让它们保持打开而不是关闭。
创建方法如下:
其中 connectionLimit
用于指定创建连接的最大数量。之后的使用和建立单个连接的情况是一样的,只需要把 connection
替换成 pool
即可。如此,当有新的请求时,就会去连接池里寻找闲置的连接来查询数据库。
判断连接是否成功
我们可以通过 pool.getConnection()
来从池中获取连接:
pool.getConnection((err, conn) => {
// 获取连接错误
if (err) {
console.log(err)
return
}
// 连接获取未报错
conn.connect(err => {
if (err) {
console.log(err)
return
}
console.log('数据库连接成功')
})
})
使用 conn.connect()
来进行连接,如果没有报错,说明可以成功连接上数据库。
请求结果的 Promise 封装
之前请求到的数据都是直接通过传入的回调函数获取,容易产生回调地狱,mysql2 支持 Promise API,只需要调用 connection
或 pool
的 promise()
函数,然后再继续调用 query()
或 execute()
,返回的就是一个 promise 对象,查询结果可以通过 .then()
的方式接收:
const promisePool = pool.promise()
promisePool
.execute('SELECT * FROM members WHERE `level` > ?;', [1])
.then(res => console.log(res))
.catch(err => console.log(err))
得到的 res
是一个数组,里面包含了 2 个数组,第一个数组对应之前使用回调形式获取结果时的 results
,第二个数组对应的是 fields
,可以直接在获取结果时进行解构:
.then(([rows, fields]) => {
console.log(rows)
console.log(fields)
})
结果如下:
转载自:https://juejin.cn/post/7389913087471992884