Egg框架ORM的Sequelize语法规则

1,923 阅读8分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

基础

依赖包

npm init egg
npm i
npm install --save egg-sequelize mysql2
npm install --save-dev sequelize-cli

配置

  • config/plugin.js

    exports.sequelize = {
      enable: true,
      package: 'egg-sequelize',
    };
    
  • config/config.default.js

    config.sequelize = {
      dialect: 'mysql', // 表示使用mysql
      host: '127.0.0.1', // 连接的数据库主机地址
      port: 3306, // mysql服务端口
      database: 'egg_mysql_development', // 数据库名
      username: 'root', // 数据库用户名
      password: '123456', // 数据库密码
      timezone: '+8:00', // 由于orm用的UTC时间,这里必须加上东八区,否则取出来的时间相差8小时
      define: { // model的全局配置
        timestamps: false, // 添加create,update,delete时间戳
        freezeTableName: false, // 防止修改表名为复数
        underscored: false, // 防止驼峰式字段被默认转为下划线
        // 字段生成软删除时间戳 deleted_at
        paranoid: true,
        createdAt: 'created_at',
        updatedAt: 'updated_at',
        deletedAt: 'deleted_at',
      },
      dialectOptions: { // 让读取date类型数据时返回字符串而不是UTC时间
        dateStrings: true,
        typeCast(field, next) {
          if (field.type === 'DATETIME') {
            return field.string();
          }
          return next();
        },
      },
    };
    
  • .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'),
    };
    
  • database/config.json

    {
      "development": {
        "username": "root",
        "password": null,
        "database": "test",
        "host": "127.0.0.1",
        "dialect": "mysql",
        "timezone": "+08:00"
      }
    }
    

命令

  • 初始化

    sequelize init                              #初始化项目
    sequelize init:config                       #初始化config
    sequelize init:migrations                   #初始化migrations
    sequelize init:models                       #初始化models
    sequelize init:seeders                      #初始化seeders
    
  • 数据库

    sequelize db:create                         创建数据库
    sequelize db:drop                           删除数据库
      
    sequelize db:migrate                        运行挂起的迁移
    sequelize db:migrate:schema:timestamps:add  
    sequelize db:migrate:status                 列出所有迁移的状态
    sequelize db:migrate:undo                   撤销上一次迁移
    sequelize db:migrate:undo:all               撤销所有迁移 
    sequelize db:migrate:undo --name xxx		  撤销指定迁移
      
    sequelize db:seed                           运行指定种子文件
    sequelize db:seed:undo                      删除指定文件
    sequelize db:seed:all                       运行所有种子文件
    sequelize db:seed:undo:all                  删除所有指定文件
    
  • 生成文件

    sequelize migration:generate                #生成一个新migrate文件[aliases: migration:create]
    sequelize migration:generate --name=[model]
    sequelize model:generate                    #生成一个新model文件[aliases: model:create]
    sequelize seed:generate                     #生成一个新seed文件[aliases: seed:create]
    

数据类型

sequelize.STRING                      // VARCHAR(255)
Sequelize.STRING(1234)                // VARCHAR(1234)
Sequelize.STRING.BINARY               // VARCHAR BINARY
Sequelize.TEXT                        // TEXT
Sequelize.TEXT('tiny')                // TINYTEXT
Sequelize.CITEXT                      // CITEXT      PostgreSQL and SQLite only.

Sequelize.INTEGER                     // INTEGER
Sequelize.BIGINT                      // BIGINT
Sequelize.BIGINT(11)                  // BIGINT(11)

Sequelize.FLOAT                       // FLOAT
Sequelize.FLOAT(11)                   // FLOAT(11)
Sequelize.FLOAT(11, 10)               // FLOAT(11,10)

Sequelize.REAL                        // REAL        PostgreSQL only.
Sequelize.REAL(11)                    // REAL(11)    PostgreSQL only.
Sequelize.REAL(11, 12)                // REAL(11,12) PostgreSQL only.

Sequelize.DOUBLE                      // DOUBLE
Sequelize.DOUBLE(11)                  // DOUBLE(11)
Sequelize.DOUBLE(11, 10)              // DOUBLE(11,10)

Sequelize.DECIMAL                     // DECIMAL
Sequelize.DECIMAL(10, 2)              // DECIMAL(10,2)

Sequelize.DATE                        // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
Sequelize.DATE(6)                     // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision
Sequelize.DATEONLY                    // DATE without time.
Sequelize.BOOLEAN                     // TINYINT(1)

