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

Getter、Setter 與虛擬屬性

Sequelize 允許您為模型的屬性定義自訂的 getter 和 setter。

Sequelize 也允許您指定所謂的虛擬屬性,這些屬性是 Sequelize 模型上的屬性,它們實際上並不存在於底層的 SQL 資料表中,而是由 Sequelize 自動填充。它們對於建立自訂屬性非常有用,例如,這也可以簡化您的程式碼。

Getter

Getter 是在模型定義中為一個欄位定義的 get() 函式

const User = sequelize.define('user', {
// Let's say we wanted to see every username in uppercase, even
// though they are not necessarily uppercase in the database itself
username: {
type: DataTypes.STRING,
get() {
const rawValue = this.getDataValue('username');
return rawValue ? rawValue.toUpperCase() : null;
},
},
});

這個 getter,就像一個標準的 JavaScript getter 一樣,當讀取欄位值時會自動被呼叫

const user = User.build({ username: 'SuperUser123' });
console.log(user.username); // 'SUPERUSER123'
console.log(user.getDataValue('username')); // 'SuperUser123'

請注意,雖然上面記錄了 SUPERUSER123,但實際儲存在資料庫中的值仍然是 SuperUser123。我們使用 this.getDataValue('username') 來取得這個值,並將其轉換為大寫。

如果我們嘗試在 getter 中使用 this.username,我們將會得到一個無限迴圈!這就是為什麼 Sequelize 提供了 getDataValue 方法的原因。

Setter

Setter 是為模型定義中的一個欄位定義的 set() 函式。它接收要設定的值

const User = sequelize.define('user', {
username: DataTypes.STRING,
password: {
type: DataTypes.STRING,
set(value) {
// Storing passwords in plaintext in the database is terrible.
// Hashing the value with an appropriate cryptographic hash function is better.
this.setDataValue('password', hash(value));
},
},
});
const user = User.build({
username: 'someone',
password: 'NotSo§tr0ngP4$SW0RD!',
});
console.log(user.password); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc'
console.log(user.getDataValue('password')); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc'

觀察到 Sequelize 在將資料傳送到資料庫之前,自動呼叫了 setter。資料庫看到的唯一資料是已經雜湊過的值。

如果我們想在計算中涉及模型實例中的另一個欄位,這是可能的而且非常容易!

const User = sequelize.define('user', {
username: DataTypes.STRING,
password: {
type: DataTypes.STRING,
set(value) {
// Storing passwords in plaintext in the database is terrible.
// Hashing the value with an appropriate cryptographic hash function is better.
// Using the username as a salt is better.
this.setDataValue('password', hash(this.username + value));
},
},
});

注意: 上面有關密碼處理的範例,雖然比單純將密碼以純文字儲存好得多,但距離完美的安全性還差得很遠。正確處理密碼是很困難的,這裡的一切都只是為了展示 Sequelize 功能的範例。我們建議諮詢網路安全專家和/或閱讀 OWASP 文件和/或訪問 InfoSec StackExchange

結合 getter 和 setter

Getter 和 setter 可以同時在同一個欄位中定義。

舉例來說,假設我們正在建模一個 Post,它的 content 是無限長度的文字。為了提高記憶體使用率,假設我們想儲存壓縮後的內容。

注意:現代資料庫在這些情況下應該會自動進行一些壓縮。請注意,這只是為了範例。

const { gzipSync, gunzipSync } = require('zlib');

const Post = sequelize.define('post', {
content: {
type: DataTypes.TEXT,
get() {
const storedValue = this.getDataValue('content');
const gzippedBuffer = Buffer.from(storedValue, 'base64');
const unzippedBuffer = gunzipSync(gzippedBuffer);
return unzippedBuffer.toString();
},
set(value) {
const gzippedBuffer = gzipSync(value);
this.setDataValue('content', gzippedBuffer.toString('base64'));
},
},
});

透過上述設定,每當我們嘗試與 Post 模型的 content 欄位互動時,Sequelize 將自動處理自訂的 getter 和 setter。例如

const post = await Post.create({ content: 'Hello everyone!' });

console.log(post.content); // 'Hello everyone!'
// Everything is happening under the hood, so we can even forget that the
// content is actually being stored as a gzipped base64 string!

// However, if we are really curious, we can get the 'raw' data...
console.log(post.getDataValue('content'));
// Output: 'H4sIAAAAAAAACvNIzcnJV0gtSy2qzM9LVQQAUuk9jQ8AAAA='

虛擬欄位

虛擬欄位是 Sequelize 在底層填充的欄位,但實際上它們甚至不存在於資料庫中。

例如,假設我們有一個使用者的 firstNamelastName 屬性。

同樣,這只是為了範例

如果有一個簡單的方法可以直接取得全名就好了!我們可以將 getter 的概念與 Sequelize 為這種情況提供的特殊資料類型結合起來:DataTypes.VIRTUAL

const { DataTypes } = require('sequelize');

const User = sequelize.define('user', {
firstName: DataTypes.TEXT,
lastName: DataTypes.TEXT,
fullName: {
type: DataTypes.VIRTUAL,
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
throw new Error('Do not try to set the `fullName` value!');
},
},
});

VIRTUAL 欄位不會導致資料表中存在一個欄位。換句話說,上面的模型將不會有一個 fullName 欄位。但是,它看起來會有!

const user = await User.create({ firstName: 'John', lastName: 'Doe' });
console.log(user.fullName); // 'John Doe'

已棄用:getterMethodssetterMethods

警告

此功能已在 Sequelize 7 中移除。您應該考慮改為使用虛擬屬性或原生類別 getter 和 setter。

Sequelize 還在模型定義中提供了 getterMethodssetterMethods 選項,以指定看起來像虛擬屬性,但並不完全相同的事物。

範例

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

const User = sequelize.define(
'user',
{
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
},
{
getterMethods: {
fullName() {
return this.firstName + ' ' + this.lastName;
},
},
setterMethods: {
fullName(value) {
// Note: this is just for demonstration.
// See: https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/
const names = value.split(' ');
const firstName = names[0];
const lastName = names.slice(1).join(' ');
this.setDataValue('firstName', firstName);
this.setDataValue('lastName', lastName);
},
},
},
);

(async () => {
await sequelize.sync();
let user = await User.create({ firstName: 'John', lastName: 'Doe' });
console.log(user.fullName); // 'John Doe'
user.fullName = 'Someone Else';
await user.save();
user = await User.findOne();
console.log(user.firstName); // 'Someone'
console.log(user.lastName); // 'Else'
})();