在本教程中,您将了解 Sequelize 中有哪些模型以及如何使用它们。

概念

模型是 Sequelize 的精髓。模型是是数据库中表的抽象。在Sequelize中,它是一个扩展自Model的类。
该模型告诉 Sequelize 关于它所代表的实体的几件事,例如数据库中表的名称以及它具有哪些列(及其数据类型)。
Sequelize 中的模型有一个名称。此名称不必与它在数据库中表示的表的名称相同。通常,模型具有单数名称(例如User),而表具有复数名称(例如Users),尽管这是完全可配置的。

模型定义

模型是对Sequelize.Model类的扩展。
在Sequelize中有两种等效的方式来定义模型:

  • 调用sequelize.define API:
  • 继承自Model类

使用sequelize.define

  1. const { Sequelize, DataTypes } = require('sequelize');
  2. const sequelize = new Sequelize('sqlite::memory:');
  3. const User = sequelize.define('User', {
  4. // 定义模型属性,对应的就是数据库中表的column名字
  5. firstName: {
  6. type: DataTypes.STRING,
  7. allowNull: false
  8. },
  9. lastName: {
  10. type: DataTypes.STRING
  11. // allowNull defaults to true
  12. }
  13. }, {
  14. // Other model options go here
  15. });
  16. // `sequelize.define` also returns the model
  17. console.log(User === sequelize.models.User); // true

继承自Model类

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

const Model = Sequelize.Model;

class User extends Model {}

User.init({
  // 定义attributes,即数据表中的field
  username: {
    type: Sequelize.STRING,
    allowNull: false // 不为空,默认为true
  }
}, { 
  // options
  sequelize,
  modelName: 'user'
});


const { Sequelize, DataTypes, Model } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');

class User extends Model {}

User.init({
  // 定义模型属性
  firstName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName: {
    type: DataTypes.STRING
    // allowNull defaults to true
  }
}, {
  // Other model options go here
  sequelize, // We need to pass the connection instance
  modelName: 'User' // We need to choose the model name
});

// the defined model is the class itself
console.log(User === sequelize.models.User); // true

在内部,sequelize.define会调用Model.init。所以这两种方法本质上是等价的。

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

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

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

Caveat with Public Class Fields

Table name inference

强制表名等于模型名

直接提供表名

您也可以直接告诉Sequelize表的名称:

sequelize.define('User', {
  // ... (attributes)
}, {
  tableName: 'Employees'
});

模型同步

一次性同步所有模型

调用sequelize.sync方法可以自动同步所有模型。
例子:

await sequelize.sync({ force: true });
console.log("All models were synchronized successfully.");

删除表

删除和模型关联的表:
await User.drop();
console.log(“User table dropped!”);

删除所有的表:
await sequelize.drop();
console.log(“All tables dropped!”);

数据库安全检查

在生产环境中同步

时间戳

默认情况下,Sequelize 会自动为模型添加createdAt和updatedAt两个属性,用来记录数据何时进入数据库,以及数据最后更新时间。

默认情况下,Sequelize会使用数据类型DataTypes自动将字段createdAt和updatedAt添加到每个模型中。日期这些字段也会被自动管理——无论何时使用Sequelize创建或更新某些内容,这些字段都会被正确设置。createdAt字段将包含表示创建时刻的时间戳,updatedAt将包含最新更新的时间戳。

列声明简写语法

如果列的唯一指定内容是其数据类型,则可以缩短语法:

// This:
sequelize.define('User', {
  name: {
    type: DataTypes.STRING
  }
});

// 可以简写为
sequelize.define('User', { 
  name: DataTypes.STRING
});

属性的默认值

默认情况下,Sequelize假定列的默认值为NULL。可以通过向列定义传递特定的defaultValue来更改此行为:

sequelize.define('User', {
  name: {
    type: DataTypes.STRING,
    defaultValue: "John Doe"
  }
});

一些特殊值,比如DataTypes.NOW,也可以接收:

