1. 程式人生 > 實用技巧 >sequelize Getters, Setters & Virtuals - 獲取器, 設定器 & 虛擬欄位

sequelize Getters, Setters & Virtuals - 獲取器, 設定器 & 虛擬欄位

  Sequelize 允許你為模型的屬性定義自定義獲取器和設定器.

  Sequelize 還允許你指定所謂的 虛擬屬性(也就是實體類的屬性,不是資料庫裡的欄位),它們是 Sequelize 模型上的屬性,這些屬性在基礎 SQL 表中實際上並不存在,而是由 Sequelize 自動填充.它們對於簡化程式碼非常有用.

一、獲取器

  獲取器是為模型定義中的一列定義的 get() 函式:

const User = sequelize.define('user', {
  // 假設我們想要以大寫形式檢視每個使用者名稱,
  // 即使它們在資料庫本身中不一定是大寫的
  username: {
    type: DataTypes.STRING,
    
get() { const rawValue = this.getDataValue(username); return rawValue ? rawValue.toUpperCase() : null; } } });

  就像標準 JavaScript 獲取器一樣,在讀取欄位值時會自動呼叫此獲取器:

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

  注意,儘管上面記錄為 SUPERUSER123,但是真正儲存在資料庫中的值仍然是 SuperUser123. 我們使用了 this.getDataValue(username) 來獲得該值,並將其轉換為大寫.

  如果我們嘗試在獲取器中使用 this.username,我們將陷入無限迴圈! 這就是為什麼 Sequelize 提供 getDataValue 方法的原因:獲取存在資料庫的真實的值。

二、設定器

  設定器是為模型定義中的一列定義的 set() 函式. 它接收要設定的值:

const User = sequelize.define('user'
, { username: DataTypes.STRING, password: { type: DataTypes.STRING, set(value) { // 在資料庫中以明文形式儲存密碼是很糟糕的. // 使用適當的雜湊函式來加密雜湊值更好. 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 在將資料傳送到資料庫之前自動呼叫了設定器. 資料庫得到的唯一資料是已經雜湊過的值.

  如果我們想將模型例項中的另一個欄位包含在計算中,那也是可以的,而且非常容易!

const User = sequelize.define('user', {
  username: DataTypes.STRING,
  password: {
    type: DataTypes.STRING,
    set(value) {
      // 在資料庫中以明文形式儲存密碼是很糟糕的.
      // 使用適當的雜湊函式來加密雜湊值更好.
      // 使用使用者名稱作為鹽更好.
      this.setDataValue('password', hash(this.username + value));
    }
  }
});

  注意: 上面涉及密碼處理的示例儘管比單純以明文形式儲存密碼要好得多,但遠非完美的安全性. 正確處理密碼很困難,這裡的所有內容只是為了舉例說明 Sequelize 功能. 我們建議讓網路安全專家閱讀 OWASP 文件或者訪問 InfoSec StackExchange.

三、組合獲取器和設定器

  獲取器和設定器都可以在同一欄位中定義.

  舉個例子,假設我們正在建一個 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 都會自動處理自定義的獲取器和設定器. 例如:

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

console.log(post.content); // 'Hello everyone!'
// 一切都在幕後進行,所以我們甚至都可以忘記內容實際上是
// 作為 gzip 壓縮的 base64 字串儲存的!

// 但是,如果我們真的很好奇,我們可以獲取 'raw' 資料...
console.log(post.getDataValue('content'));
// Output: 'H4sIAAAAAAAACvNIzcnJV0gtSy2qzM9LVQQAUuk9jQ8AAAA='

四、虛擬欄位

  虛擬欄位是 Sequelize 在後臺填充的欄位,但實際上它們不存在於資料庫中.(也就是說在實體類裡的,不存在資料庫裡的)

  例如,假設我們有一個 User 的 firstNamelastName 屬性.\

  同樣,這僅是為了示例.

  如果有一種簡單的方法能直接獲取 全名 那會非常好! 我們可以將 getters 的概念與 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('不要嘗試設定 `fullName` 的值!');
    }
  }
});

  VIRTUAL 欄位不會導致資料表也存在此列. 換句話說,上面的模型雖然沒有 fullName 列. 但是它似乎存在著!

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