模型基礎
在本教學中,您將學習 Sequelize 中的模型是什麼以及如何使用它們。
概念
模型是 Sequelize 的本質。模型是代表資料庫中表格的抽象概念。在 Sequelize 中,它是一個擴展 Model 的類別。
模型會告訴 Sequelize 關於它所代表的實體的幾個事項,例如資料庫中的表格名稱以及它擁有哪些欄位(以及它們的資料類型)。
Sequelize 中的模型有一個名稱。此名稱不一定要與它在資料庫中代表的表格名稱相同。通常,模型具有單數名稱(例如 User
),而表格具有複數名稱(例如 Users
),儘管這是完全可設定的。
模型定義
模型可以在 Sequelize 中以兩種等效的方式定義
定義模型後,它可以在 sequelize.models
中以其模型名稱取得。
為了透過範例學習,我們將考慮建立一個模型來代表使用者,該使用者具有 firstName
和 lastName
。我們希望我們的模型稱為 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!');
資料庫安全檢查
如上所示,sync
和 drop
作業具有破壞性。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
自動將欄位 createdAt
和 updatedAt
新增至每個模型。這些欄位也會自動管理 - 每當您使用 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 中,資料型別 INTEGER
、BIGINT
、FLOAT
和 DOUBLE
可以設定為無號或零填充 (或兩者),如下所示:
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.UUIDV1
或 DataTypes.UUIDV4
作為預設值即可。
{
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4 // Or DataTypes.UUIDV1
}
其他
還有其他資料型別,在獨立的指南中涵蓋。
欄位選項
在定義欄位時,除了指定欄位的 type
,以及上面提到的 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 模型是 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'