sequelize.define('Foo', {
  bar: {
    type: DataTypes.DATETIME,
    defaultValue: DataTypes.NOW
    // This way, the current date/time will be used to populate this column (at the moment of insertion)
  }
});

数据类型

在模型中定义的每一列都必须有一个数据类型。Sequelize提供了许多内置数据类型。要访问内置数据类型,必须导入数据类型:

// 导入内置的数据类型
const { DataTypes } = require(“sequelize”);

字符串

布尔类型

数字类型

日期类型

UUID

其他类型

列选项

定义列时,除了指定列的类型以及上面提到的allowNull和defaultValue选项外,还有更多可用的选项。下面是一些例子。

const { Model, DataTypes, Deferrable } = require("sequelize");

class Foo extends Model {}

Foo.init({
  // instantiating will automatically set the flag to true if not set
  flag: { 
    type: DataTypes.BOOLEAN, 
    allowNull: false, 
    defaultValue: true
  },

  // default values for dates => current time
  myDate: { 
    type: DataTypes.DATE, 
    defaultValue: DataTypes.NOW
  },

  // setting allowNull to false will add NOT NULL to the column, which means an error will be
  // thrown from the DB when the query is executed if the column is null. If you want to check that a value
  // is not null before querying the DB, look at the validations section below.
  title: { 
    type: DataTypes.STRING, 
    allowNull: false
  },

  // Creating two objects with the same value will throw an error. The unique property can be either a
  // boolean, or a string. If you provide the same string for multiple columns, they will form a
  // composite unique key.
  uniqueOne: { type: DataTypes.STRING,  unique: 'compositeIndex' },
  uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' },

  // The unique property is simply a shorthand to create a unique constraint.
  someUnique: { type: DataTypes.STRING, unique: true },

  // Go on reading for further information about primary keys
  identifier: { type: DataTypes.STRING, primaryKey: true },

  // autoIncrement can be used to create auto_incrementing integer columns
  incrementMe: { type: DataTypes.INTEGER, autoIncrement: true },

  // You can specify a custom column name via the 'field' attribute:
  fieldWithUnderscores: { type: DataTypes.STRING, field: 'field_with_underscores' },

  // It is possible to create foreign keys:
  bar_id: {
    type: DataTypes.INTEGER,

    references: {
      // This is a reference to another model
      model: Bar,

      // This is the column name of the referenced model
      key: 'id',

      // With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type.
      deferrable: Deferrable.INITIALLY_IMMEDIATE
      // Options:
      // - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints
      // - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction
      // - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction
    }
  },

  // Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL
  commentMe: {
    type: DataTypes.INTEGER,
    comment: 'This is a column name that has a comment'
  }
}, {
  sequelize,
  modelName: 'foo',

  // Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options:
  indexes: [{ unique: true, fields: ['someUnique'] }]
});

利用类的优势

Sequelize的Model是ES6的类。您可以非常容易地添加自定义实例方法或者类级别的方法。

class User extends Model {
  // 静态方法
  static classLevelMethod() {
    return 'foo';
  }

  // 实例方法
  instanceLevelMethod() {
    return 'bar';
  }
  //实例方法
  getFullname() {
    return [this.firstname, this.lastname].join(' ');
  }
}

User.init({
  firstname: Sequelize.TEXT,
  lastname: Sequelize.TEXT
}, { sequelize });

// 调用类的静态方法
console.log(User.classLevelMethod()); // 'foo'

// 创建一个User的实例
const user = User.build({ firstname: 'Jane', lastname: 'Doe' });
// 调用实例方法
console.log(user.instanceLevelMethod()); // 'bar'
console.log(user.getFullname()); // 'Jane Doe'

模型定义(Model definition)

设置模型属性/列字段

定义模型和表之间的映射时,使用define方法。
表中每一列必须指明一个数据类型,详细参考数据类型

class Project extends Model {}
Project.init({
  title: Sequelize.STRING,
  description: Sequelize.TEXT
}, { sequelize, modelName: 'project' });


