Sequelize 操作数据库的ORM框架

一般在操作数据库时,并不会直接使用sql语句来操作,而是使用ORM框架与数据库建立映射关系,Sequelize就是nodejs服务中常用的一个orm库,orm框架具备以下特点:

  • 优点

1、只需要面向对象编程, 不需要面向数据库编写代码: 对数据库的操作都转化成对类属性和方法的操作,不用编写各种数据库的sql语句

2、实现了数据模型与数据库的解耦,屏蔽了不同数据库操作的差异: 不在关注用的是mysql、oracle….等,通过简单的配置就可以轻松更换数据库,而不需要修改代码。

  • 缺点

1、相比较直接使用SQL语句操作数据库,有性能损失.

2、根据对象的操作转换成SQL语句,根据查询的结果转化成对象, 在映射过程中有性能损失.。

搭建 Sequelize 使用环境

如果是在eggjs中使用的话,参考eggjs文档,比较齐全。练习搭建练习环境如下:

mkdir Sequelize-demo
npm init -y
  • 然后安装 Sequelize
npm install --save sequelize
// 使用mysql安装mysql2
npm install --save mysql2
  • 使用 Sequelize
const Sequelize = require('sequelize')

const sequelize = new Sequelize('music_api', 'root', '123456', {
  host: 'localhost',
  dialect: 'mysql' /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */
})

常用数据类型

  • STRING

字符串类型,传入参数可指定长度,如:

Sequelize.STRING(60)
  • TEXT

文本类型,相当于转成数据库中的类型如下:

Sequelize.TEXT                        // TEXT
Sequelize.TEXT('tiny')                // TINYTEXT
Sequelize.CITEXT                      // CITEXT      PostgreSQL and SQLite only.
  • INTEGER

整数数类型,int

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

浮点数类型,float

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

日期类型

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.
  • BOOLEAN

布尔值类型

Sequelize.BOOLEAN                     // TINYINT(1)

模型的定义

modelName: 数据表的名字; timestamps: false:不将timestamps设置成false的话,会自动有两个时间createdAt和updatedAt; freezeTableName: true:不将freezeTableName设置成false,查询时直接表名就是复数形式的了

class Singer extends Sequelize.Model { }
Singer.init({
  id: {
    type: Sequelize.STRING,
    primaryKey: true, // 主键
    allowNull: false  // 不允许空值
  },
  name: Sequelize.STRING(50)
}, { sequelize, modelName: 'singer', timestamps: false, freezeTableName: true });

查询记录

  • findOne()

查询符合条件的一条记录,相当于加了个 limit 1 的条件查询

Singer.findOne().then(singers=>{
  console.log(singers)
})

还能添加条件,排序方式,查询字段

Singer.findOne({
  attributes: ['name', 'avatar'],
  where:{},
  order: [['id', 'asc']]
}).then(singers=>{
  console.log(singers)
})
  • findByPk()

相当于查询详情,传入id

Singer.findByPk(1).then(singers => {
  console.log(singers)
})
  • findAll()

查询符合条件的全部记录

Singer.findAll().then(singers => {
  console.log(singers)
})
  • findAndCountAll()

查询符合条件的全部记录,并且统计记录条数,在分页是常用

Singer.findAndCountAll({
  limit: 5,
  offset: 2
}).then(res => {
  let { count, rows } = res
  console.log(count, rows)
})
  • count() max() min() sum()

统计相关的函数

Project.count({ where: {'id': {[Op.gt]: 25}} }).then(c => {
  console.log("There are " + c + " projects with an id greater than 25.")
})

Project.max('age', { where: { age: { [Op.lt]: 20 } } }).then(max => {
  // will be 10
})

Project.min('age', { where: { age: { [Op.gt]: 5 } } }).then(min => {
  // will be 10
})

Project.sum('age', { where: { age: { [Op.gt]: 5 } } }).then(sum => {
  // will be 50
})

Op逻辑条件

Op集成了多种条件查询,模糊查询,in语法,大于,小于,范围之间等等

const Op = Sequelize.Op

