在 Egg 中使用 sequelize 操做 mysql

文中完整示例代碼已上傳至 GitHub, guchongxi/egg-sequelize-demohtml

環境

2019.10.11mysql

  • MacOS 10.14.6
  • Node 10.16.3
  • Yarn 1.17.3
  • egg 2.24.0
  • egg-bin 4.13.2
  • docker 19.03.1
  • chrome 77.0.3865.90

引入 sequelize

直接使用 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 Compose 啓動數據庫

若是僅使用 docker 啓動 mysql 可參考 mysqlchrome

docker 安裝與其餘操做請自行 googledocker

建議使用 docker compose,更方便擴展其餘容器數據庫

新建 docker-compose.ymljson

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
複製代碼

初始化表結構

設計表

實體

  • user 用戶
  • app 應用 由用戶建立
  • task 任務 可能由用戶建立指定應用的任務
  • info 用戶信息
  • group 應用羣組 羣組和包含多個應用,應用可加入多個羣組

表結構

  • users -> 存儲用戶
  • infos -> 存儲用戶信息,使用 userId 關聯 user
  • apps -> 存儲應用,經過 userId 關聯 user
  • tasks -> 存儲任務,經過 appId 關聯 app,經過 userId 關聯 user
  • groups -> 存在羣組
  • group_app -> 存儲羣組和應用關係,經過 appId 關聯 app,經過 groupId 關聯 group

表關係

  • users
    • 與應用是 一對多
    • 與任務是 一對多
    • 與信息是 一對一
  • infos
    • 與用戶是 一對一
  • apps
    • 與用戶是 多對一
    • 與任務是 一對多
    • 與羣組是 多對多
  • tasks
    • 與用戶是 多對一
    • 與應用是 多對一
  • groups
    • 與應用是 多對多

生成初始化數據模型

爲方便以後對數據模型進行變動和簡化操做,咱們使用 sequelize-cli 來作數據庫初始化

yarn add sequelize-cli -D

npx sequelize init
複製代碼

更改配置

  • database/config.json
{
  "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"
      }
    }
  }
}
複製代碼
  • .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')
};

複製代碼

定義users表

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');
  },
};
複製代碼

定義tasks表

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 可視化查看數據庫

定義 Model & 表間關係

在初始化數據模型時,同時生成的還有 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/

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 中處理與數據庫的交互

示例1:獲取 app 列表,app 對象包含對應 user,包含全部 task,和所屬 group

// 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;
複製代碼

示例2:獲取 group 列表,group 對象包含所屬 app,app 對象包含對應 user

// 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;
複製代碼

示例3:獲取 task 列表,task 對象包含對應 app,包含對應 user

// 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;
複製代碼

示例4:獲取 user 列表,user 對象包含全部 app,包含全部 task,包含惟一 info

// 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

相關文章
相關標籤/搜索