Sequelize.ENUM('value 1', 'value 2')  // An ENUM with allowed values 'value 1' and 'value 2'
Sequelize.ARRAY(Sequelize.TEXT)       // Defines an array. PostgreSQL only.
Sequelize.ARRAY(Sequelize.ENUM)       // Defines an array of ENUM. PostgreSQL only.

Sequelize.JSON                        // JSON column. PostgreSQL, SQLite and MySQL only.
Sequelize.JSONB                       // JSONB column. PostgreSQL only.

Sequelize.BLOB                        // BLOB (bytea for PostgreSQL)
Sequelize.BLOB('tiny')                // TINYBLOB (bytea for PostgreSQL. Other options are medium and long)

Sequelize.UUID                        // UUID datatype for PostgreSQL and SQLite, CHAR(36) BINARY for MySQL (use defaultValue: Sequelize.UUIDV1 or Sequelize.UUIDV4 to make sequelize generate the ids automatically)

Sequelize.CIDR                        // CIDR datatype for PostgreSQL
Sequelize.INET                        // INET datatype for PostgreSQL
Sequelize.MACADDR                     // MACADDR datatype for PostgreSQL

Sequelize.RANGE(Sequelize.INTEGER)    // Defines int4range range. PostgreSQL only.
Sequelize.RANGE(Sequelize.BIGINT)     // Defined int8range range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DATE)       // Defines tstzrange range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DATEONLY)   // Defines daterange range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DECIMAL)    // Defines numrange range. PostgreSQL only.

Sequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE)) // Defines array of tstzrange ranges. PostgreSQL only.

Sequelize.GEOMETRY                    // Spatial column.  PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT')           // Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT', 4326)     // Spatial column with geometry type and SRID.  PostgreSQL (with PostGIS) or MySQL only.

模型

migrations

  • 创建

    module.exports = {
      up: async (queryInterface, Sequelize) => {
        const { INTEGER, STRING, DATE, ENUM } = Sequelize;
        // 创建表
        await queryInterface.createTable('tabelName',
          { }, { });
        // 添加索引
        queryInterface.addIndex('tabelName', [ '列名' ]);
          // 外键约束
    queryInterface.addConstraint('tableName', ['user_id'], {
        type: 'foreign key',
        name: 'user_id',
        references: { //Required field
            table: 'users',
            field: 'id'
        },
        onDelete: 'cascade',
        onUpdate: 'cascade'
    });
      },
    
      down: async queryInterface => {
        await queryInterface.dropTable('tabelName');
      },
    };
    
  • 新增

    module.exports = {
      up: (queryInterface, Sequelize) => {
        return queryInterface.sequelize.transaction(t => {
            //新增字段
          return Promise.all([
            queryInterface.addColumn('tabelName', '字段', {
            //字段相关参数
              type: Sequelize.INTEGER,
            }, { transaction: t }),
            queryInterface.addColumn('tabelName', '字段', {
              type: Sequelize.STRING,
            }, { transaction: t }),
          ]);
        });
      },
    
      down: (queryInterface, Sequelize) => {
        return queryInterface.sequelize.transaction(t => {
          return Promise.all([
            queryInterface.removeColumn('tabelName', '字段', { transaction: t }),
            queryInterface.removeColumn('tabelName', '字段', { transaction: t }),
          ]);
        });
      },
    };
    

model