class Task extends Model {}
Task.init({
  title: Sequelize.STRING,
  description: Sequelize.TEXT,
  deadline: Sequelize.DATE
}, { sequelize, modelName: 'task' })

还有更多可选的配置:

class Foo extends Model {}

Foo.init({

 flag: { 
   type: Sequelize.BOOLEAN, 
   allowNull: false, // 该项默认为true,允许为空,即初始化时不传值
   defaultValue: true  // 实例化时将自动将flag设置为true(如果未设置)
 },


 myDate: { 
   type: Sequelize.DATE,  // 日期的默认什 => 当前时间
   defaultValue: Sequelize.NOW
 },

 // 设置 `allowNull` 为 `false` 时,会将 NOT NULL 添加对应列,
 // 这意味着,如果查询为`null`将从数据库抛出错误
 // 如果你想在查询前做非空较验,请参考“验证”章节
 title: { 
   type: Sequelize.STRING, allowNull: false },

 // 使用相同值创建两个对象时将抛出错误
 // `unique` 属性可以是布尔或字符串
 // 如果对多列使用相同的字符串,它们会形成一个复合唯一键
 uniqueOne: { 
   type: Sequelize.STRING,  unique: 'compositeIndex' },
 uniqueTwo: { 
   type: Sequelize.INTEGER, unique: 'compositeIndex' },

 // `unique` 属性只是创建唯一约束的简写
 someUnique: { 
   type: Sequelize.STRING, unique: true },

 // 与在模型选项中创建索引完全相同
 { someUnique: { type: Sequelize.STRING } },
 { indexes: [ { unique: true, fields: [ 'someUnique' ] } ] },

 // 主键设置,后面会继续介绍
 identifier: { type: Sequelize.STRING, primaryKey: true },

 // `autoIncrement` 会对整型列使用 `auto_incrementing` 
 incrementMe: { 
   type: Sequelize.INTEGER, autoIncrement: true },

 // 你可以通过 'field' 属性自定义列名
 fieldWithUnderscores: { t
 ype: Sequelize.STRING, field: 'field_with_underscores' },

 // 可以创建外键
 bar_id: {
   type: Sequelize.INTEGER,

   references: {
     // 这是对另一个模型模型的引用: Bar,

     // 这是引用的模型键的列名: 'id',

     // 这声明何时检查外键约束。仅 PostgreSQL.
     deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE
   }
 },

 // 可以为列添加注释。仅 MySQL、PostgreSQL 和 MSSQL
 commentMe: {
   type: Sequelize.INTEGER,

   comment: 'This is a column name that has a comment'
 }
}, {
  sequelize,
  modelName: 'foo'
});

扩展模型

Sequelize 的Model是ES6类,你可以轻松的定义实例或类级别的方法。

class User extends Model {
  // 创建一个静态方法
  static classLevelMethod() {
    return 'foo';
  }

  // 创建一个实例方法
  instanceLevelMethod() {
    return 'bar';
  }
}


User.init({ firstname: Sequelize.STRING }, { sequelize });

// 使用
User.classLevelMethod();

User.build({ firstname: 'foo', lastname: 'bar' }).getFullname() // 'foo bar'

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)

模型与数据库同步

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

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

一次同步所有模型

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

生产注意事项

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

基本查询示例

Sequelize是基于Promise的。

// 查询所有 users
User.findAll().then(users => {
  console.log("All users:", JSON.stringify(users, null, 4));
});

// 创建一个新 user
User.create({ firstName: "Jane", lastName: "Doe" }).then(jane => {
  console.log("Jane's auto-generated ID:", jane.id);
});

// 删除每个名为 "Jane" 的记录
User.destroy({
  where: {
    firstName: "Jane"
  }
}).then(() => {
  console.log("Done");
});


// 修改每个`lastName`为`null`的记录修改为"Doe"
User.update({ lastName: "Doe" }, {
  where: {
    lastName: null
  }
}).then(() => {
  console.log("Done");
});

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