nodejs——连接mysql数据库
完成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 }
数据处理
我们进入到项目中数据处理的结构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语句。根据接口设计,我们需要传入author、title、content、createTime这几个参数,而博客的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 }