module.exports = app => {
  const modelInfo = app.model.define(
    // 用于指定表的名称
    'users表名称',
    // 用于指定表中有哪些字段
    {
      字段名称: {
        allowNull: false, // 是否允许为空
        autoIncrement: true, // 字段是否是自增类型
        primaryKey: true, // 字段是否是主键
        type: Sequelize.INTEGER, // 字段是整型
        defaultValue: null, // 字段默认值
        unique: true, // 唯一索引
        autoIncrement: true, // 整数列
        field: 'field_with_underscores', // 自定义列名称
        comment: '这是带有注释的列', // 注释列
        // 外键列
        references: {
          // 这是对另一个模型的参考
          model: Bar,
          // 这是引用模型的列名
          key: 'id',
          // 使用 PostgreSQL,可以通过 Deferrable 类型声明何时检查外键约束.
          deferrable: Deferrable.INITIALLY_IMMEDIATE,
          // 参数:
          // - `Deferrable.INITIALLY_IMMEDIATE` - 立即检查外键约束
          // - `Deferrable.INITIALLY_DEFERRED` - 将所有外键约束检查推迟到事务结束
          // - `Deferrable.NOT` - 完全不推迟检查(默认) - 这将不允许你动态更改事务中的规则
        },
        get() {
          return this.getDataValue('字段名称');
        },
        set(val) {
          this.setDataValue('字段名称', val);
        },
      },
    },
    // 用于配置表的一些额外信息
    {
      // 自定义表名
      freezeTableName: true,
      // tableName: 'users',
      // 是否需要增加createdAt、updatedAt、deletedAt字段
      timestamps: true,
      // 不需要createdAt字段
      createdAt: false,
      // 将updatedAt字段改个名
      updatedAt: 'utime',
      // 将deletedAt字段改名
      // 同时需要设置paranoid为true(此种模式下,删除数据时不会进行物理删除,而是设置deletedAt为当前时间
      deletedAt: 'dtime',
      paranoid: true,
      // 指定索引
      indexes: [],
    }
  );
  // 生命周期
    // 查询前
  modelInfo.beforeFind((model, option) => {
    console.log('查询前');
  });
  // 查询后
  modelInfo.afterFind((model, option) => {
    console.log('查询后');
  });
  // 新增前
  modelInfo.beforeCreate((model, option) => {
    console.log('新增前');
  });
  // 新增后
  modelInfo.afterCreate((model, option) => {
    console.log('新增后');
  });
  // 修改前
  modelInfo.beforeUpdate((model, option) => {
    console.log('修改前');
  });
  // 修改后
  modelInfo.afterUpdate((model, option) => {
    console.log('修改后'); // 真正修改才会触发,数据相同不会触发
  });
  // 删除前
  modelInfo.beforeDestroy((model, option) => {
    console.log('删除前');
  });
  // 删除后
  modelInfo.afterDestroy((model, option) => {
    console.log('删除后');
  });
     // 关联关系
  modelInfo.associate = function(models) {
    // 关联用户资料 一对一
    modelInfo.hasOne(app.model.modelInfo);
  };
  // 关联用户表
  modelInfo.associate = function(models) {
    app.model.modelInfo.belongsTo(app.model.User);
  };
  return modelInfo;
};

seed

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('表名',
    //data数据
      [],
      // 其他配置信息
      {});
  },

  down: (queryInterface, Sequelize) => {
    return queryInterface.bulkDelete('表名', null, {});
  },
};

关联操作

  • 一对多

    class City extends Model {}
    City.init({ countryCode: Sequelize.STRING }, { sequelize, modelName: 'city' });
    class Country extends Model {}
    Country.init({ isoCode: Sequelize.STRING }, { sequelize, modelName: 'country' });
    
    // 在这里,我们可以根据国家代码连接国家和城市
    Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'});
    City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'});
    
  • 多对多

    User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' })
    Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' })
    
  • 案例

    // 获取关联模型对象,n对一不需要加s
    let userinfo = await user.getUserinfo();
    // n对多需要加s
    await user.getPosts({
        attributes: ['title'],
        where: {
            id: 3
        }
    });
    // 关联操作
    // 1:用户创建文章(一对多)
    await this.ctx.model.Post.create({
        title: "第一篇文章",
        user_id: user.id
    });
    
    // 2.获取当前用户所有文章
    await user.getPosts();
    await user.getPosts({
        attributes: ['id'],
        where:{
            title:"测试"
        }
    });
    
    // 3:用户删除文章(一对多)
    // (1) 获取当前用户的所有文章
    let posts = await user.getPosts({
        attributes: ['id']
    });
    posts = posts.map(v => v.id);
    await this.ctx.model.Post.destroy({
        where: {
            id: posts
        }
    });
    
    // 场景三:用户关注话题(多对多)
    await this.ctx.model.TopicUser.bulkCreate([{
        user_id: user.id,
        topic_id: 1
    },{
        user_id: user.id,
        topic_id: 2
    }]);
    
    // 用户关注话题(多对多)
    await this.ctx.model.TopicUser.destroy({
        where: {
            user_id: user.id,
            topic_id: [1, 2]
        }
    });
    

查询

模型查询

  • 主键查询

    Model.findByPk(1)
    
  • 查找不存在则创建

    //检查数据库中是否已存在某个元素. 如果元素存在,则查询 如果元素不存在,将会被创建.
    Model.findOrCreate()
    
  • 查询并计数

    返回对象
    - `count` - 一个整数,总数记录匹配where语句和关联的其它过滤器
    - `rows` - 一个数组对象,记录在limit和offset范围内匹配where语句和关联的其它过滤器,
    Model.findAndCountAll()
    
  • 查询多个

    Model.findAll()
    
  • 查询单个

    Model.findOne()
    

条件

