文中完整示例代碼已上傳至 GitHub, guchongxi/egg-sequelize-demo。html
2019.10.11mysql
直接使用 egg-sequelize 插件便可git
yarn add egg-sequelize mysql2 -S
複製代碼
啓用插件github
// config/plugin.js
exports.sequelize = {
enable: true,
package: 'egg-sequelize'
}
複製代碼
添加配置sql
// config/config.{env}.js
exports.sequelize = {
// 數據庫類型
dialect: 'mysql',
// 數據庫地址
host: '127.0.0.1',
// 端口
port: 3306,
// 數據庫名
database: 'test-database',
// 用戶名,默認爲 root
username: 'root',
// 密碼,默認爲空
password: '',
// sequelize 配置
define: {
// 添加createAt,updateAt,deleteAt時間戳
timestamps: true,
// 使用軟刪除,即僅更新 deleteAt 時間戳 而不刪除數據
paranoid: true,
// 不容許修改表名
freezeTableName: true,
// 禁止駝峯式字段默認轉爲下劃線
underscored: false,
},
// 因爲orm用的UTC時間,這裏必須加上東八區,不然設置的時間相差8小時
timezone: '+08:00',
// mysql2 配置
dialectOptions: {
// 讓讀取date類型數據時返回時間戳而不是UTC時間
dateStrings: true,
typeCast(field, next) {
if (field.type === 'DATETIME') {
return new Date(field.string()).valueOf();
}
return next();
},
},
}
複製代碼
若是僅使用 docker 啓動 mysql 可參考 mysqlchrome
docker 安裝與其餘操做請自行 googledocker
建議使用 docker compose,更方便擴展其餘容器數據庫
新建 docker-compose.yml
json
version: '3.7'
services:
mysql:
image: mysql:5.7
volumes:
- data-mysql:/var/lib/mysql
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
MYSQL_DATABASE: 'test-database'
ports:
- '3306:3306'
restart: unless-stopped
volumes:
data-mysql:
複製代碼
啓動容器bash
docker-compose up -d
複製代碼
中止容器
docker-compose down
複製代碼
中止並銷燬數據
docker-compose dowm -v
複製代碼
實體
表結構
users
-> 存儲用戶infos
-> 存儲用戶信息,使用 userId 關聯 userapps
-> 存儲應用,經過 userId 關聯 usertasks
-> 存儲任務,經過 appId 關聯 app,經過 userId 關聯 usergroups
-> 存在羣組group_app
-> 存儲羣組和應用關係,經過 appId 關聯 app,經過 groupId 關聯 group表關係
爲方便以後對數據模型進行變動和簡化操做,咱們使用 sequelize-cli 來作數據庫初始化
yarn add sequelize-cli -D
npx sequelize init
複製代碼
更改配置
{
"development": {
"username": "root",
"password": null,
"database": "test-database",
"host": "127.0.0.1",
"port": 3306,
"dialect": "mysql",
"timezone": "+08:00",
"define": {
"charset": "utf8",
"dialectOptions": {
"collate": "utf8_general_ci"
}
}
}
}
複製代碼
'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')
};
複製代碼
npx sequelize model:generate --name users --attributes username:string,email:string
複製代碼
// database/migrations/{time}-create-users.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable(
'users',
{
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
username: {
allowNull: false,
type: Sequelize.STRING,
},
email: {
allowNull: false,
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
type: Sequelize.DATE,
},
},
{
charset: 'utf8',
},
);
},
down: (queryInterface) => {
return queryInterface.dropTable('users');
},
};
複製代碼
npx sequelize model:generate --name tasks --attributes appId:integer,userId:integer,status:integer,description:string
複製代碼
// database/migrations/{time}-create-tasks.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable(
'tasks',
{
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
appId: {
allowNull: false,
type: Sequelize.INTEGER,
},
// 由於可能不是用戶操做,容許爲 null
userId: {
type: Sequelize.INTEGER,
},
status: {
allowNull: false,
type: Sequelize.INTEGER,
},
description: {
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
type: Sequelize.DATE,
},
},
{
charset: 'utf8',
},
);
},
down: (queryInterface) => {
return queryInterface.dropTable('tasks');
},
};
複製代碼
其餘表參照 users
表生成便可
爲方便測試,咱們能夠預先將模擬數據一併生成
npx sequelize seed:generate --name users
複製代碼
// database/seeders/{time}-users.js
'use strict';
module.exports = {
up: (queryInterface) => {
return queryInterface.bulkInsert(
'users',
[
{
username: 'test user',
email: 'test@test.com',
createdAt: new Date(),
updatedAt: new Date(),
},
],
{},
);
},
down: (queryInterface) => {
return queryInterface.bulkDelete('users', { email: 'test@test.com' }, {});
},
};
複製代碼
其餘表按照相同步驟生成便可
# 初始化表結構
npx sequelize db:migrate
# 初始化表數據
npx sequelize db:seed:all
複製代碼
這樣數據庫中的表和數據就建好了
Mac OS 用戶可以使用 Sequel Pro 可視化查看數據庫
在初始化數據模型時,同時生成的還有 app/model/*.js
users.js
'use strict';
module.exports = (app) => {
const { Sequelize, model } = app;
const Users = model.define(
'users',
{
// 用戶名
username: Sequelize.STRING,
// 郵箱
email: Sequelize.STRING,
},
{
// 默認不輸出 deletedAt 字段
// 參考 https://sequelize.org/master/class/lib/model.js~Model.html#static-method-scope
defaultScope: {
attributes: {
exclude: ['deletedAt'],
},
},
},
);
Users.associate = function () {
// 與應用是 一對多
app.model.Users.hasMany(app.model.Apps, {
foreignKey: 'userId',
});
// 與任務是 一對多
app.model.Users.hasMany(app.model.Tasks, {
foreignKey: 'userId',
});
// 與信息是 一對一
app.model.Users.hasOne(app.model.Infos, {
foreignKey: 'userId',
});
};
return Users;
};
複製代碼
apps.js
'use strict';
module.exports = (app) => {
const { Sequelize, model } = app;
const Apps = model.define(
'apps',
{
// 建立用戶 id
userId: Sequelize.INTEGER,
// 應用中文名
name: Sequelize.STRING,
// 應用倉庫地址,ssh 風格
gitUrl: Sequelize.STRING,
},
{
defaultScope: {
attributes: {
exclude: ['deletedAt'],
},
},
},
);
Apps.associate = function () {
// 與用戶是 多對一
app.model.Apps.belongsTo(app.model.Users, {
foreignKey: 'userId',
});
// 與任務是 一對多
app.model.Apps.hasMany(app.model.Tasks, {
foreignKey: 'appId',
});
// 與羣組是 多對多
app.model.Apps.belongsToMany(app.model.Groups, {
through: app.model.GroupApp,
foreignKey: 'appId',
otherKey: 'groupId'
});
};
return Apps;
};
複製代碼
其餘 model
參考 egg-sequelize-demo/app/model/
參考:associations
在 model
中咱們定義了表間的關聯關係能夠看出主要是三種關係:一對一,一對多,多對多
在一對一中,通常會有主從關係,示例爲 user(主) 和 info(從)
對於主使用 hasOne(Model, { foreignKey: '從表中關聯主表的字段名,示例爲 userId' })
對於從使用 belongsTo(Model ,{ foreignKey: '從表中關聯主表的字段名,示例爲 userId' })
在一對多中,一爲主,多爲從,示例爲 user(主) 和 app(從)
對於主使用 hasMany(Model, { foreignKey: '從表中關聯主表的字段名,示例爲 userId' })
對於從使用 belongsTo(Model, { foreignKey: '從表中關聯主表的字段名,示例爲 userId' })
在多對多中,不須要區分主從的概念
雙方都使用 belongsToMany(Model, { through: MiddleModel, foreignKey: '中間表中關聯本身的字段名,若是在 group 中定義則爲 groupId', otherKey: '中間表中關聯另外一方的字段名,若是在 group 中定義則爲 appId' })
一般,咱們會在 service 中處理與數據庫的交互
// app/service/apps.js
'use strict';
const Service = require('egg').Service;
class Apps extends Service {
async list() {
return this.ctx.model.Apps.findAll({
include: [{
model: this.ctx.model.Users,
as: 'user',
}, {
model: this.ctx.model.Tasks,
as: 'tasks',
}, {
model: this.ctx.model.Groups,
as: 'groups',
// 配置 groups 元素輸出 name,desc 字段
attributes: ['name', 'desc'],
}],
});
}
}
module.exports = Apps;
複製代碼
// app/service/groups.js
'use strict';
const Service = require('egg').Service;
class Groups extends Service {
async list() {
return this.ctx.model.Groups.findAll({
include: [{
model: this.ctx.model.Apps,
as: 'apps',
include: [{
model: this.ctx.model.Users,
as: 'user'
}]
}],
});
}
}
module.exports = Groups;
複製代碼
// app/service/tasks.js
'use strict';
const Service = require('egg').Service;
class Tasks extends Service {
async list() {
return this.ctx.model.Tasks.findAll({
include: [{
model: this.ctx.model.Users,
as: 'user',
}, {
model: this.ctx.model.Apps,
as: 'app',
}],
});
}
}
module.exports = Tasks;
複製代碼
// app/service/users.js
'use strict';
const Service = require('egg').Service;
class Users extends Service {
async list() {
return this.ctx.model.Users.findAll({
include: [{
model: this.ctx.model.Apps,
as: 'apps',
}, {
model: this.ctx.model.Tasks,
as: 'tasks',
}, {
model: this.ctx.model.Infos,
as: 'info',
}],
});
}
}
module.exports = Users;
複製代碼
END