介绍

基于Promise的ORM(Object Relation Mapping),支持多种数据库、事务、关联等

安装

pnpm install sequelize mysql2

如果使用sequelize操作mysql数据库,需要安装对应的驱动。
其他的数据库,比如
都有各自对应的驱动程序。

创建sequelize实例

  1. const Sequelize = require('sequelize');
  2. // 方式:分别传入参数
  3. const sequelize = new Sequelize('database', 'username', 'password', {
  4. host: 'localhost',
  5. dialect: /* 'mysql' | 'mariadb' | 'postgres' | 'mssql' 之一 */
  6. });
  7. // 方式2:传入连接URI
  8. const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname');

配置方言dialect

Sequelize独立于特定的方言(数据库),这意味着你必须自己将相应的数据库连接器安装到项目中。

  • MySQL
  • MariaDB
  • SQLite
  • PostgreSQL
  • MSSQL

    MySQL

    为了使Sequelize与MySQL配合良好,需要安装mysql2@^1.5.2或更高版本。然后,可以像这样使用它:
    const sequelize = new Sequelize(‘database’, ‘username’, ‘password’, {
    dialect: ‘mysql’
    })

注意:可以通过设置dialectOptions参数将选项直接传递到方言库。

连接池配置

如果你在单进程中连接到数据库,则应仅创建一个Sequelize实例。

Sequelize可以在初始化时建立连接池了,可以通过构造函数的options参数,即options.pool配置此连接池,如下所示:

  1. const sequelize = new Sequelize(/* ... */, {
  2. // ...
  3. pool: {
  4. max: 5, // 连接池中最大的连接数量
  5. min: 0, // 最小数量
  6. acquire: 30000,
  7. idle: 10 * 1000 // 如果一个连接池 10s之内没有被使用,则释放
  8. }
  9. });

如果在多进程中连接数据库,那么应该为每个进程创建一个实例,但每个进程的连接池应有一个合适的大小,以确保符合最大连接总数。例如:你希望最大连接池大小为90,并且有三个进程,则每个进程的Sequelize实例的最大连接池大小应为30。

一个进程一个实例。

image.png
不使用连接池的话,每个用户进来都会创建一个链接,mysql的连接好像是8小时,到了点就会断开。
这种方式很浪费。

一般上线的项目都使用连接池,连接池是常驻内存的,里面管理了很多连接,有用户进来就会使用连接,用户走了,连接就会断开。如果人太多,还会进行排队。
这样就能实现,随来随用,随用随走。
这样性能会很好。

开发环境下,就使用普通的连接就行。
线上环境,和测试环境,使用连接池就行。

define

测试连接

使用.authenticate()函数测试连接是否正常

  1. sequelize
  2. .authenticate()
  3. .then(() => {
  4. console.log('Connection has been established successfully.');
  5. })
  6. .catch(err => {
  7. console.error('Unable to connect to the database:', err);
  8. });

关闭连接

Sequelize默认保持打开连接,你可以在多个查询中使用相关的连接。如果需要关闭连接,可以调用sequelize.close()。

sync

在初始化Sequelize实例之后,如果需要将定义的Model同步生成数据表,则可以调用sync方法:
let seq = new Sequelize

seq.sync()

猜测是,以表为单位进行同步:
1.若没有这个表,则会根据Model动态生成
2.若存在这个表,则不再对表进行任何操作,修改了model,也不会再对数据库中的表产生同步的作用。

建议:修改了Model之后,手动到数据库中修改对应的表结构。

seq.sync({
force: true
})
强制同步。
只要源码发生改变,则根据清空数据库,根据Model生成所有的表。

所以,轻易不要使用这个属性。

创建Model

模型是对Sequelize.Model类的扩展。
模型可以通过两种方式定义。

方式1

通过Sequelize.Model.init(attributes, options):

  1. const Model = Sequelize.Model;
  2. class User extends Model {}
  3. User.init({
  4. // 定义attributes,即数据表中的field
  5. username: {
  6. type: Sequelize.STRING,
  7. allowNull: false // 不为空,默认为true
  8. }
  9. }, {
  10. // options
  11. sequelize,
  12. modelName: 'user'
  13. });

方式2

  1. const User = sequelize.define('user', {
  2. username: {
  3. type: Sequelize.STRING,
  4. allowNull: false
  5. }
  6. }, {
  7. // options
  8. });

在内部,sequelize.define会调用Model.init。

