简介
MySQL是一个非常流行的 关系型数据库管理系统(RDBMS)- 在很长一段时间里,它一直是任何后端工程师工具包中的一个主要部分,就在PostgreSQL旁边。
随着后端JavaScript的出现,通过Node.js的流行和适用性的显著上升,MySQL通常被用作用Node构建的项目和应用程序的数据库。
在本指南中,我们将看看如何将MySQL与Node.js应用程序集成。我们将通过设置过程,以编程方式执行CRUD操作,并通过转义输入和设置超时来查看基本的查询安全性。
**注意:**在整个指南中,我们将把npm MySQL驱动称为mysql ,把MySQL数据库本身称为MySQL。
MySQL的设置
要使用MySQL,你需要下载 MySQL数据库并在你的本地机器上托管一个小型服务器。社区版本是完全免费的!
你也可以选择安装一个支持MySQL数据库的开源Web服务器*(Xampp*、Lampp)--它们与Node的MySQL驱动也能很好地工作。
安装程序很简单,一旦你设置了一个服务实例,你就可以创建一个项目来连接它了
Node项目的MySQL驱动
假设服务器正在运行,你可以通过一个Node应用程序,使用驱动程序与它进行程序性通信。为了使一个应用程序具有这种能力,你需要安装一个MySQL驱动。驱动程序可在npm ,即mysql!
首先,让我们创建一个新的目录来托管我们的应用程序,并初始化一个新的Node项目,并使用默认设置。
$ mkdir mysql-app
$ cd mysql-app
$ npm init -y
这将生成一个新的package.json ,其中包含我们项目的基本元数据。一旦完成,我们就可以通过npm ,安装node MySQL驱动包。
$ npm install mysql
我们可以通过检查我们的package.json 文件来验证安装是否成功,在那里我们应该发现在dependencies 下添加了一个新的条目mysql 。
...
"dependencies": {
"mysql": "^2.18.1"
}
注意: ^2.18.1 代表版本号,这将取决于你所安装的MySQL的版本。
建立一个连接
可以通过mysql 实例的createConnection() 函数来创建一个连接对象。然后,这个对象可以用来在服务器和客户端/驱动程序之间建立一个连接!它接受三个参数:1.它接受三个参数。
- 主机- 你的数据库的
host。默认值是'localhost'。 - 用户- 授权数据库管理员的用户名。默认值是
'root'。 - 密码- 授权数据库管理员的密码。默认值是一个空字符串(没有密码)。
- 数据库- 一个可选的参数,定义你要修改的数据库名称。
考虑到这些,让我们打开默认的index.js 文件 - 我们可以用它连接到MySQL服务器。
var mysql = require("mysql");
var connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "",
});
connection.connect((err) => {
if (err) {
console.log("Error occurred", err);
} else {
console.log("Connected to MySQL Server");
}
});
一旦配置了一个连接,你就可以简单地将connect() 到服务器上!connect() 函数使用一个回调,允许你检查连接是否成功。
通过JavaScript编写查询
几乎所有你需要在MySQL数据库中执行的操作都是通过查询完成的,自然是用SQL编写的。connection 实例的query() 方法接受一个字符串,代表我们要发送给数据库的查询。
一个查询可能会失败或返回一些结果,所以我们会有另一个回调,根据操作的结果,有一个err 或result 。
创建一个数据库
让我们开始通过Node的MySQL驱动来创建一个数据库。你可以通过CLI(如果你已经安装了它)或通过任何形式的GUI(官方或第三方)在服务器上创建一个数据库。
另外,你也可以使用我们创建的连接来发送一个查询,为你创建一个数据库!让我们创建一个新的students_records 数据库。在项目的根目录中,在我们之前使用过的同一个index.js ,让我们在连接到数据库之后,如果连接成功的话,添加一个query() 的调用。
var mysql = require("mysql");
// Create connection configuration
var connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "",
});
// Connect to the server
connection.connect((err) => {
if (err) {
// Return error if present
console.log("Error occurred", err);
} else {
// Create database
console.log("Connected to MySQL Server");
const query = "CREATE DATABASE students_records";
connection.query(query, function (err, result) {
if (err) {
err;
}
console.log("New database created");
});
}
});
在验证了我们的连接是否成功之后,我们执行了一个带有查询字符串的查询--'CREATE DATABASE students_records' ,如果还没有存在,就创建一个新的数据库。现在,让我们运行该项目。
$ node index.js
其结果是
Connected to MySQL Server
New database created
棒极了!现在,一个students_records 数据库已经存在,我们可以取消数据库创建查询,直接连接到配置中的students_records 。
var connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "",
database: "students_records",
});
创建记录
就像其他关系型数据库一样,MySQL的数据记录被存储在 表与 列和 行.一个表可以由任意数量的列和行组成,但它们必须是一致的。数据库中的列代表一个对象的特征/字段,而每一行代表一个条目。
每一行必须有每一列的值,而且只有这些列,即使它只是
NULL。对于任何关系型数据库,模式必须保持一致。
这意味着我们需要预先创建一个表,并定义其模式(每一个列和类型),以便向我们的数据库添加新记录。
要在MySQL中创建一个新表,我们使用CREATE TABLE 语句。假设我们想在我们的students_records 数据库中创建一个students 表。同样,一旦连接,我们可以直接执行相关的查询。
var mysql = require("mysql");
var connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "",
database: "students_records",
});
connection.connect((err) => {
if (err) {
console.log("Error occurred", err);
} else {
console.log("Connected to database");
var sql = "CREATE TABLE students (id INT, name VARCHAR(255), course VARCHAR(255))";
connection.query(sql, function (err, result) {
if (err) {
console.log(err);
}
console.log("New table created");
});
}
});
这就创建了一个新的表,可以容纳带有name 和course 字段的记录!每个条目都会有一个name 和course ,即使其中有些是空的。此外,我们已经将它们的类型设置为VARCHAR ,最大长度为255个字符。
在这里,你可以另外设置其他的约束条件,比如某些字段是强制性的(NOT NULL ),或者PRIMARY KEY/FOREIGN KEY 。
CREATE TABLE students (
id INT NOT NULL,
name VARCHAR(255) NOT NULL,
course VARCHAR(255) NOT NULL,
PRIMARY KEY (ID)
)
在这里,没有一个字段可以被设置为NULL ,而且每个字段都有一些相关的数据类型。
**注意:**在SQL中,你是否将关键字大写并不重要。create table 在功能上等同于CREATE TABLE ,就像varchar 相对于VARCHAR 。将命令、关键字和约束条件大写是常见的惯例,不过,这并不是一个要求。
创建记录
INSERT INTO 查询,然后是表名和映射到它的列的VALUES ,用于用SQL插入/创建记录!例如,让我们在我们的students 表中添加一个John Doe 。
var sql = "INSERT INTO students (id, name, course) VALUES (1, 'John Doe', 'Computer Science')";
不出所料,这条语句将在students 表中插入一条记录(行),并为name 和course 。像往常一样,该查询可以通过connection.query() 来执行。
var sql = "INSERT INTO students (id, name, course) VALUES (1, 'John Doe', 'Computer Science')";
// Just execute
connection.query(sql);
// Or execute with callback to handle results/errors
connection.query(sql, function (err, result) {
// ...
});
读取记录
我们不只是想存储数据--我们还想能够读取它。而且,如下图所示,我们可以通过SELECT 查询从students 表中检索数据。
我们可以选择特定的记录或使用通配符(* )来选择所有可用的记录。
var sql = "SELECT * FROM students";
connection.query(sql, function (err, result) {
console.log(result);
});
运行这段代码将返回我们表中的所有数据。
[
RowDataPacket { id: 1, name: 'John Doe', course: 'Computer Science' },
RowDataPacket { id: 2, name: 'Jane Doe', course: 'Art' }
]
你还可以使用BETWEEN 语句来创建一个匹配行的范围。
SELECT * FROM students WHERE id BETWEEN 1 AND 5
这将返回所有的学生,其ID在1到5之间。
更新记录
我们可以使用UPDATE 语句来更新数据库表,以及它们的内容。例如,你可以使用几个语句来识别某条记录并更新该记录。一般来说,一个id ,应该是一个用于识别行的PRIMARY KEY ,同时也是一个唯一的值,所以id ,通常是用来搜索记录的列。
假设我们想把学生,John Doe's,课程从Computer Science 更新到Art ,和他的表弟一起。约翰的id 是1 。
var sql = "UPDATE students SET course = 'Art' WHERE id = 1";
connection.query(sql, function (err, result) {
console.log(result);
});
像这样的查询结果通常包括关于查询如何影响表的细节,这就是你如何验证结果是否按照你的意图工作。
{
fieldCount: 0,
affectedRows: 1,
insertId: 1,
serverStatus: 34,
warningCount: 0,
message: '(Rows matched: 1 Changed: 1 Warnings: 0',
protocol41: true,
changedRows: 1
}
删除记录
最后,要删除记录--我们使用DELETE 语句,并指定哪些记录要被删除。
var sql = "DELETE FROM students WHERE id = 1";
connection.query(sql, function (err, result) {
console.log(result);
});
**注意:**在使用DELETE 语句时,你应该小心,并仔细指定WHERE 子句。此外,如果你不指定WHERE 子句,整个表的内容将被清除。
转移查询值
当软件工程师允许在SQL调用中使用用户输入时,一个常见的早期漏洞被发现。例如,你可以有一个带输入字段的搜索栏。用户可以插入一本书的名字,这个输入将被用在一个查询中,例如。
SELECT * FROM books WHERE name = user_input
user_input ,自然会被替换成一个字符串,如'Foundation' 。然而,这为巨大的漏洞打开了一扇门,这些漏洞就像逃避你建立的查询并运行他们自己的查询一样简单。
例如,如果系统被构建为天真地接受输入并将其插入到查询中,那么用户只要输入a; DROP TABLE books ,就可以利用你的查询。
SELECT * FROM books WHERE name = a; DROP TABLE books
分号将结束前一个语句,而排在后面的一个语句将运行,从数据库中删除整个表,并删除所有书籍。这是一个简单的SQL注入的例子,攻击可以比这复杂得多。此外,并不是所有的攻击都是破坏性的--攻击者也可能只是读取敏感数据,这是一种无声的攻击,通常会被掩盖。
鉴于这些攻击可能变得如此普遍--大多数软件包都有基本的内置安全。为Node构建的mysql 驱动程序提供了帮助避免SQL注入的功能和措施。
最常见的方法是 转义值并将它们作为命名参数来代替。
const input = 'Foundation';
connection.query('SELECT * FROM books WHERE name = ?', [input], function (err, results, fields) {
// ...
});
通过使用? 转义字符--你转义了输入,把它变成了一个字面字符串,无法被评估。如果有人在输入字段中输入一个命令,导致这个参数--它不会被评估为一个命令。它将被视为一个字面字符串,并将发送一个查询,以找到一个名称与该命令相匹配的书。
该方法接受一个参数数组,这些参数将依次输入到查询中。如果查询有2个注释参数,列表中的第一个项目将被映射到第一个参数,列表中的第二个项目将被映射到第二个参数。
另外,你也可以通过JavaScript的字符串格式化明确地注入这些值,并使用escape() 方法来转义可能的命令,不过--这种方法的可读性一般较差。
const input = mysql.escape('Foundation');
connection.query(`SELECT * FROM books WHERE name = ${input}`, function (err, results, fields) {
// ...
});
设置超时时间
每个与连接相关的操作都会在默认的超时时间后终止,我们可以通过设置我们自己喜欢的超时时间来解决这个问题。这可以通过向.query() 函数传递一个对象来实现,我们的查询和超时时间(以毫秒为单位)是该对象的字段。
connection.query({sql: 'SELECT * FROM students', timeout: 50000}, function (err, results) {
// ...
});
这段代码将运行一个SELECT 查询,如果没有返回结果,将在50秒后终止。
总结
MySQL是一个常用的关系型数据库。在本指南中,我们看了如何为Node.js项目安装MySQL服务器的驱动,如何设置与服务器的连接并执行CRUD操作。
最后,我们指出了转义用户输入的重要性,并看了一下自定义超时时间。