文章目录
- 参考
- 描述
- mysql 模块
- 连接数据库
- 检测
- 基本操作
- 查询
- 数据与代码分离原则
- 占位符
- 插入
- 另一种姿态
- 修改
- 另一种姿态
- 删除
- 标记删除
参考
项目 | 描述 |
---|---|
哔哩哔哩 | 黑马程序员 |
搜索引擎 | Bing |
描述
项目 | 描述 |
---|---|
NodeJS | v18.13.0 |
nodemon | 2.0.20 |
MySQL | 5.7.40 |
mysql | 2.18.1 |
mysql 模块
npm(NodeJS Package Manager) 包管理器提供了第三方模块 mysql,该模块可用于实现 NodeJS 与 MySQL 数据库的连接。
在终端中使用如下命令开始对第三方模块 mysql 的安装:
npm install mysql
在安装该模块后,你可以在 NodeJS 中使用如下代码将该模块进行导入:
const mysql = require('mysql');
连接数据库
提醒:
在实践本示例中的代码时请确保你已经安装了 MySQL 并已经开启了 mysql 服务。
我们可以使用 mysql.createPool() 函数来创建一个与 MySQL 的连接,你需要向该函数传递一个对象,该对象描述了 MySQL 数据库管理系统的相关信息。
const mysql = require('mysql');
// 建立 NodeJS 与 MySQL 的连接
const db = mysql.createPool({
// MySQL 所在的计算机的 IP 地址
host: '127.0.0.1',
// 用户名称
user: 'root',
// 登录密码
password: '123456',
// 需要使用的数据库
database: 'db_test',
// MySQL 监听的端口号
port: 3360
})
注:
- 提交给 mysql.createPool() 函数的对象中的 password 属性不能是数值类型。如果将属性 password 的值 ‘123456’ 中的单引号去掉,那么在正式连接数据库时(此时只是提供连接需要使用到的信息,并没有开始连接)终端将抛出如下错误信息:
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received type number (123456)
- 如果你需要连接的 MySQL 的位于当前计算机中,则你可以省略 host 属性;如果 MySQL 监听的端口为默认端口 3306 时,你可以将 port 属性省略。
检测
你可以在原代码的基础上添加如下代码来检测 NodeJS 与 MySQL 的连接是否成功。
// 对数据库进行查询操作
db.query('select 999', (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else{
console.log('{ Win }');
}
})
执行该程序,如果终端输出 { Win } ,则表明数据库已经成功连接;如果终端中输出 { Lose },请检查连接 MySQL 所需要用到的相关信息是否正确。
注:
在 NodeJS 对数据库进行查询等操作时 NodeJS 将正式向 MySQL 发起连接,所以通过此法可以检查数据库是否可以正常连接。
基本操作
查询
db.query()
使用 db.query() 函数可以对数据库进行查询操作,该函数可以接收两个参数,第一个参数是对数据库进行查询操作时所需要使用的 SQL 查询语句,第二个参数则是查询操作完成后需要执行的回调函数。
你可以向回调函数提交两个参数,其中第一个参数用以接收查询操作过程中可能产生的错误对象,第二个参数用以接收查询成功后数据库返回的查询结果。
举个栗子:
先瞅瞅我的 users 表中有些什么东西:
我们可以通过 NodeJS 查询该表中 id 为 1 的数据:
const mysql = require('mysql');
// 建立 NodeJS 与 MySQL 的连接
const db = mysql.createPool({
// MySQL 所在的计算机的 IP 地址
host: '127.0.0.1',
// 用户名称
user: 'root',
// 登录密码
password: '123456',
// 需要使用的数据库
database: 'db_test',
// MySQL 监听的端口号
port: 3360
})
// 对数据库进行查询操作
db.query('select * from users where id = 1', (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else{
console.log(result[0]);
}
})
执行结果:
在执行上述示例中的代码后,终端将输出如下内容:
[ RowDataPacket { id: 1, username: ‘RedHeart’, password: ‘TwoMoons’ } ]
注:
- 在使用 mysql 模块的 db.query() 函数数据库中的内容进行查询时将得到一个包含一个或多个对象的数组。
- 在上述示例中,我们在查询过程中使用了 console.log(err.message); 来打印错误信息,这有助于我们在发现错误时能够快速的解决错误。
数据与代码分离原则
在编写后端代码时,一定要注意数据与代码分离的原则。
注入攻击是 Web 安全领域中一种最为常见的攻击方式。
注入攻击的本质,是把用户输入的数据当作代码执行。这里有两个关键条件,第一个是用户能够控制输入;第二个是原本程序要执行的代码,拼接了用户的输入。
上述内容引用自吴瀚清的 《白帽子讲Web安全》
当你需要用到用户提供的数据时,一定要小心,恶意的用户会构造特定的数据对你的应用进行攻击。
占位符
为了遵循数据与代码分离的原则,避免网页被 SQL 注入 攻击,mysql 模块为我们提供了占位符这一特性。
在 mysql 模块中,你可以使用 ? 来代替某个数据。在使用占位符代替数据后,请不要忘记向 mysql 模块提供需要被替代的数据。
向 mysql 模块提供被代替的数据的方式依照需要被替代的数据而定,具体如下:
- 当被替代的数据有多个时,你可以通过向 db.query() 函数提供三个实参(其中两个实参我们前面已经见过),第二个实参需要为一个数组,数组中的内容与 SQL 语句中的占位符按从左到右的顺序一一对应。例如:
db.query('select * from users where id = ? and username = ?', [1, 'RedHeart'], (err, result) => {})
- 当被替代的数据仅有一个时,你可以通过你可以通过向 db.query() 函数提供三个实参(其中两个实参我们前面已经见过),第二个实参可以为一个仅包含一个元素的数组也可以为被替代的数据。例如:
db.query('select * from users where id = ?', 1, (err, result) => {})
或:
db.query('select * from users where id = ?', [1], (err, result) => {})
注:
并不是 SQL 语句中的所有内容都可以被替换,例如:
db.query('select * from users where ? = 1', 'id', (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else{
console.log(result);
}
})
终端中的输出将为(一个空数组):
[]
在使用占位符时,仅 SQL 语句中的数据部分可以被替换 。
插入
const mysql = require('mysql');
// 建立 NodeJS 与 MySQL 的连接
const db = mysql.createPool({
// MySQL 所在的计算机的 IP 地址
host: '127.0.0.1',
// 用户名称
user: 'root',
// 登录密码
password: '123456',
// 需要使用的数据库
database: 'db_test',
// MySQL 监听的端口号
port: 3360
})
// 定义需要插入表中的数据
data = {id: 2, username: 'YJH', password: 'RedHeart'}
// 尝试将数据插入表中
db.query('insert into users values(?, ?, ?)', [data.id, data.username, data.password], (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else{
console.log(result);
}
})
执行结果:
在执行上述代码后,终端将输出如下内容:
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 2,
serverStatus: 2,
warningCount: 0,
message: '',
protocol41: true,
changedRows: 0
}
注:
在执行插入操作后,mysql 模块将返回一个对象。我们可以通过这个对象中的 affectedRows 来判断插入操作是否成功。affectedRows 表示该操作所影响的行数。
所以我们可以都上述代码做一些适当的修改:
db.query('insert into users values(?, ?, ?)', [data.id, data.username, data.password], (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else if(result.affectedRows === 1){
console.log('{ Win }');
}
})
另一种姿态
你可以通过向提交给 db.query() 函数的 SQL 查询语句中添加关键字 set (不区分大小写)及占位符 ? 来简化语句。
const mysql = require('mysql');
// 建立 NodeJS 与 MySQL 的连接
const db = mysql.createPool({
// MySQL 所在的计算机的 IP 地址
host: '127.0.0.1',
// 用户名称
user: 'root',
// 登录密码
password: '123456',
// 需要使用的数据库
database: 'db_test',
// MySQL 监听的端口号
port: 3360
})
data = {id: 3, username: 'CYH', password: 'RedHeart'}
db.query('insert into users sEt ?', data, (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else if(result.affectedRows === 1){
console.log('{ Win }');
}
})
执行结果:
在执行上述代码后,终端将输出如下内容:
{ Win }
注:
使用上述方式对数据库进行操作需要注意一点,即提交给 db.query() 函数的第二个参数需要为一个对象且该对象中的属性的个数与表中字段的个数一样且属性名与表中的字段名一一对应。
错误示范:
db.query('insert into users sEt ?', [4, 'XJL', 'RedHeart'], (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else if(result.affectedRows === 1){
console.log('{ Win }');
}
})
上述示例中,我们传递的并不是一个对象而是一个数组。数组中的元素的个数虽能够与字段一一对应,但由于不是对象,mysql 模块将抛出错误。执行该示例代码,你将得到如下内容:
{ Lose }
ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘4’ at line 1
修改
const mysql = require('mysql');
// 建立 NodeJS 与 MySQL 的连接
const db = mysql.createPool({
// MySQL 所在的计算机的 IP 地址
host: '127.0.0.1',
// 用户名称
user: 'root',
// 登录密码
password: '123456',
// 需要使用的数据库
database: 'db_test',
// MySQL 监听的端口号
port: 3360
})
db.query('update users set password = ? where id = ?', ['Other', 3], (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else if(result.affectedRows === 1){
console.log('{ Win }');
console.log(result)
}
})
执行结果:
{ Win }
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 0,
message: '(Rows matched: 1 Changed: 1 Warnings: 0',
protocol41: true,
changedRows: 1
}
可以看到,使用 mysql 模块对数据库进行修改操作,返回的结果仍是一个对象,我们依旧可以通过 affectedRows 来判断修改操作是否成功。
另一种姿态
与插入一样,我们可以使用 set 关键字来简化语句,但同样的,我们需要向 db.query() 函数传递一个对象作为第二个实参,且该对象中的属性需要为被修改的字段,而属性值则需要为字段修改后的结果值。
const mysql = require('mysql');
// 建立 NodeJS 与 MySQL 的连接
const db = mysql.createPool({
// MySQL 所在的计算机的 IP 地址
host: '127.0.0.1',
// 用户名称
user: 'root',
// 登录密码
password: '123456',
// 需要使用的数据库
database: 'db_test',
// MySQL 监听的端口号
port: 3360
})
data = {password: 'Other'}
db.query('update users set ? where id = ?', [data, 3], (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else if(result.affectedRows === 1){
console.log('{ Win }');
console.log(result)
}
})
执行结果:
{ Win }
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 0,
message: '(Rows matched: 1 Changed: 1 Warnings: 0',
protocol41: true,
changedRows: 1
}
注:
在这里我们向 db.query() 的第二个参数传递了一个数组,这似乎违背了我们前面的推断,使用这种方式对表进行插入或是修改操作时,第二个参数也可以是一个数组。但这个数组中的元素需要有一个元素为对象,且这个对象在数组中的排位与 set 关键字右边第一个占位符在该语句中所有占位符的排位相对应。
删除
const mysql = require('mysql');
// 建立 NodeJS 与 MySQL 的连接
const db = mysql.createPool({
// MySQL 所在的计算机的 IP 地址
host: '127.0.0.1',
// 用户名称
user: 'root',
// 登录密码
password: '123456',
// 需要使用的数据库
database: 'db_test',
// MySQL 监听的端口号
port: 3360
})
db.query('delete from users where id = ?', 3, (err, result) => {
if(err){
console.log('{ Lose }');
// 打印在查询过程中遇到的错误的信息
console.log(err.message);
}else if(result.affectedRows === 1){
console.log('{ Win }');
console.log(result)
}
})
执行结果:
{ Win }
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 0,
message: '',
protocol41: true,
changedRows: 0
}
标记删除
使用 DELETE 语句,会把真正的把数据从表中删除掉。为了保险起见,推荐使用标记删除的形式,来模拟删除的动作。所谓的标记删除,就是在表中设置类似于 status 这样的状态字段,来标记当前这条数据是否被删除。
当用户执行了删除的动作时,我们并没有执行 DELETE 语句把数据删除掉,而是执行了 UPDATE 语句,将这条数据对应的 status 字段标记为删除即可