使用Sequelize

访问MySQL

当我们安装好MySQL后,Node.js程序如何访问MySQL数据库呢?

访问MySQL数据库只有一种方法,就是通过网络发送SQL命令,然后,MySQL服务器执行后返回结果。

我们可以在命令行窗口输入mysql -u root -p,然后输入root口令后,就连接到了MySQL服务器。因为没有指定--host参数,所以我们连接到的是localhost,也就是本机的MySQL服务器。

在命令行窗口下,我们可以输入命令,操作MySQL服务器:

  1. mysql> show databases;
  2. +--+
  3. | Database |
  4. +--+
  5. | information_schema |
  6. | mysql |
  7. | performance_schema |
  8. | test |
  9. +--+
  10. 4 rows in set (0.05 sec)

输入exit退出MySQL命令行模式。

对于Node.js程序,访问MySQL也是通过网络发送SQL命令给MySQL服务器。这个访问MySQL服务器的软件包通常称为MySQL驱动程序。不同的编程语言需要实现自己的驱动,MySQL官方提供了Java、.Net、Python、Node.js、C++和C的驱动程序,官方的Node.js驱动目前仅支持5.7以上版本,而我们上面使用的命令行程序实际上用的就是C驱动。

目前使用最广泛的MySQL Node.js驱动程序是开源的mysql,可以直接使用npm安装。

ORM

如果直接使用mysql包提供的接口,我们编写的代码就比较底层,例如,查询代码:

  1. connection.query('SELECT * FROM users WHERE id = ?', ['123'], function(err, rows) {
  2. if (err) {
  3. // error
  4. } else {
  5. for (let row in rows) {
  6. processRow(row);
  7. }
  8. }
  9. });

考虑到数据库表是一个二维表,包含多行多列,例如一个pets的表:

  1. mysql> select * from pets;
  2. +-+--++
  3. | id | name | birth |
  4. +-+--++
  5. | 1 | Gaffey | 2007-07-07 |
  6. | 2 | Odie | 2008-08-08 |
  7. +-+--++
  8. 2 rows in set (0.00 sec)

每一行可以用一个JavaScript对象表示,例如第一行:

  1. {
  2. "id": 1,
  3. "name": "Gaffey",
  4. "birth": "2007-07-07"
  5. }

这就是传说中的ORM技术:Object-Relational Mapping,把关系数据库的表结构映射到对象上。是不是很简单?

但是由谁来做这个转换呢?所以ORM框架应运而生。

我们选择Node的ORM框架Sequelize来操作数据库。这样,我们读写的都是JavaScript对象,Sequelize帮我们把对象变成数据库中的行。

用Sequelize查询pets表,代码像这样:

  1. Pet.findAll()
  2. .then(function (pets) {
  3. for (let pet in pets) {
  4. console.log(`${pet.id}: ${pet.name}`);
  5. }
  6. }).catch(function (err) {
  7. // error
  8. });

因为Sequelize返回的对象是Promise,所以我们可以用then()catch()分别异步响应成功和失败。

但是用then()catch()仍然比较麻烦。有没有更简单的方法呢?

可以用ES7的await来调用任何一个Promise对象,这样我们写出来的代码就变成了:

  1. var pets = await Pet.findAll();

真的就是这么简单!

await只有一个限制,就是必须在async函数中调用。上面的代码直接运行还差一点,我们可以改成:

  1. (async () => {
  2. var pets = await Pet.findAll();
  3. })();

考虑到koa的处理函数都是async函数,所以我们实际上将来在koa的async函数中直接写await访问数据库就可以了!

这也是为什么我们选择Sequelize的原因:只要API返回Promise,就可以用await调用,写代码就非常简单!

实战

在使用Sequlize操作数据库之前,我们先在MySQL中创建一个表来测试。我们可以在test数据库中创建一个pets表。test数据库是MySQL安装后自动创建的用于测试的数据库。在MySQL命令行执行下列命令:

  1. grant all privileges on test.* to 'www'@'%' identified by 'www';
  2. use test;
  3. create table pets (
  4. id varchar(50) not null,
  5. name varchar(100) not null,
  6. gender bool not null,
  7. birth varchar(10) not null,
  8. createdAt bigint not null,
  9. updatedAt bigint not null,
  10. version bigint not null,
  11. primary key (id)
  12. ) engine=innodb;

第一条grant命令是创建MySQL的用户名和口令,均为www,并赋予操作test数据库的所有权限。

第二条use命令把当前数据库切换为test

第三条命令创建了pets表。

然后,我们根据前面的工程结构创建hello-sequelize工程,结构如下:

  1. hello-sequelize/
  2. |
  3. +- .vscode/
  4. | |
  5. | +- launch.json <-- VSCode 配置文件
  6. |
  7. +- init.txt <-- 初始化SQL命令
  8. |
  9. +- config.js <-- MySQL配置文件
  10. |
  11. +- app.js <-- 使用koajs
  12. |
  13. +- package.json <-- 项目描述文件
  14. |
  15. +- node_modules/ <-- npm安装的所有依赖包

