模型基础
在本教程中,你将了解 Sequelize 中有哪些模型以及如何使用它们。
¥In this tutorial you will learn what models are in Sequelize and how to use them.
概念
¥Concept
模型是 Sequelize 的本质。模型是表示数据库中的表的抽象。在 Sequelize 中,它是一个扩展 模型 的类。
¥Models are the essence of Sequelize. A model is an abstraction that represents a table in your database. In Sequelize, it is a class that extends Model.
该模型告诉 Sequelize 关于它所代表的实体的一些信息,例如数据库中表的名称以及它具有哪些列(及其数据类型)。
¥The model tells Sequelize several things about the entity it represents, such as the name of the table in the database and which columns it has (and their data types).
Sequelize 中的模型有一个名称。该名称不必与其在数据库中表示的表的名称相同。通常,模型具有单数名称(例如 User
),而表具有复数名称(例如 Users
),尽管这是完全可配置的。
¥A model in Sequelize has a name. This name does not have to be the same name of the table it represents in the database. Usually, models have singular names (such as User
) while tables have pluralized names (such as Users
), although this is fully configurable.
模型定义
¥Model Definition
在 Sequelize 中可以用两种等效的方式定义模型:
¥Models can be defined in two equivalent ways in Sequelize:
-
扩展 模型 并调用
init(attributes, options)
¥Extending Model and calling
init(attributes, options)
定义模型后,可通过其模型名称在 sequelize.models
内使用该模型。
¥After a model is defined, it is available within sequelize.models
by its model name.
为了通过示例进行学习,我们将考虑创建一个模型来表示用户,其中有 firstName
和 lastName
。我们希望我们的模型被称为 User
,它代表的表在数据库中被称为 Users
。
¥To learn with an example, we will consider that we want to create a model to represent users, which have a firstName
and a lastName
. We want our model to be called User
, and the table it represents is called Users
in the database.
定义该模型的两种方法如下所示。定义完成后,我们可以使用 sequelize.models.User
访问我们的模型。
¥Both ways to define this model are shown below. After being defined, we can access our model with sequelize.models.User
.
使用 sequelize.define
:
¥Using sequelize.define
:
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define(
'User',
{
// Model attributes are defined here
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: {
type: DataTypes.STRING,
// allowNull defaults to true
},
},
{
// Other model options go here
},
);
// `sequelize.define` also returns the model
console.log(User === sequelize.models.User); // true
扩展 模型
¥Extending Model
const { Sequelize, DataTypes, Model } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
class User extends Model {}
User.init(
{
// Model attributes are defined here
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
,因此两种方法本质上是等效的。
¥Internally, sequelize.define
calls Model.init
, so both approaches are essentially equivalent.
公共类字段的警告
¥Caveat with Public Class Fields
添加与模型属性之一同名的 公开的类字段 会导致问题。Sequelize 为通过 Model.init
定义的每个属性添加一个 getter 和一个 setter。添加公共类字段将隐藏这些 getter 和 setter,从而阻止对模型实际数据的访问。
¥Adding a Public Class Field with the same name as one of the model's attribute is going to cause issues.
Sequelize adds a getter & a setter for each attribute defined through Model.init
.
Adding a Public Class Field will shadow those getter and setters, blocking access to the model's actual data.
// Invalid
class User extends Model {
id; // this field will shadow sequelize's getter & setter. It should be removed.
otherPublicField; // this field does not shadow anything. It is fine.
}
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
},
{ sequelize },
);
const user = new User({ id: 1 });
user.id; // undefined
// Valid
class User extends Model {
otherPublicField;
}
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
},
{ sequelize },
);
const user = new User({ id: 1 });
user.id; // 1
在 TypeScript 中,你可以使用 declare
关键字添加类型信息,而无需添加实际的公共类字段:
¥In TypeScript, you can add typing information without adding an actual public class field by using the declare
keyword:
// Valid
class User extends Model {
declare id: number; // this is ok! The 'declare' keyword ensures this field will not be emitted by TypeScript.
}
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
},
{ sequelize },
);
const user = new User({ id: 1 });
user.id; // 1
表名推断
¥Table name inference
请注意,在上述两种方法中,从未显式定义表名 (Users
)。但是,给出了型号名称(User
)。
¥Observe that, in both methods above, the table name (Users
) was never explicitly defined. However, the model name was given (User
).
默认情况下,当未给出表名称时,Sequelize 会自动将模型名称复数并将其用作表名称。这种复数化是由名为 inflection 的库在幕后完成的,因此可以正确计算不规则复数(例如 person -> people
)。
¥By default, when the table name is not given, Sequelize automatically pluralizes the model name and uses that as the table name. This pluralization is done under the hood by a library called inflection, so that irregular plurals (such as person -> people
) are computed correctly.
当然,这种行为很容易配置。
¥Of course, this behavior is easily configurable.
强制表名与模型名相同
¥Enforcing the table name to be equal to the model name
你可以使用 freezeTableName: true
选项停止 Sequelize 执行的自动复数化。这样,Sequelize 将推断表名称等于模型名称,无需任何修改:
¥You can stop the auto-pluralization performed by Sequelize using the freezeTableName: true
option. This way, Sequelize will infer the table name to be equal to the model name, without any modifications:
sequelize.define(
'User',
{
// ... (attributes)
},
{
freezeTableName: true,
},
);
上面的示例将创建一个名为 User
的模型,该模型指向一个也名为 User
的表。
¥The example above will create a model named User
pointing to a table also named User
.
此行为也可以在创建时为 sequelize 实例全局定义:
¥This behavior can also be defined globally for the sequelize instance, when it is created:
const sequelize = new Sequelize('sqlite::memory:', {
define: {
freezeTableName: true,
},
});
这样,所有表都将使用与模型名称相同的名称。
¥This way, all tables will use the same name as the model name.
直接提供表名
¥Providing the table name directly
你也可以直接告诉 Sequelize 表的名称:
¥You can simply tell Sequelize the name of the table directly as well:
sequelize.define(
'User',
{
// ... (attributes)
},
{
tableName: 'Employees',
},
);
模型同步
¥Model synchronization
当你定义模型时,你是在告诉 Sequelize 有关其数据库中的表的一些信息。但是,如果该表实际上不存在于数据库中怎么办?如果它存在,但有不同的列、更少的列或任何其他差异怎么办?
¥When you define a model, you're telling Sequelize a few things about its table in the database. However, what if the table actually doesn't even exist in the database? What if it exists, but it has different columns, less columns, or any other difference?
这就是模型同步的用武之地。模型可以通过调用异步函数 model.sync(options)
(返回 Promise)来与数据库同步。通过此调用,Sequelize 将自动对数据库执行 SQL 查询。请注意,这仅更改数据库中的表,而不更改 JavaScript 端的模型。
¥This is where model synchronization comes in. A model can be synchronized with the database by calling model.sync(options)
, an asynchronous function (that returns a Promise). With this call, Sequelize will automatically perform an SQL query to the database. Note that this changes only the table in the database, not the model in the JavaScript side.
-
User.sync()
- 如果表不存在,则创建该表(如果已经存在,则不执行任何操作)¥
User.sync()
- This creates the table if it doesn't exist (and does nothing if it already exists) -
User.sync({ force: true })
- 这将创建表,如果它已经存在则首先删除它¥
User.sync({ force: true })
- This creates the table, dropping it first if it already existed -
User.sync({ alter: true })
- 这会检查数据库中表的当前状态(它有哪些列,它们的数据类型是什么等),然后在表中执行必要的更改以使其与模型匹配。¥
User.sync({ alter: true })
- This checks what is the current state of the table in the database (which columns it has, what are their data types, etc), and then performs the necessary changes in the table to make it match the model.
示例:
¥Example:
await User.sync({ force: true });
console.log('The table for the User model was just (re)created!');
一次同步所有模型
¥Synchronizing all models at once
你可以使用 sequelize.sync()
自动同步所有模型。示例:
¥You can use sequelize.sync()
to automatically synchronize all models. Example:
await sequelize.sync({ force: true });
console.log('All models were synchronized successfully.');
删除表
¥Dropping tables
要删除与模型相关的表:
¥To drop the table related to a model:
await User.drop();
console.log('User table dropped!');
删除所有表:
¥To drop all tables:
await sequelize.drop();
console.log('All tables dropped!');
数据库安全检查
¥Database safety check
如上所示,sync
和 drop
操作是破坏性的。Sequelize 接受 match
选项作为附加安全检查,该选项接收 RegExp:
¥As shown above, the sync
and drop
operations are destructive. Sequelize accepts a match
option as an additional safety check, which receives a RegExp:
// This will run .sync() only if database name ends with '_test'
sequelize.sync({ force: true, match: /_test$/ });
生产同步
¥Synchronization in production
如上所示,sync({ force: true })
和 sync({ alter: true })
可以是破坏性操作。因此,不建议将它们用于生产级软件。相反,应该借助 迁移 的高级理念,借助 Sequelize CLI 来完成同步。
¥As shown above, sync({ force: true })
and sync({ alter: true })
can be destructive operations. Therefore, they are not recommended for production-level software. Instead, synchronization should be done with the advanced concept of Migrations, with the help of the Sequelize CLI.
时间戳
¥Timestamps
默认情况下,Sequelize 使用数据类型 DataTypes.DATE
自动将字段 createdAt
和 updatedAt
添加到每个模型。这些字段也会自动管理 - 每当你使用 Sequelize 创建或更新某些内容时,这些字段都会被正确设置。createdAt
字段将包含代表创建时刻的时间戳,updatedAt
将包含最新更新的时间戳。
¥By default, Sequelize automatically adds the fields createdAt
and updatedAt
to every model, using the data type DataTypes.DATE
. Those fields are automatically managed as well - whenever you use Sequelize to create or update something, those fields will be set correctly. The createdAt
field will contain the timestamp representing the moment of creation, and the updatedAt
will contain the timestamp of the latest update.
注意:这是在 Sequelize 级别完成的(即不使用 SQL 触发器完成)。这意味着直接 SQL 查询(例如通过任何其他方式在没有 Sequelize 的情况下执行的查询)不会导致这些字段自动更新。
¥Note: This is done in the Sequelize level (i.e. not done with SQL triggers). This means that direct SQL queries (for example queries performed without Sequelize by any other means) will not cause these fields to be updated automatically.
对于具有 timestamps: false
选项的模型,可以禁用此行为:
¥This behavior can be disabled for a model with the timestamps: false
option:
sequelize.define(
'User',
{
// ... (attributes)
},
{
timestamps: false,
},
);
也可以仅启用 createdAt
/updatedAt
之一,并为这些列提供自定义名称:
¥It is also possible to enable only one of createdAt
/updatedAt
, and to provide a custom name for these columns:
class Foo extends Model {}
Foo.init(
{
/* attributes */
},
{
sequelize,
// don't forget to enable timestamps!
timestamps: true,
// I don't want createdAt
createdAt: false,
// I want updatedAt to actually be called updateTimestamp
updatedAt: 'updateTimestamp',
},
);
列声明简写语法
¥Column declaration shorthand syntax
如果有关列的唯一指定内容是其数据类型,则可以缩短语法:
¥If the only thing being specified about a column is its data type, the syntax can be shortened:
// This:
sequelize.define('User', {
name: {
type: DataTypes.STRING,
},
});
// Can be simplified to:
sequelize.define('User', { name: DataTypes.STRING });
默认值
¥Default Values
默认情况下,Sequelize 假定列的默认值为 NULL
。可以通过将特定的 defaultValue
传递给列定义来更改此行为:
¥By default, Sequelize assumes that the default value of a column is NULL
. This behavior can be changed by passing a specific defaultValue
to the column definition:
sequelize.define('User', {
name: {
type: DataTypes.STRING,
defaultValue: 'John Doe',
},
});
一些特殊值,例如 DataTypes.NOW
,也被接受:
¥Some special values, such as DataTypes.NOW
, are also accepted:
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)
},
});
数据类型
¥Data Types
你在模型中定义的每一列都必须具有数据类型。Sequelize 提供 很多内置数据类型。要访问内置数据类型,你必须导入 DataTypes
:
¥Every column you define in your model must have a data type. Sequelize provides a lot of built-in data types. To access a built-in data type, you must import DataTypes
:
const { DataTypes } = require('sequelize'); // Import the built-in data types
字符串
¥Strings
DataTypes.STRING; // VARCHAR(255)
DataTypes.STRING(1234); // VARCHAR(1234)
DataTypes.STRING.BINARY; // VARCHAR BINARY
DataTypes.TEXT; // TEXT
DataTypes.TEXT('tiny'); // TINYTEXT
DataTypes.CITEXT; // CITEXT PostgreSQL and SQLite only.
DataTypes.TSVECTOR; // TSVECTOR PostgreSQL only.
布尔值
¥Boolean
DataTypes.BOOLEAN; // TINYINT(1)
数字
¥Numbers
DataTypes.INTEGER; // INTEGER
DataTypes.BIGINT; // BIGINT
DataTypes.BIGINT(11); // BIGINT(11)
DataTypes.FLOAT; // FLOAT
DataTypes.FLOAT(11); // FLOAT(11)
DataTypes.FLOAT(11, 10); // FLOAT(11,10)
DataTypes.REAL; // REAL PostgreSQL only.
DataTypes.REAL(11); // REAL(11) PostgreSQL only.
DataTypes.REAL(11, 12); // REAL(11,12) PostgreSQL only.
DataTypes.DOUBLE; // DOUBLE
DataTypes.DOUBLE(11); // DOUBLE(11)
DataTypes.DOUBLE(11, 10); // DOUBLE(11,10)
DataTypes.DECIMAL; // DECIMAL
DataTypes.DECIMAL(10, 2); // DECIMAL(10,2)
无符号和填零整数 - 仅限 MySQL/MariaDB
¥Unsigned & Zerofill integers - MySQL/MariaDB only
在 MySQL 和 MariaDB 中,数据类型 INTEGER
、BIGINT
、FLOAT
和 DOUBLE
可以设置为无符号或零填充(或两者),如下所示:
¥In MySQL and MariaDB, the data types INTEGER
, BIGINT
, FLOAT
and DOUBLE
can be set as unsigned or zerofill (or both), as follows:
DataTypes.INTEGER.UNSIGNED;
DataTypes.INTEGER.ZEROFILL;
DataTypes.INTEGER.UNSIGNED.ZEROFILL;
// You can also specify the size i.e. INTEGER(10) instead of simply INTEGER
// Same for BIGINT, FLOAT and DOUBLE
日期
¥Dates
DataTypes.DATE; // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
DataTypes.DATE(6); // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision
DataTypes.DATEONLY; // DATE without time
UUID
对于 UUID,请使用 DataTypes.UUID
。它成为 PostgreSQL 和 SQLite 的 UUID
数据类型,以及 MySQL 的 CHAR(36)
数据类型。Sequelize 可以自动为这些字段生成 UUID,只需使用 DataTypes.UUIDV1
或 DataTypes.UUIDV4
作为默认值:
¥For UUIDs, use DataTypes.UUID
. It becomes the UUID
data type for PostgreSQL and SQLite, and CHAR(36)
for MySQL. Sequelize can generate UUIDs automatically for these fields, simply use DataTypes.UUIDV1
or DataTypes.UUIDV4
as the default value:
{
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4 // Or DataTypes.UUIDV1
}
其他
¥Others
还有其他数据类型,在 单独的指南。
¥There are other data types, covered in a separate guide.
列选项
¥Column Options
定义列时,除了指定列的 type
以及上面提到的 allowNull
和 defaultValue
选项外,还有很多选项可以使用。下面是一些示例。
¥When defining a column, apart from specifying the type
of the column, and the allowNull
and defaultValue
options mentioned above, there are a lot more options that can be used. Some examples are below.
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'] }],
},
);
利用模型作为类的优势
¥Taking advantage of Models being classes
Sequelize 型号为 ES6 类。你可以非常轻松地添加自定义实例或类级别方法。
¥The Sequelize models are ES6 classes. You can very easily add custom instance or class level methods.
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'
const user = User.build({ firstname: 'Jane', lastname: 'Doe' });
console.log(user.instanceLevelMethod()); // 'bar'
console.log(user.getFullname()); // 'Jane Doe'