跳到主要内容
版本:v6 - 稳定版

获取器、设置器和虚拟属性

Sequelize 允许您为模型的属性定义自定义获取器和设置器。

Sequelize 还允许您指定所谓的虚拟属性,这些属性是 Sequelize 模型上的属性,实际上并不存在于底层的 SQL 表中,而是由 Sequelize 自动填充。例如,它们非常有用,可以创建自定义属性,还可以简化您的代码。

获取器

获取器是在模型定义中为一列定义的 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;
},
},
});

此获取器与标准的 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) {
// 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 在将数据发送到数据库之前就自动调用了设置器。数据库唯一看到的只是已经散列的值。

如果我们想将模型实例中的另一个字段包含在计算中,这既有可能又很容易!

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

组合获取器和设置器

获取器和设置器可以在同一字段中定义。

为了举例说明,假设我们正在建模一个 Post,它的 content 是一个无限长度的文本。为了提高内存使用率,假设我们想存储内容的 gzip 版本。

注意:在这些情况下,现代数据库应该自动进行一些压缩。请注意,这只是为了举例说明。

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!'
// 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 中移除。您应该考虑使用 VIRTUAL 属性或本机类获取器和设置器。

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