在上面代码中,会告诉Sequelize期望数据库中有名为users的表,其包含username字段。
默认情况下,表名会自动使用复数形式(通过inflection库来实现)。可以通过配置options中的freezeTableName选项来禁用这一特性:
{
freezeTableName: true
}
这个配置项,即可用在difine方法的options中,也可以在初始化Sequelize的define中。

可以以使用Sequelize构造函数中的define选项来禁用所有模型的这一形为。

另外,默认情况下,Sequelize 还会为每个模型定义id(主键)、createdAt和updatedAt字段。
当然,也可以更改此行为,请参考模型定义章节模型配置API以了解更多信息。

模型与数据库同步

如果你想 Sequelize 通过定义的模型自动创建表(或修改已存在的表),可以使用sync方法,如下所示:

  1. // 注意: `force: true` 选项会在表存在时首先删除表
  2. User.sync({ force: true }).then(() => {
  3. // 现在 `users` 表会与模型定义一致
  4. return User.create({
  5. firstName: 'John',
  6. lastName: 'Hancock'
  7. });
  8. });

一次同步所有模型

可以使用sequelize.sync()方法来同步所有模型,而不是调用每个模型的sync()方法。

生产注意事项

在生产环境中,你应该考虑使用“Migration”来替代调用sync()。了解更多,请参考迁移(Migrations)章节。

基本查询示例

Sequelize是基于Promise的。

  1. // 查询所有 users
  2. User.findAll().then(users => {
  3. console.log("All users:", JSON.stringify(users, null, 4));
  4. });
  5. // 创建一个新 user
  6. User.create({ firstName: "Jane", lastName: "Doe" }).then(jane => {
  7. console.log("Jane's auto-generated ID:", jane.id);
  8. });
  9. // 删除每个名为 "Jane" 的记录
  10. User.destroy({
  11. where: {
  12. firstName: "Jane"
  13. }
  14. }).then(() => {
  15. console.log("Done");
  16. });
  17. // 修改每个`lastName`为`null`的记录修改为"Doe"
  18. User.update({ lastName: "Doe" }, {
  19. where: {
  20. lastName: null
  21. }
  22. }).then(() => {
  23. console.log("Done");
  24. });

Sequelize有很多查询选项,可以通过查询章节了解更多。如果确实需要原始 SQL 查询,也可以进行原始查询

优雅使用:Promise与async/await

在前面示例中,广泛使用了.then,也就是说Sequelize使用Promise。
这意味着,如果你的Node版本支持,就可以通过Sequelize使用ES2017的async/await语法进行异步调用。

另外,所有SequelizePromise实际上都是BluebirdPromise,所以你也可以使用丰富的Bluebird API(如:final、tap、tapCatch、map、mapSeries等)。
如果要设置任何特定于Bluebird的选项,则可以使用Sequelize.Promise访问Sequelize内部使用的Bluebird构造函数。

2022年3月29日
这是2019年v5版本的文档,不知道到了今天有没有替换成原生的Promise。

模型基本使用

数据检索/查找器(Finder)

Finder方法用于从数据库查询数据。它们不会返回普通对象,而是返回模型实例。因为finder方法返回模型实例,所以你可以按照实例文档的所述在查询结果上调用任何模型实例方法。

在本章节,将介绍一些查找器方法可以执行的操作。

find - 搜索数据库中一个特定元素

// 根据已知ID查询
Project.findByPk(123).then(project => {
  // project 会是 Project 的实例,而且为表中 id 为 123 的存储内容
  // 如果未定义此类条目,则将为 null
})

// 根据属性查询
Project.findOne({ where: {title: 'aProject'} }).then(project => {
  // project 会是所匹配到的第一条`title`为'aProject'的 Project || null
})


Project.findOne({
  where: {title: 'aProject'},
  attributes: ['id', ['name', 'title']]
}).then(project => {
  // project 会是所匹配到的第一条`title`为'aProject'的 Project || null
  // project.get('title') 将包含该项目的名称
})

findOrCreate - 搜索一个特定元素不存在时则新建

findOrCreate方法可用于检查数据库中是否已存在某个元素。如果已存在,则返回相应的实例。 如果该元素不存在,则将创建它。
假设我们有一个有User模型的空数据库,其有一个username和一个job属性。
对于创建的情况,可以在where选项后面添加defaults。

findAndCountAll - 搜索数据库中多个元素,同时返回数据和总数

