Sequelize
前面的章節(jié)中,我們介紹了如何在框架中通過(guò) egg-mysql 插件來(lái)訪問(wèn)數(shù)據(jù)庫(kù)要出。而在一些較為復(fù)雜的應(yīng)用中器予,我們可能會(huì)需要一個(gè) ORM 框架來(lái)幫助我們管理數(shù)據(jù)層的代碼偿枕。而在 Node.js 社區(qū)中隙券,sequelize 是一個(gè)廣泛使用的 ORM 框架榜聂,它支持 MySQL、PostgreSQL陌凳、SQLite 和 MSSQL 等多個(gè)數(shù)據(jù)源剥懒。
本章節(jié)我們會(huì)通過(guò)開(kāi)發(fā)一個(gè)對(duì) MySQL 中 users
表的數(shù)據(jù)做 CURD 的例子來(lái)一步步介紹如何在 egg 項(xiàng)目中使用 sequelize。
初始化項(xiàng)目
通過(guò) npm
初始化一個(gè)項(xiàng)目:
$ mkdir sequelize-project && cd sequelize-project
$ npm init egg --type=simple
$ npm i
安裝并配置 egg-sequelize 插件(它會(huì)輔助我們將定義好的 Model 對(duì)象加載到 app 和 ctx 上)和 mysql2 模塊:
- 安裝
npm install --save egg-sequelize mysql2
- 在
config/plugin.js
中引入 egg-sequelize 插件
exports.sequelize = {
enable: true,
package: 'egg-sequelize',
};
- 在
config/config.default.js
中編寫(xiě) sequelize 配置
config.sequelize = {
dialect: 'mysql',
host: '127.0.0.1',
port: 3306,
database: 'egg-sequelize-doc-default',
};
完成上面的配置之后合敦,一個(gè)使用 sequelize 的項(xiàng)目就初始化完成了初橘。egg-sequelize 和 sequelize 還支持更多的配置項(xiàng),可以在他們的文檔中找到充岛。
初始化數(shù)據(jù)庫(kù)和 Migrations
sequelize 提供了 sequelize-cli 工具來(lái)實(shí)現(xiàn) Migrations保檐,我們也可以在 egg 項(xiàng)目中引入 sequelize-cli。
- 安裝 sequelize-cli
npm install --save-dev sequelize-cli
在 egg 項(xiàng)目中崔梗,我們希望將所有數(shù)據(jù)庫(kù) Migrations 相關(guān)的內(nèi)容都放在 database 目錄下夜只,所以我們?cè)陧?xiàng)目根目錄下新建一個(gè) .sequelizerc 配置文件:
'use strict';
const path = require('path');
module.exports = {
config: path.join(__dirname, 'database/config.json'),
'migrations-path': path.join(__dirname, 'database/migrations'),
'seeders-path': path.join(__dirname, 'database/seeders'),
'models-path': path.join(__dirname, 'app/model'),
};
- 初始化 Migrations 配置文件和目錄
npx sequelize init:config
npx sequelize init:migrations
執(zhí)行完后會(huì)生成 database/config.json 文件和 database/migrations 目錄,我們修改一下 database/config.json 中的內(nèi)容蒜魄,將其改成我們項(xiàng)目中使用的數(shù)據(jù)庫(kù)配置:
{
"development": {
"username": "root",
"password": null,
"database": "egg-sequelize-doc-default",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "egg-sequelize-doc-unittest",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
此時(shí) sequelize-cli 和相關(guān)的配置也都初始化好了扔亥,我們可以開(kāi)始編寫(xiě)項(xiàng)目的第一個(gè) Migration 文件來(lái)創(chuàng)建我們的一個(gè) users 表了。
npx sequelize migration:generate --name=init-users
執(zhí)行完后會(huì)在 database/migrations 目錄下生成一個(gè) migration 文件(${timestamp}-init-users.js)权悟,我們修改它來(lái)處理初始化 users 表:
'use strict';
module.exports = {
// 在執(zhí)行數(shù)據(jù)庫(kù)升級(jí)時(shí)調(diào)用的函數(shù)砸王,創(chuàng)建 users 表
up: async (queryInterface, Sequelize) => {
const { INTEGER, DATE, STRING } = Sequelize;
await queryInterface.createTable('users', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
});
},
// 在執(zhí)行數(shù)據(jù)庫(kù)降級(jí)時(shí)調(diào)用的函數(shù),刪除 users 表
down: async queryInterface => {
await queryInterface.dropTable('users');
},
};
- 執(zhí)行 migrate 進(jìn)行數(shù)據(jù)庫(kù)變更
# 升級(jí)數(shù)據(jù)庫(kù)
npx sequelize db:migrate
# 如果有問(wèn)題需要回滾峦阁,可以通過(guò) `db:migrate:undo` 回退一個(gè)變更
# npx sequelize db:migrate:undo
# 可以通過(guò) `db:migrate:undo:all` 回退到初始狀態(tài)
# npx sequelize db:migrate:undo:all
編寫(xiě)代碼
現(xiàn)在終于可以開(kāi)始編寫(xiě)代碼實(shí)現(xiàn)業(yè)務(wù)邏輯了谦铃,首先我們來(lái)在 app/model/ 目錄下編寫(xiě) user 這個(gè) Model:
'use strict';
module.exports = app => {
const { STRING, INTEGER, DATE } = app.Sequelize;
const User = app.model.define('user', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
});
return User;
};
這個(gè) Model 就可以在 Controller 和 Service 中通過(guò) app.model.User 或者 ctx.model.User 訪問(wèn)到了,例如我們編寫(xiě) app/controller/users.js:
// app/controller/users.js
const Controller = require('egg').Controller;
function toInt(str) {
if (typeof str === 'number') return str;
if (!str) return str;
return parseInt(str, 10) || 0;
}
class UserController extends Controller {
async index() {
const ctx = this.ctx;
const query = { limit: toInt(ctx.query.limit), offset: toInt(ctx.query.offset) };
ctx.body = await ctx.model.User.findAll(query);
}
async show() {
const ctx = this.ctx;
ctx.body = await ctx.model.User.findByPk(toInt(ctx.params.id));
}
async create() {
const ctx = this.ctx;
const { name, age } = ctx.request.body;
const user = await ctx.model.User.create({ name, age });
ctx.status = 201;
ctx.body = user;
}
async update() {
const ctx = this.ctx;
const id = toInt(ctx.params.id);
const user = await ctx.model.User.findByPk(id);
if (!user) {
ctx.status = 404;
return;
}
const { name, age } = ctx.request.body;
await user.update({ name, age });
ctx.body = user;
}
async destroy() {
const ctx = this.ctx;
const id = toInt(ctx.params.id);
const user = await ctx.model.User.findByPk(id);
if (!user) {
ctx.status = 404;
return;
}
await user.destroy();
ctx.status = 200;
}
}
module.exports = UserController;
最后我們將這個(gè) controller 掛載到路由上:
// app/router.js
module.exports = app => {
const { router, controller } = app;
router.resources('users', '/users', controller.users);
};
腳手架
我們也提供了 sequelize 的腳手架榔昔,集成了文檔中提供的 egg-sequelize, sequelize-cli 與 factory-girl 等模塊驹闰。可以通過(guò) npm init egg --type=sequelize
來(lái)基于它快速初始化一個(gè)新的應(yīng)用撒会。