Skip to main content
Version: v6 - stable

获取器、设置器和虚拟值

Sequelize 允许你为模型的属性定义自定义 getter 和 setter。

¥Sequelize allows you to define custom getters and setters for the attributes of your models.

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

¥Sequelize also allows you to specify the so-called virtual attributes, which are attributes on the Sequelize Model that doesn't really exist in the underlying SQL table, but instead are populated automatically by Sequelize. They are very useful to create custom attributes which also could simplify your code, for example.

获取器

¥Getters

getter 是为模型定义中的一列定义的 get() 函数:

¥A getter is a get() function defined for one column in the model definition:

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 一样,在读取字段值时自动调用:

¥This getter, just like a standard JavaScript getter, is called automatically when the field value is read:

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

需要注意的是,虽然上面记录了 SUPERUSER123,但是真正存储在数据库中的值仍然是 SuperUser123。我们使用 this.getDataValue('username') 来获取这个值,并将其转换为大写。

¥Note that, although SUPERUSER123 was logged above, the value truly stored in the database is still SuperUser123. We used this.getDataValue('username') to obtain this value, and converted it to uppercase.

如果我们尝试在 getter 中使用 this.username,我们就会陷入无限循环!这就是 Sequelize 提供 getDataValue 方法的原因。

¥Had we tried to use this.username in the getter instead, we would have gotten an infinite loop! This is why Sequelize provides the getDataValue method.

设置器

¥Setters

setter 是为模型定义中的一列定义的 set() 函数。它接收正在设置的值:

¥A setter is a set() function defined for one column in the model definition. It receives the value being 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。数据库看到的唯一数据是已经散列的值。

¥Observe that Sequelize called the setter automatically, before even sending data to the database. The only data the database ever saw was the already hashed value.

如果我们想在计算中涉及模型实例中的另一个字段,这是可能的,而且非常简单!

¥If we wanted to involve another field from our model instance in the computation, that is possible and very easy!

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 文件和/或访问 信息安全堆栈交换

¥Note: The above examples involving password handling, although much better than simply storing the password in plaintext, are far from perfect security. Handling passwords properly is hard, everything here is just for the sake of an example to show Sequelize functionality. We suggest involving a cybersecurity expert and/or reading OWASP documents and/or visiting the InfoSec StackExchange.

组合获取器和设置器

¥Combining getters and setters

getter 和 setter 可以在同一字段中定义。

¥Getters and setters can be both defined in the same field.

举个例子,假设我们正在建模 Post,其 content 是无限长度的文本。为了提高内存使用率,假设我们要存储内容的 gzip 压缩版本。

¥For the sake of an example, let's say we are modeling a Post, whose content is a text of unlimited length. To improve memory usage, let's say we want to store a gzipped version of the content.

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

¥Note: modern databases should do some compression automatically in these cases. Please note that this is just for the sake of an example.

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。例如:

¥With the above setup, whenever we try to interact with the content field of our Post model, Sequelize will automatically handle the custom getter and setter. For example:

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='

虚拟字段

¥Virtual fields

虚拟字段是 Sequelize 在后台填充的字段,但实际上它们甚至不存在于数据库中。

¥Virtual fields are fields that Sequelize populates under the hood, but in reality they don't even exist in the database.

例如,假设用户有 firstNamelastName 属性。

¥For example, let's say we have the firstName and lastName attributes for a User.

再说一次,这是 仅供参考

¥Again, this is only for the sake of an example.

如果有一个简单的方法可以直接获取全名就好了!我们可以将 getters 的思想与 Sequelize 为这种情况提供的特殊数据类型结合起来:DataTypes.VIRTUAL

¥It would be nice to have a simple way to obtain the full name directly! We can combine the idea of getters with the special data type Sequelize provides for this kind of situation: 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 列。然而,它似乎有它!

¥The VIRTUAL field does not cause a column in the table to exist. In other words, the model above will not have a fullName column. However, it will appear to have it!

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

已弃用:getterMethodssetterMethods

¥Deprecated: getterMethods and setterMethods

警告

此功能已在 Sequelize 7 中删除。你应该考虑使用 VIRTUAL 属性或原生类 getter 和 setter。

¥This feature has been removed in Sequelize 7. You should consider using either VIRTUAL attributes or native class getter & setters instead.

Sequelize 还在模型定义中提供 getterMethodssetterMethods 选项,以指定与虚拟属性类似但不完全相同的内容。

¥Sequelize also provides the getterMethods and setterMethods options in the model definition to specify things that look like, but aren't exactly the same as, virtual attributes.

示例:

¥Example:

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