findAndCountAll是一个结合了findAll和count便捷方法(请参见下文),在处理与分页相关的查询时非常有用。在这一查询中,可以用来检索有limit和offset的数据,返回结果除检索数据外还会有记录总数。
本方法查询成功后,将收到有以下两个属性的对象:

  • count - 整数,表示由where子句和其它过滤条件检索到的数据总数
  • rows - 对象数组,由where子句和其它过滤条件匹配到的,及limit和offset的数据范围内的数据
Project
  .findAndCountAll({
     where: {
        title: {
          [Op.like]: 'foo%'
        }
     },
     offset: 10,
     limit: 2
  })
  .then(result => {
    console.log(result.count);
    console.log(result.rows);
  });

它还支持include查询,仅会将标记为required的包含项添加到计数部分。

如果,在检索用户的同时,同时想查出其概况信息:

User.findAndCountAll({ include: [ { model: Profile, required: true } ], limit: 3 });

因为Profile设置了required,所以结果是内连接查询,也就只有有profile信息的用户才会被统计。如果不设置required,则不管有没有profile都会被统计。
另外,添加where条件后,会自动设置required:
User.findAndCountAll({ include: [ { model: Profile, where: { active: true }} ], limit: 3 });
在以上查询中,因为添加了where条件,所以会自动将required设置为true。
传给findAndCountAll的选项参数,与findAll相同(参见下节)。

findAll - 搜索数据库中多个元素

// find multiple entries
Project.findAll().then(projects => {
  // projects will be an array of all Project instances
})

// search for specific attributes - hash usage
Project.findAll({ where: { name: 'A Project' } }).then(projects => {
  // projects will be an array of Project instances with the specified name
})

// search within a specific range
Project.findAll({ where: { id: [1,2,3] } }).then(projects => {
  // projects will be an array of Projects having the id 1, 2 or 3
  // this is actually doing an IN query
})

Project.findAll({
  where: {
    id: {
      [Op.and]: {a: 5},           // AND (a = 5)
      [Op.or]: [{a: 5}, {a: 6}],  // (a = 5 OR a = 6)
      [Op.gt]: 6,                // id > 6
      [Op.gte]: 6,               // id >= 6
      [Op.lt]: 10,               // id < 10
      [Op.lte]: 10,              // id <= 10
      [Op.ne]: 20,               // id != 20
      [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.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)
    },
    status: {
      [Op.not]: false           // status NOT FALSE
    }
  }
})

复合过滤 / OR / NOT 查询

对数据集使用limit、offset、order和group

要获取更多相关数据,可以使用limit、offset、order和group:

有用 !原始查询

有时,你可能希望得到一个仅需显示而无需处理的庞大数据集。
对于所选择的每一行(即每一条记录),Sequelize创建一个实例,该实例具有用于更新、删除、获取关联等功能。
如果数据量较大,则可能需要一些时间。
如果只需要原始数据并且不想更新任何内容,可以像下面这样直接获取原始数据。

// 如果你需要查询大量数据,而不想为每条数据构建DAO花费时间
// 可以传入一个 `raw` 选项,以获取原始数据:
Project.findAll({ where: { ... }, raw: true })

count - 统计数据库中元素数

这是一个统计数据库对象数的方法:

Project.count().then(c => {
  console.log("There are " + c + " projects!")
})

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

max - 获取表中特定属性的最大值

用于获取某一属性的最大值:

/*
  Let's assume 3 person objects with an attribute age.
  The first one is 10 years old,
  the second one is 5 years old,
  the third one is 40 years old.
*/
Project.max('age').then(max => {
  // this will return 40
})

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

min - 获取表中特定属性的最小值

用于获取某一属性的最大值:

/*
  Let's assume 3 person objects with an attribute age.
  The first one is 10 years old,
  the second one is 5 years old,
  the third one is 40 years old.
*/
Project.min('age').then(min => {
  // this will return 5
})

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

sum - 对特定属性的值求和

要计算表中指定列的总和,可以使用sum方法:

/*
  Let's assume 3 person objects with an attribute age.
  The first one is 10 years old,
  the second one is 5 years old,
  the third one is 40 years old.
*/
Project.sum('age').then(sum => {
  // this will return 55
})

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

预加载

当从数据库中检索数据时,你可能会希望获得与同一查询的关联项-这称为“预加载”。其背后的基本思想是,在调用find或findAll时使用include属性。假设有以下设置:

实例

这里的实例,指的是通过Model.create创建的实例,它就是该Model的实例。

let user = await User.create({
    username, password, avatar, email
})

