我感觉使用任何一门语言作为后台开发都应该了解他的 ORM(Object Relational Mapping) 框架,这是对开发效率的负责,省掉写 Sql 的时间,把它留给更有意义的地方。


本篇以 Mysql 数据库为例,带大家了解 Node 中最多人用的 ORM 框架 Sequelize

下载

npm

1
2
$ npm install sequelize --save
$ npm install mysql2 --save

yarn

1
2
$ yarn add sequelize
$ yarn add mysql2

创建连接

使用配置

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

使用连接URI

1
const sequelize = new Sequelize('postgres://user:[email protected]:5432/dbname');

Sequelize 更多用法见 API

创建 Model

首先使用执行下面的语句创建一个测试表 test

1
2
3
4
5
6
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL DEFAULT '',
`create_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='test';

接下来创建 Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Test = sequelize.define('test', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: {
type: Sequelize.STRING
},
create_ts: {
type: Sequelize.DATE
}
},{
timestamps: false,
freezeTableName: true,
});

更多 dataTypes 以字段检查 validations
define 方法的前两个参数比较好理解,第一个是表名,第二个定义了表中字段的映射关系,第三个比较有意思,待会儿我们在聊它的作用。

使用 Model

首先来创建一条数据,需要用到 build(), save() 两个方法

1
2
3
Test.build({name: "wxnacy"}).save()
.then(() => {sequelize.close()})
.catch(() => {sequelize.close()})

save() 方法回来一个 Premise ,并且必须在结束时加上 sequelize.close() 方法关闭连接,目前我还没找到自动关闭的地方,如果你知道,一定要告诉我。
查询

1
2
3
4
5
6
Test.findOne({where: {name: 'wxnacy'}}).then(test => {
console.log(test.id, test.name) // 1 wxnacy
sequelize.close()
}).catch(e => {
sequelize.close()
})

这种查询方式很简洁,写起来很爽,跟 Rails 很像,这时我们可以来说说前边创建 Model 时提到的 timestamps, freezeTableName 两个参数的作用,你去掉这两个参数在执行一次,它会报错,日志中给出的 Sql 语句是这样的

1
SELECT `id`, `name`, `create_ts`, `createdAt`, `updatedAt` FROM `tests` AS `test` WHERE `test`.`name` = 'wxnacy' LIMIT 1;

两个奇怪的点,凭空多出了 createAt, updateAt 两个字段,原表名默认来 tests,Sequelize 很“贴心”,直接将表名加了个复数,也把我们表中最常用的两个字段也默认加了进去,BUT,表名我就不想加复数,我的创建时间修改时间也不打算叫这个名字,所以这里需要一些额外的控制

  • freezeTableName 是否将表名默认使用 define() 的第一个参数,默认 false
  • tableName 自定义表名
  • timestamps 控制是否添加(createAt,updateAt)两个字段,默认 true
  • createdAt 如果 timestamps 等于 true,可以设置这个参数,false 为不添加,或者可以为它设置别名,比如 create_ts
  • updatedAt 同上
  • deletedAt 同上
    我知道你想说什么,如果每个 Model 都这样设置那是要累死的,所以他必须要有一个可以设置全局的地方,比如
    1
    2
    3
    4
    5
    6
    const sequelize = new Sequelize(db, user, pw, {
    define: {
    timestamps: false,
    freezeTableName: true,
    }
    })

更多的 define 配置请见文档
好了,我们来写一个完整的例子吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
});
const Test = sequelize.define('test', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: {
type: Sequelize.STRING,
allowNull: true,
defaultValue: ''
},
create_ts: {
type: Sequelize.DATE
}
});
Test.build({name: "wxnacy"}).save()
.then(() => {
return Test.findById(1)
}).then(item => {
console.log(item.id, item.name); // 1 wxnacy
sequelize.close()
}).catch(e => {
console.log(e);
sequelize.close()
})

常用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Test.create({name: "wxnacy"})
Test.build({name: "wxnacy"}).save()
Test.findById(1).then(test => {console.log(test.toJSON());})
Test.findOne({name: "wxnacy"})
Test.findAll({where: {id: [1, 2, 3]}})
Test.findAll({
where: {
id: [1, 2, 3],
name: "wxnacy",
name: {
[Op.like]: 'wxn'
},
},
order: [
['id'],
['create_ts', 'DESC']
],
limit: 10
})
Test.all()
Test.findById(1).then(item => {
item.name = 'winn'
item.save()
})
Test.findOrCreate({where: {name: "wxnacy"}, defaults: {}})
.spread((blog, created) => {
console.log(blog.get({plain: true}));
console.log(created);
})

Model 更多使用方法见文档

03-16 20:48