首页 > 技术文章 > nodejs——连接mysql数据库

shige720 2020-07-26 19:53 原文

  完成url处理以及假数据返回后,开始需要考虑如何连接数据库了。这里我们从需要的工具开始,一步步实现nodejs和数据库的连接,用真实的数据来进行操作。

 

基础配置

MySQL安装

  首先安装MySQL,可以在www.mysql.com首页最下方downloads区域点击MySQL Community Server,弹出的页面中选择下面的那个下载。

  

  下载完成进入安装,一路确定即可。过程中需要输入root(管理员)的密码,记得务必记住!

  安装过程中需要注意的是保持开机时启动MySQL,不然像我就碰到过,开机后试图手动启动,但怎么也启动不了MySQL的服务……

 

Navicat安装

  MySQL安装完成后,需要安装一个可视化的MySQL管理工具。这里选择Navicat。这部分我在网上找了一个破解版的下载安装方法,详情点击传送门

  安装完成后进入主界面,创建一个新连接。创建之前可以点左下角的测试看看是否能连接,有时候会报1251的连接错误,处理方案同样见传送门

  连接成功后,新建数据库。在新建的数据库中新建表,如下图所示。

  

  其中要注意的有:不是NULL(非空)主键(PK)以及下方的默认自动递增(AI)几个选项。对于id这种字段的唯一标识符,需要选择为主键,同时设置自动递增。

 

SQL基本语句

  完成了以上操作,表的基本框架也都搭建好了,接下来的就是对创建好的表进行程序开发中老生常谈的CRUD操作了。下面简单列举一下四种操作的基本语法。

 

添加数据Create

  INSERT INTO 表名(字段1, 字段2, 字段3……) values (字段1赋值, 字段2赋值, 字段3赋值……);

  这里注意字段和后面的赋值的顺序必须相同。此外,如果字段用了SQL语句里的保留字(例如password),需要在字段外加上反引号(``,tab键上方的)进行标记。

 

读取数据Read

  SELECT 字段1, 字段2, …… FROM 表名 WHERE 字段名=值 ORDER BY 字段名 DESC;

  查表格数据中可添加的东西比较多。如果是获取表中所有的字段,直接SELECT * FROM 表名即可,但这样比较消耗性能,实际开发过程中不推荐使用。

  WHERE非必填,这里是用来筛选过滤的。这里的匹配可以用精确匹配/模糊查找两种方法。精确匹配如上方所示,即必须和值完全一样。而模糊查找用LIKE标识符,并且带上%符号作为通配符。除此之外,关于WHERE语句,在开发过程中也有使用的小技巧,后续会有介绍。

  ORDER BY非必填,用来给查到到的数据进行排序DESC表示降序ASC为升序(默认值)

 

更新数据UPDATE

  UPDATE 表名 SET 待修改字段名=修改值 WHERE 字段名=值

  没什么太多好说的,这里的SET后设置了待修改的字段名和目标值,WHERE后是修改目标的要求。

 

删除数据DELETE

  DELETE FROM 表名 WHERE 字段名=值

  实际开发过程中,一般不用这种硬删除(即直接删除,数据后续无法恢复),而多使用UPDATE方法,通过修改某个字段的值,实现删除的效果。

 

 

好了,以上准备工作结束,终于可以开始用nodejs来操作数据库了!

 

NodeJS连接数据库

基本配置

  老惯例,先安装依赖包,npm i mysql。安装完成后,用require语句引入。

  mysql使用前需要先建立一个连接,用其自带的createConnection方法,内部需要传入一些配置,包括主机数据库的用户名和密码端口数据库等,如下图。

  

  这里最上方的env即所用的环境,可以在package.json里scripts的环境配置中找到。因为我使用的是nodemon监听文件的变化,所以env的值就是NODE_ENV后的那个值。比如npm run dev,便是dev环境中,NODE_ENV=后的那个值。

  

  创建完连接后,先用connect方法启动连接,再传入我们需要执行的sql语句,并获取sql语句执行后的反馈。流程如下图:

  

  这里由于需要取得返回的值,我们考虑将这个传入sql语句并获取反馈信息的方法用promise包裹。至此,整个sql操作的封装如下(为了便于操作,提供该段代码):

 1 // 统一执行sql的函数
 2 function exec(sql) {
 3   // 用promise包裹一下
 4   const promise = new Promise((resolve, reject) => {
 5     con.query(sql, (err, result) => {
 6       if (err) {
 7         reject(err);
 8         return;
 9       }
10       resolve(result);
11     });
12   });
13   return promise;
14 }
sql函数封装

 

数据处理

  我们进入到项目中数据处理的结构controller中,将之前的假数据清除,引入执行sql的方法。这里我也列举一下增删改查四种接口的修改方法。

 