然后,添加如下依赖包:

  1. "sequelize": "3.24.1",
  2. "mysql": "2.11.1"

注意mysql是驱动,我们不直接使用,但是sequelize会用。

npm install安装。

config.js实际上是一个简单的配置文件:

  1. var config = {
  2. database: 'test', // 使用哪个数据库
  3. username: 'www', // 用户名
  4. password: 'www', // 口令
  5. host: 'localhost', // 主机名
  6. port: 3306 // 端口号,MySQL默认3306
  7. };
  8. module.exports = config;

下面,我们就可以在app.js中操作数据库了。使用Sequelize操作MySQL需要先做两件准备工作:

第一步,创建一个sequelize对象实例:

  1. const Sequelize = require('sequelize');
  2. const config = require('./config');
  3. var sequelize = new Sequelize(config.database, config.username, config.password, {
  4. host: config.host,
  5. dialect: 'mysql',
  6. pool: {
  7. max: 5,
  8. min: 0,
  9. idle: 30000
  10. }
  11. });

第二步,定义模型Pet,告诉Sequelize如何映射数据库表:

  1. var Pet = sequelize.define('pet', {
  2. id: {
  3. type: Sequelize.STRING(50),
  4. primaryKey: true
  5. },
  6. name: Sequelize.STRING(100),
  7. gender: Sequelize.BOOLEAN,
  8. birth: Sequelize.STRING(10),
  9. createdAt: Sequelize.BIGINT,
  10. updatedAt: Sequelize.BIGINT,
  11. version: Sequelize.BIGINT
  12. }, {
  13. timestamps: false
  14. });

sequelize.define()定义Model时,传入名称pet,默认的表名就是pets。第二个参数指定列名和数据类型,如果是主键,需要更详细地指定。第三个参数是额外的配置,我们传入{ timestamps: false }是为了关闭Sequelize的自动添加timestamp的功能。所有的ORM框架都有一种很不好的风气,总是自作聪明地加上所谓“自动化”的功能,但是会让人感到完全摸不着头脑。

接下来,我们就可以往数据库中塞一些数据了。我们可以用Promise的方式写:

  1. var now = Date.now();
  2. Pet.create({
  3. id: 'g-' + now,
  4. name: 'Gaffey',
  5. gender: false,
  6. birth: '2007-07-07',
  7. createdAt: now,
  8. updatedAt: now,
  9. version: 0
  10. }).then(function (p) {
  11. console.log('created.' + JSON.stringify(p));
  12. }).catch(function (err) {
  13. console.log('failed: ' + err);
  14. });

也可以用await写:

  1. (async () => {
  2. var dog = await Pet.create({
  3. id: 'd-' + now,
  4. name: 'Odie',
  5. gender: false,
  6. birth: '2008-08-08',
  7. createdAt: now,
  8. updatedAt: now,
  9. version: 0
  10. });
  11. console.log('created: ' + JSON.stringify(dog));
  12. })();

显然await代码更胜一筹。

查询数据时,用await写法如下:

  1. (async () => {
  2. var pets = await Pet.findAll({
  3. where: {
  4. name: 'Gaffey'
  5. }
  6. });
  7. console.log(`find ${pets.length} pets:`);
  8. for (let p of pets) {
  9. console.log(JSON.stringify(p));
  10. }
  11. })();

如果要更新数据,可以对查询到的实例调用save()方法:

  1. (async () => {
  2. var p = await queryFromSomewhere();
  3. p.gender = true;
  4. p.updatedAt = Date.now();
  5. p.version ++;
  6. await p.save();
  7. })();

如果要删除数据,可以对查询到的实例调用destroy()方法:

  1. (async () => {
  2. var p = await queryFromSomewhere();
  3. await p.destroy();
  4. })();

运行代码,可以看到Sequelize打印出的每一个SQL语句,便于我们查看:

  1. Executing (default): INSERT INTO `pets` (`id`,`name`,`gender`,`birth`,`createdAt`,`updatedAt`,`version`) VALUES ('g-1471961204219','Gaffey',false,'2007-07-07',1471961204219,1471961204219,0);

Model

我们把通过sequelize.define()返回的Pet称为Model,它表示一个数据模型。

我们把通过Pet.findAll()返回的一个或一组对象称为Model实例,每个实例都可以直接通过JSON.stringify序列化为JSON字符串。但是它们和普通JSON对象相比,多了一些由Sequelize添加的方法,比如save()destroy()。调用这些方法我们可以执行更新或者删除操作。

所以,使用Sequelize操作数据库的一般步骤就是:

首先,通过某个Model对象的findAll()方法获取实例;

如果要更新实例,先对实例属性赋新值,再调用save()方法;

如果要删除实例,直接调用destroy()方法。

注意findAll()方法可以接收whereorder这些参数,这和将要生成的SQL语句是对应的。

文档

Sequelize的API可以参考官方文档

参考源码

hello-sequelize

读后有收获可以支付宝请作者喝咖啡,读后有疑问请加微信群讨论:

使用Sequelize - 图1 使用Sequelize - 图2