[Op.and]: {a: 5}           // AND (a = 5)
[Op.or]: [{a: 5}, {a: 6}]  // (a = 5 OR a = 6)
[Op.gt]: 6,                // > 6
[Op.gte]: 6,               // >= 6
[Op.lt]: 10,               // < 10
[Op.lte]: 10,              // <= 10
[Op.ne]: 20,               // != 20
[Op.eq]: 3,                // = 3
[Op.not]: true,            // IS NOT TRUE
[Op.between]: [6, 10],     // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
[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.iLike]: '%hat'         // ILIKE '%hat' (case insensitive) (PG only)
[Op.notILike]: '%hat'      // NOT ILIKE '%hat'  (PG only)
[Op.startsWith]: 'hat'     // LIKE 'hat%'
[Op.endsWith]: 'hat'       // LIKE '%hat'
[Op.substring]: 'hat'      // LIKE '%hat%'
[Op.regexp]: '^[h|a|t]'    // REGEXP/~ '^[h|a|t]' (MySQL/PG only)
[Op.notRegexp]: '^[h|a|t]' // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only)
[Op.iRegexp]: '^[h|a|t]'    // ~* '^[h|a|t]' (PG only)
[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only)
[Op.like]: { [Op.any]: ['cat', 'hat']}
                       // LIKE ANY ARRAY['cat', 'hat'] - also works for iLike and notLike
[Op.overlap]: [1, 2]       // && [1, 2] (PG array overlap operator)
[Op.contains]: [1, 2]      // @> [1, 2] (PG array contains operator)
[Op.contained]: [1, 2]     // <@ [1, 2] (PG array contained by operator)
[Op.any]: [2,3]            // ANY ARRAY[2, 3]::INTEGER (PG only)

[Op.col]: 'user.organization_id' // = "user"."organization_id", with dialect specific column identifiers, PG in this example

Associations关联

一对一,一对多,多对多关联参考文章

foreignKey: 外键字段名称; sourceKey:当前表的字段名; constraints: false,不同步建立外键关系

class Singer extends Sequelize.Model { }
Singer.init({
  id: {
    type: Sequelize.STRING,
    primaryKey: true,
    allowNull: false
  },
  name: Sequelize.STRING(50),
  singer_id: Sequelize.STRING,
  avatar: Sequelize.STRING,
  created: Sequelize.INTEGER(10),
  updated: Sequelize.INTEGER(10)
}, { sequelize, modelName: 'singer', timestamps: false, freezeTableName: true });

const Audio = sequelize.define('audio', {
  // attributes
  id: {
    type: Sequelize.STRING,
    primaryKey: true,
    allowNull: false
  },
  name: Sequelize.STRING(50)
}, { sequelize, modelName: 'audio', timestamps: false, freezeTableName: true });
Singer.hasMany(Audio, {
  foreignKey: 'singer_id',
  sourceKey: 'singer_id'
});
Audio.belongsTo(Singer, {
  foreignKey: 'singer_id',
  sourceKey: 'singer_id'
})

Singer.findAndCountAll({
  include: [Audio],
  limit: 1,
  offset: 2
}).then(res => {
  let { count, rows } = res
  console.log(rows)
  // rows.forEach(v=>{
  //   v.audios.forEach(e=>{
  //     console.log(e)
  //   })
  // })
})

添加记录

create方法,传入数据

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;
}

更新记录

update方法,传入数据

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;
}

删除记录

destroy方法

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;
}

eggjs的一个完整实例

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;

egg-sequelize连接多个数据库

config.sequelize = {
  datasources: [
    {
      delegate: 'model', // load all models to app.model and ctx.model
      baseDir: 'model', // load models from `app/model/*.js`
      dialect: 'mysql',
      database: process.env.EGG_MYSQL_DATABASE || 'seq_test_staging',
      host: process.env.EGG_MYSQL_HOST || '127.0.0.1',
      port: process.env.EGG_MYSQL_PORT || '3306',
      username: process.env.EGG_MYSQL_USERNAME,
      password: process.env.EGG_MYSQL_PASSWORD,
      operatorsAliases: Op,
      pool: {
        max: 20,
        min: 0,
        idle: 10000,
      },
    },
    {
      delegate: 'testModel', // load all models to app.adminModel and ctx.adminModel
      baseDir: 'test_model', // load models from `app/admin_model/*.js`
      dialect: 'mysql',
      database: process.env.EGG_MYSQL_DATABASE || 'test',
      host: process.env.EGG_MYSQL_HOST || '127.0.0.1',
      port: process.env.EGG_MYSQL_PORT || '3306',
      username: process.env.EGG_MYSQL_USERNAME,
      password: process.env.EGG_MYSQL_PASSWORD,
      operatorsAliases: Op,
      pool: {
        max: 20,
        min: 0,
        idle: 10000,
      },
    },
  ],
};

参考文献

  • 使用Sequelize连接数据库