读取数据Read

  列表的获取便是读取数据的一个很好的例子。这个接口设计的时候要求我们在querystring里传入author、keyword两个查询条件,并从数据库中找到符合条件的数据。思路很清晰,代码如下:

 1 const getList = (author, keyword) => {
 2   // 这里SQL语句的1=1只是为了占位
 3   let sql = `select * from blogs where 1=1 `;
 4   if (author) {
 5     sql += `and author='${author}' `;
 6   }
 7   if (keyword) {
 8     sql += `and title like '%${keyword}%' `;
 9   }
10   sql += `order by createTime desc;`
11 
12   return exec(sql);
13 }
获取数据-列表查询

  值得注意的是这里最开始的sql语句中加入了WHERE 1=1这个恒等条件。因为考虑到两个查询条件都是非必须的,有可能传也可能不传,但是也不能光写一个WHERE在那里,这样后面的语句会变成语法错误了。所以特意添加一个用来占位的1=1这么一个恒等式。

  另外一个例子就是详情的获取。这里我们需要传入博客的id来获取博客的详细信息,代码如下:

const getDetail = id => {
  const sql = `select * from blogs where id='${id}';`;
  return exec(sql).then(rows => {
    return rows[0];
  });
}
获取数据-详情查询

  这里也有一点需要注意。由于我们查询得到的结果都是一个数组,而实际上我们需要的详情应该是一个对象(因为根据固定的id只可能查到一个值嘛),所以对返回的数据,我们需要改变一下数据格式

 

添加数据Create

  新建博客用到了添加数据的sql语句。根据接口设计,我们需要传入authortitlecontentcreateTime这几个参数,而博客的id我们在数据库中已经设置成主键,数据库会自动生成。代码如下:

 1 const newBlog = (blogData = {}) => {
 2   // blogData是一个博客对象,包含了title、content、author等属性
 3   const { title, content, author } = blogData;
 4   const createTime = Date.now();
 5 
 6   const sql = `insert into blogs (title, content, author, createTime)
 7   values ('${title}', '${content}', '${author}', ${createTime});`
 8   
 9   return exec(sql).then(insertData => {
10     return {
11       id: insertData.insertId
12     }
13   });
14 }
添加数据-新建博客

  这里需要注意的是我们执行sql语句后,数据库的返回信息。我们可以试着在控制台输出一下,结果如下:

  

  可以看到这个返回结果中包含了诸多信息,包括sql语句影响的行数(affectedRows)插入的数据的id(insertId)改变的数据行数(changedRows)等等。由于这里我们是插入操作,需要关注的主要是插入之后的数据id,即insertId

 

修改数据Update

  修改博客详情无疑执行的是sql中的修改语句。这里需要从querystring中传入修改详情博客的id,同时用post发送修改的详细数据。代码如下:

 1 const updateBlog = (id, blogData = {}) => {
 2   // id为需要更新博客的id
 3   // blogData是一个博客对象,包含title、content等属性
 4   const { title, content } = blogData;
 5 
 6   const sql = `update blogs set title='${title}', content='${content}' where id=${id};`;
 7   return exec(sql).then(updateData => {
 8     if (updateData.affectedRows > 0) {
 9       return true;
10     }
11     return false;
12   });
13 }
修改数据-修改博客详情

  同样需要注意的是这里执行完sql语句后的返回信息。信息的内容和上面添加数据的基本一致。message里会带上匹配数据的行数修改成功的数据行数警告数量等信息,如下图:

  

  如果修改失败(例如传入的博客id不存在),返回的消息体如下图:

  

  可以看到影响行数为0,message也带了具体的错误信息(匹配到的行数为0,变化的数据量为0,无警告)。

 

删除数据Delete

  删除博客用到了sql里的删除语句(虽然上面说到,实际开发中尽可能不用删除语句)。代码如下:

 1 const delBlog = (id, author) => {
 2   // id为要删除博客的id
 3   const sql = `delete from blogs where id=${id} and author='${author}';`;
 4   return exec(sql).then(delData => {
 5     if (delData.affectedRows > 0) {
 6       return true;
 7     }
 8     return false;
 9   });
10 }
删除数据-删除博客详情

  执行完sql语句后的返回信息同上方一致,这里需要关注的是affectedRows。此外需要注意一个问题,为了安全,删除博客的时候,需要传入博客id以及作者author两个信息,以防止随意删除他人信息的事情出现。

 

路由方法修改

  数据处理的方法上面以及ok了,接下来就是在路由的处理方法上需要改变一些地方。

  首先,明确目前数据处理完返回的是一个Promise实例,所以要用then接受实例返回的数据。这里我以获取博客列表为例,发一下改进之后的代码:

 1 if (method === 'GET' && req.path === '/api/blog/list') {
 2     const author = req.query.author || '';
 3     const keyword = req.query.keyword || '';
 4     
 5     const result = getList(author, keyword);
 6     // 注意这里返回的是一个promise,所以需要在外侧也return一下
 7     return result.then(listData => {
 8       return new SuccessModel(listData);
 9     });
10   }
博客列表路由处理

  之后,在最顶层的app.js里处理一下路由获取的返回值,代码如下:

1 const blogResult = handleBlogRouter(req, res);
2     if (blogResult) {
3       blogResult.then(blogData => {
4         res.end(JSON.stringify(blogData));
5       })
6       return;
7     }
顶层路由处理

 

推荐阅读