{
  // 获取数量查询
  limit: 10,
  // 跳过数量查询
  offset: 2,
  // 分组查询
  group: 'name',
  // 字段过滤
  attributes: [ 'foo', 'bar' ],
  // 字段过滤,可以重命名
  // attributes: ['foo', ['bar', 'baz']]
  // 条件过滤查询
  where: {},
  // 排序查询
  order: [
    // 将返回 `name`
    [ 'name' ],
    // 将返回 `username` DESC
    [ 'username', 'DESC' ],
    // 将返回 max(`age`)
    sequelize.fn('max', sequelize.col('age')),
    // 将返回 max(`age`) DESC
    [ sequelize.fn('max', sequelize.col('age')), 'DESC' ],
    // 将返回 otherfunction(`col1`, 12, 'lalala') DESC
    [ sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC' ],
    // 将返回 otherfunction(awesomefunction(`col`)) DESC,这个嵌套是可以无限的!
    [ sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC' ],
  ],
  // 关联查询
  include: [{
    model: Task,
    where: { state: Sequelize.col('project.state') },
  }],
};

where

  • 基础

    [
      // WHERE authorId = 2
      {
        authorId: 2,
      },
      // WHERE authorId = 12 AND status = 'active';
      {
        authorId: 12,
        status: 'active',
      },
      // WHERE authorId = 12 OR authorId = 13;
      {
        [Op.or]: [{ authorId: 12 }, { authorId: 13 }],
      },
      // WHERE authorId = 12 OR authorId = 13;
      {
        authorId: {
          [Op.or]: [ 12, 13 ],
        },
      },
      // WHERE status = 'inactive';
      {
        status: 'inactive',
      },
      // WHERE deletedAt NOT NULL;
      {
        deletedAt: {
          [Op.ne]: null,
        },
      },
      // WHERE char_length(status) = 6;
      {
        where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6),
      },
    ];
    
  • 操作符

    const { Op } = require("sequelize");
    Post.findAll({
      where: {
        [Op.and]: [{ a: 5 }, { b: 6 }],            // (a = 5) AND (b = 6)
        [Op.or]: [{ a: 5 }, { b: 6 }],             // (a = 5) OR (b = 6)
        someAttribute: {
          // 基本
          [Op.eq]: 3,                              // = 3
          [Op.ne]: 20,                             // != 20
          [Op.is]: null,                           // IS NULL
          [Op.not]: true,                          // IS NOT TRUE
          [Op.or]: [5, 6],                         // (someAttribute = 5) OR (someAttribute = 6)
    
          // 使用方言特定的列标识符 (以下示例中使用 PG):
          [Op.col]: 'user.organization_id',        // = "user"."organization_id"
    
          // 数字比较
          [Op.gt]: 6,                              // > 6
          [Op.gte]: 6,                             // >= 6
          [Op.lt]: 10,                             // < 10
          [Op.lte]: 10,                            // <= 10
          [Op.between]: [6, 10],                   // BETWEEN 6 AND 10
          [Op.notBetween]: [11, 15],               // NOT BETWEEN 11 AND 15
    
          // 其它操作符
    
          [Op.all]: sequelize.literal('SELECT 1'), // > ALL (SELECT 1)
    
          [Op.in]: [1, 2],                         // IN [1, 2]
          [Op.notIn]: [1, 2],                      // NOT IN [1, 2]
    
          [Op.like]: '%hat',                       // LIKE '%hat'
          [Op.notLike]: '%hat',                    // NOT LIKE '%hat'
          [Op.startsWith]: 'hat',                  // LIKE 'hat%'
          [Op.endsWith]: 'hat',                    // LIKE '%hat'
          [Op.substring]: 'hat',                   // LIKE '%hat%'
          [Op.iLike]: '%hat',                      // ILIKE '%hat' (不区分大小写) (仅 PG)
          [Op.notILike]: '%hat',                   // NOT ILIKE '%hat'  (仅 PG)
          [Op.regexp]: '^[h|a|t]',                 // REGEXP/~ '^[h|a|t]' (仅 MySQL/PG)
          [Op.notRegexp]: '^[h|a|t]',              // NOT REGEXP/!~ '^[h|a|t]' (仅 MySQL/PG)
          [Op.iRegexp]: '^[h|a|t]',                // ~* '^[h|a|t]' (仅 PG)
          [Op.notIRegexp]: '^[h|a|t]',             // !~* '^[h|a|t]' (仅 PG)
    
          [Op.any]: [2, 3],                        // ANY ARRAY[2, 3]::INTEGER (仅 PG)
          [Op.match]: Sequelize.fn('to_tsquery', 'fat & rat') // 匹配文本搜索字符串 'fat' 和 'rat' (仅 PG)
    
          // 在 Postgres 中, Op.like/Op.iLike/Op.notLike 可以结合 Op.any 使用:
          [Op.like]: { [Op.any]: ['cat', 'hat'] }  // LIKE ANY ARRAY['cat', 'hat']
    
          // 还有更多的仅限 postgres 的范围运算符,请参见下文
        }
      }
    });
    
  • 范围选项

    [Op.contains]: 2           // @> '2'::integer (PG range contains element operator)
    [Op.contains]: [1, 2]      // @> [1, 2) (PG range contains range operator)
    [Op.contained]: [1, 2]     // <@ [1, 2) (PG range is contained by operator)
    [Op.overlap]: [1, 2]       // && [1, 2) (PG range overlap (have points in common) operator)
    [Op.adjacent]: [1, 2]      // -|- [1, 2) (PG range is adjacent to operator)
    [Op.strictLeft]: [1, 2]    // << [1, 2) (PG range strictly left of operator)
    [Op.strictRight]: [1, 2]   // >> [1, 2) (PG range strictly right of operator)
    [Op.noExtendRight]: [1, 2] // &< [1, 2) (PG range does not extend to the right of operator)
    [Op.noExtendLeft]: [1, 2]  // &> [1, 2) (PG range does not extend to the left of operator)
    
  • 组合

    {
      rank: {
        [Op.or]: {
          [Op.lt]: 1000,
          [Op.eq]: null
        }
      }
    }
    // rank < 1000 OR rank IS NULL
    
    {
      createdAt: {
        [Op.lt]: new Date(),
        [Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000)
      }
    }
    // createdAt < [timestamp] AND createdAt > [timestamp]
    
    {
      [Op.or]: [
        {
          title: {
            [Op.like]: 'Boat%'
          }
        },
        {
          description: {
            [Op.like]: '%boat%'
          }
        }
      ]
    }
    // title LIKE 'Boat%' OR description LIKE '%boat%'
    

实用方法

Model.count(“查询条件”)
Model.max(字段,查询条件); 
Model.min(字段,查询条件); 
Model.sum(字段,查询条件); 

增删改

新增

//单新增字段限制
await User.create({ username: 'barfooz', isAdmin: true }, { fields: [ 'username' ] });
// 只有username有效
//批量新增字段限制
User.bulkCreate([
  { username: 'foo' },
  { username: 'bar', admin: true}
], { fields: ['username'] }).then(() => {
  // admin 将不会被构建
})
// 新增
await this.ctx.model.User.create({});
// 批量新增
await this.ctx.model.User.bulkCreate([
  {  },
  {  },
  {  },
]);

修改

//修改字段限制
user.title = 'foooo'
user.description = 'baaaaaar'
await user.save({fields: ['title']});
// title 现在将是 “foooo”,而 description 与以前一样

// 使用等效的 update 调用如下所示:
await user.update({ title: 'foooo', description: 'baaaaaar'}, {fields: ['title']});
//  title 现在将是 “foooo”,而 description 与以前一样

//修改
await user.update(data);
//批量修改
await this.ctx.model.User.update(data, 修改条件);
//递增
await user.increment({
  age: 3, // age每次递增3
  other:2 // other每次递增2
});
//递减
await user.decrement({
  age: 3, // age每次递减3
  other:2 // other每次递减2
});

删除

// 软删除
this.app.model.define('user', { /* bla */}, {
  // 同时需要设置paranoid为true(此种模式下,删除数据时不会进行物理删除,而是设置deletedAt为当前时间
  paranoid: true,
});
//查询包括软删除内容
await this.ctx.model.User.findOne({
  include:{
    model:ctx.model.Video,
    // 包括软删除
    paranoid: false
  },
  where: {
      id: 33
  },
  // 包括软删除
  paranoid: false
});
//彻底删除
this.ctx.model.User.destroy({ force: true })
// 进行软删除...
this.ctx.model.User.destroy();
// 恢复软删除...
this.ctx.model.User.restore();
//条件删除
await this.ctx.model.User.destroy({
  where: {
      name: "批量修改"
   }
});
//批量删除
await this.ctx.model.Post.destroy({
  where: {
      id: posts
  }
});

模型自定义方法

// 模型
// 模型自定义方法
topic_user.ceshi = (param) => {
    console.log('模型自定义方法');
    console.log(param);
    return param;
}

// 控制器
await this.ctx.model.TopicUser.ceshi(123);