跳至主要內容
版本:v6 - 穩定版

模型基礎

在本教學中,您將學習 Sequelize 中的模型是什麼以及如何使用它們。

概念

模型是 Sequelize 的本質。模型是代表資料庫中表格的抽象概念。在 Sequelize 中,它是一個擴展 Model 的類別。

模型會告訴 Sequelize 關於它所代表的實體的幾個事項,例如資料庫中的表格名稱以及它擁有哪些欄位(以及它們的資料類型)。

Sequelize 中的模型有一個名稱。此名稱不一定要與它在資料庫中代表的表格名稱相同。通常,模型具有單數名稱(例如 User),而表格具有複數名稱(例如 Users),儘管這是完全可設定的。

模型定義

模型可以在 Sequelize 中以兩種等效的方式定義

定義模型後,它可以在 sequelize.models 中以其模型名稱取得。

為了透過範例學習,我們將考慮建立一個模型來代表使用者,該使用者具有 firstNamelastName。我們希望我們的模型稱為 User,而它代表的表格在資料庫中稱為 Users

下面顯示了定義此模型的兩種方式。定義後,我們可以使用 sequelize.models.User 存取我們的模型。

使用 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

擴展 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,因此這兩種方法基本上是等效的。

使用公有類別欄位的注意事項

新增一個與模型的屬性之一同名的 公有類別欄位 將會造成問題。Sequelize 會為每個透過 Model.init 定義的屬性新增一個 getter 和 setter。新增公有類別欄位將會遮蔽這些 getter 和 setter,阻擋存取模型的實際資料。

// 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 關鍵字新增類型資訊,而無需新增實際的公有類別欄位

// 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

表格名稱推斷

請注意,在上述兩種方法中,從未明確定義表格名稱 (Users)。但是,提供了模型名稱 (User)。

預設情況下,當未提供表格名稱時,Sequelize 會自動將模型名稱複數化,並將其用作表格名稱。此複數化是透過一個名為 inflection 的函式庫在底層完成的,以便正確計算不規則的複數形式(例如 person -> people)。

當然,此行為很容易設定。

強制表格名稱等於模型名稱

您可以使用 freezeTableName: true 選項來停止 Sequelize 執行的自動複數化。這樣,Sequelize 將推斷表格名稱等於模型名稱,而不會進行任何修改

sequelize.define(
'User',
{
// ... (attributes)
},
{
freezeTableName: true,
},
);

上面的範例將建立一個名為 User 的模型,該模型指向也名為 User 的表格。

此行為也可以在建立時針對 sequelize 實例全域定義

const sequelize = new Sequelize('sqlite::memory:', {
define: {
freezeTableName: true,
},
});

這樣,所有表格都將使用與模型名稱相同的名稱。

直接提供表格名稱

您也可以直接告訴 Sequelize 表格的名稱

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

模型同步

當您定義模型時,您會告訴 Sequelize 關於其在資料庫中表格的一些事項。但是,如果表格實際上甚至不存在於資料庫中會怎樣?如果它存在,但具有不同的欄位、較少的欄位或任何其他差異呢?

這就是模型同步的用武之地。可以透過呼叫 model.sync(options)(一個傳回 Promise 的非同步函式)來將模型與資料庫同步。透過此呼叫,Sequelize 將自動對資料庫執行 SQL 查詢。請注意,這僅變更資料庫中的表格,而不變更 JavaScript 端中的模型。

  • User.sync() - 這會在表格不存在時建立表格(如果表格已存在,則不會執行任何操作)
  • User.sync({ force: true }) - 這會建立表格,如果表格已存在,則會先捨棄它
  • User.sync({ alter: true }) - 這會檢查資料庫中表格的目前狀態(它有哪些欄位、它們的資料類型是什麼等等),然後在表格中執行必要的變更,使其與模型相符。

範例

await User.sync({ force: true });
console.log('The table for the User model was just (re)created!');

一次同步所有模型

您可以使用 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!');

資料庫安全檢查

如上所示,syncdrop 作業具有破壞性。Sequelize 接受 match 選項作為額外的安全檢查,它接收 RegExp

// This will run .sync() only if database name ends with '_test'
sequelize.sync({ force: true, match: /_test$/ });

生產環境中的同步

如上所示,sync({ force: true })sync({ alter: true }) 可能是破壞性操作。因此,不建議將它們用於生產級軟體。相反,應該使用 遷移 的進階概念,在 Sequelize CLI 的協助下完成同步。

時間戳記

預設情況下,Sequelize 會使用資料類型 DataTypes.DATE 自動將欄位 createdAtupdatedAt 新增至每個模型。這些欄位也會自動管理 - 每當您使用 Sequelize 建立或更新內容時,這些欄位都會正確設定。createdAt 欄位將包含代表建立時間的的時間戳記,而 updatedAt 將包含最新更新的時間戳記。

注意:這是在 Sequelize 層級完成的(即不是使用SQL 觸發器完成的)。這表示直接 SQL 查詢(例如,以任何其他方式在沒有 Sequelize 的情況下執行的查詢)將不會導致這些欄位自動更新。

可以使用 timestamps: false 選項針對模型停用此行為

sequelize.define(
'User',
{
// ... (attributes)
},
{
timestamps: false,
},
);

也可以僅啟用 createdAt/updatedAt 中的一個,並為這些欄位提供自訂名稱

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',
},
);

欄位宣告簡寫語法

如果關於欄位唯一定義的是其資料類型,則可以縮短語法

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

// Can be simplified to:
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 提供了許多內建的資料型別。要存取內建的資料型別,您必須匯入 DataTypes

const { DataTypes } = require('sequelize'); // Import the built-in data types

字串

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.

布林值

DataTypes.BOOLEAN; // TINYINT(1)

數字

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

在 MySQL 和 MariaDB 中,資料型別 INTEGERBIGINTFLOATDOUBLE 可以設定為無號或零填充 (或兩者),如下所示:

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

日期

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.UUIDV1DataTypes.UUIDV4 作為預設值即可。

{
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4 // Or DataTypes.UUIDV1
}

其他

還有其他資料型別,在獨立的指南中涵蓋。

欄位選項

在定義欄位時,除了指定欄位的 type,以及上面提到的 allowNulldefaultValue 選項之外,還有許多其他選項可以使用。以下是一些範例。

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 模型是 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'
const user = User.build({ firstname: 'Jane', lastname: 'Doe' });
console.log(user.instanceLevelMethod()); // 'bar'
console.log(user.getFullname()); // 'Jane Doe'