console.log('user是User Model的实例:', user instanceof User)

image.png

非持久化实例

let user = await User.build()
user.save() // 也返回一个promise

持久化实例

let user = await User.create()

实例身上有update、destroy

创建

const User = require('./user')

await User.create({})

查询

// 查询所有记录
await User.findAll()


// 查询一条记录,根据主键,一般就是id
await User.findByPk

// 查询一条记录,条件查询
findOne({where: {username: 1}})

// 查询指定记录,条件查询
await User.findAll({
  where: {
    username: 'lee'
  }
})


// 字段查询
User.findAll({
  attributes: ['id', 'name'],
  limit: 10,
  order:[["id","asc"]] // 查询的结果升序排序
});

等同于
SELECT id, name ...

// 查询并计算记录条数
const res = await UserModel.findAndCountAll({
    where: {
      // 模糊查询
      name: { 
        $like: '%小%', 
      },

      // 精确查询
      age: 18
    },
    limit: size,
    offset: size * (page - 1),
  })

res { count: 记录数, rows: [] }


// 查询不到则创建记录
findOrCreate()

排序

asc:ascend 上升
desc:descend 下降

分页查询


// 查询第一页10条数据,结果按id字段降序排序
await User.findAll({
  limit: 10, 
  offset: 0,
  order:[["id","desc"]]
});

// 查询10条记录
Project.findAll({ limit: 10 })

// 略过8条记录
Project.findAll({ offset: 8 })

// Skip 5 instances and fetch the 5 after that
Project.findAll({ offset: 5, limit: 5 })

更新

DD

// 方式1:先查询出记录,再调用结果的update方法进行更新:
const id = 106;
const user = await User.findByPk(id);

if (!user) {
  ctx.status = 404;
  return;
}

await user.update({ name:"李四", age:43 });

// 方式2:将id为1的用户,重命名为lee
await User.update({name: 'lee'}, {
  where: {
    id: 1
}
})

UPDATE

删除

// 方式1:先查询出记录,再调用结果的destory方法进行删除
const user = await User.findByPk(id);
await user.destory();


// 方式2
await User.destroy({
  where: {
    username: '小',
  }
})

DELETE FORM users WHERE username = '小'

查询操作符

Sequelize公开了可用于创建更复杂比较的符号运算符:
const Op = Sequelize.Op
[Op.and]: [{a: 5}, {b: 6}] // (a = 5) AND (b = 6)
[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.is]: null // IS NULL
[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
[Op.gt]: { [Op.all]: literal(‘SELECT 1’) } // > ALL (SELECT 1)

联表查询

表之间的关系:
1对1
1对多
多对1
多对多

hasOne
belongsTo
hasMany
belongsMany

查询的时候,使用include属性:

User.findOne({
where: {

},

include: [{
   model: Article,
   where: {

   }
}]
})


Sequelize常见的数据类型

Sequelize.STRING // VARCHAR(255)
Sequelize.STRING(1234) // VARCHAR(1234)
Sequelize.STRING.BINARY // VARCHAR BINARY
Sequelize.TEXT // TEXT
Sequelize.TEXT('tiny') // TINYTEXT
Sequelize.INTEGER // INTEGER
Sequelize.BIGINT // BIGINT
Sequelize.BIGINT(11) // BIGINT(11)
Sequelize.FLOAT // FLOAT
Sequelize.FLOAT(11) // FLOAT(11)
Sequelize.FLOAT(11, 12) // FLOAT(11,12)
Sequelize.DOUBLE // DOUBLE
Sequelize.DOUBLE(11) // DOUBLE(11)
Sequelize.DOUBLE(11, 12) // DOUBLE(11,12)
Sequelize.DECIMAL // DECIMAL
Sequelize.DECIMAL(10, 2) // DECIMAL(10,2)
Sequelize.DATE // DATETIME 针对 mysql / sqlite, TIMESTAMP
WITH TIME ZONE 针对 postgres
Sequelize.DATE(6) // DATETIME(6) 针对 mysql 5.6.4+. 小数秒支持多达 6 位精度
Sequelize.DATEONLY // DATE 不带时间.
Sequelize.BOOLEAN // TINYINT(1)

使用原生SQL查询

https://itbilu.com/nodejs/npm/sequelize-docs-v5.html#raw-queries

每个Model上都有一个sequelize实例,通过实例就可以执行原生SQL语句:

sequelize.query('select * from users').then(([results, metadata])=>{

})