sequelize中model的配置:model definition

本文介绍model的options选项

model的一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Foo extends Model {}
Foo.init({
// 不许为空,默认值为true
flag: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true },

// 默认值为当前时间
myDate: { type: Sequelize.DATE, defaultValue: Sequelize.NOW },

// 设置成不许为空,执行插入操作的时候如果是值null,DB将会抛出异常。
// 如果想在插入操作前检查是否为空,参照下面的验证一节
title: { type: Sequelize.STRING, allowNull: false },

// 创建两个相同的值将抛出错误。唯一属性可以是boolean类型或字符串。
// 如果在多个字段提供了相同的字符串,将创建一个复合唯一键。
uniqueOne: { type: Sequelize.STRING, unique: 'compositeIndex' },
uniqueTwo: { type: Sequelize.INTEGER, unique: 'compositeIndex' },

// unique属性可以简单快速的创建一个唯一约束
someUnique: { type: Sequelize.STRING, unique: true },

// 下面的方法跟直接创建是一样的
{ someUnique: { type: Sequelize.STRING } },
{ indexes: [ { unique: true, fields: [ 'someUnique' ] } ] },

// 主键
identifier: { type: Sequelize.STRING, primaryKey: true },

// 自增
incrementMe: { type: Sequelize.INTEGER, autoIncrement: true },

// 通过field属性指定DB中的列
fieldWithUnderscores: { type: Sequelize.STRING, field: 'field_with_underscores' },

// 创建外键
bar_id: {
type: Sequelize.INTEGER,

references: {
// 另一个model的引用
model: Bar,

// 另一个model引用的列名
key: 'id',

// 指定何时检查外键约束 PostgreSQL only.
deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE
}
},

// 加列注释 for MySQL, PostgreSQL and MSSQL only
commentMe: {
type: Sequelize.INTEGER,

comment: 'This is a column name that has a comment'
}
}, {
sequelize,
modelName: 'foo'
});

时间戳(timestamps)

默认情况下自动创建createdAtupdatedAt

详见下面的配置。

Getter & Setter

可以有两种方法定义getter&setter:

  • 属性的定义中
  • model的options中

如果两个地方都定义了,则属性中的优先

属性定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Employee extends Model {}
Employee.init({
name: {
type: Sequelize.STRING,
allowNull: false,
get() {
const title = this.getDataValue('title');
// 'this' allows you to access attributes of the instance
return this.getDataValue('name') + ' (' + title + ')';
},
},
title: {
type: Sequelize.STRING,
allowNull: false,
set(val) {
this.setDataValue('title', val.toUpperCase());
}
}
}, { sequelize, modelName: 'employee' });

Employee
.create({ name: 'John Doe', title: 'senior engineer' })
.then(employee => {
console.log(employee.get('name')); // John Doe (SENIOR ENGINEER)
console.log(employee.get('title')); // SENIOR ENGINEER
})

model的options中定义:

下面是在model的options中定义getter&setter的例子

fullName getter是一个你如何定义数据库中并不存在的列的虚假属性(pseudo properties)的例子。事实上,虚假属性可以在两个地方定义:model getters和virtual datatype。virtual datatype可以有验证,而虚拟属性不行。注意在fullName的getter方法中引用的this.firstnamethis.lastname将会触发各自的getter方法。如果你不想触发,可以使用getDataValue()方法来获取原始值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Foo extends Model {
get fullName() {
return this.firstname + ' ' + this.lastname;
}

set fullName(value) {
const names = value.split(' ');
this.setDataValue('firstname', names.slice(0, -1).join(' '));
this.setDataValue('lastname', names.slice(-1).join(' '));
}
}
Foo.init({
firstname: Sequelize.STRING,
lastname: Sequelize.STRING
}, {
sequelize,
modelName: 'foo'
});

// Or with `sequelize.define`
sequelize.define('Foo', {
firstname: Sequelize.STRING,
lastname: Sequelize.STRING
}, {
getterMethods: {
fullName() {
return this.firstname + ' ' + this.lastname;
}
},

setterMethods: {
fullName(value) {
const names = value.split(' ');

this.setDataValue('firstname', names.slice(0, -1).join(' '));
this.setDataValue('lastname', names.slice(-1).join(' '));
}
}
});

验证(validation)

Model validations允许为每一个attribute指定格式/内容/继承验证。

Validations会在 create, updatesave时自动触发,也可以在一个实例里手动调用validate()触发

属性验证器

可以自定义验证器或者使用validator.js内建的一些验证器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class ValidateMe extends Model {}
ValidateMe.init({
bar: {
type: Sequelize.STRING,
validate: {
is: ["^[a-z]+$",'i'], // will only allow letters
is: /^[a-z]+$/i, // same as the previous example using real RegExp
not: ["[a-z]",'i'], // will not allow letters
isEmail: true, // checks for email format (foo@bar.com)
isUrl: true, // checks for url format (http://foo.com)
isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format
isIPv4: true, // checks for IPv4 (129.89.23.1)
isIPv6: true, // checks for IPv6 format
isAlpha: true, // will only allow letters
isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail
isNumeric: true, // will only allow numbers
isInt: true, // checks for valid integers
isFloat: true, // checks for valid floating point numbers
isDecimal: true, // checks for any numbers
isLowercase: true, // checks for lowercase
isUppercase: true, // checks for uppercase
notNull: true, // won't allow null
isNull: true, // only allows null
notEmpty: true, // don't allow empty strings
equals: 'specific value', // only allow a specific value
contains: 'foo', // force specific substrings
notIn: [['foo', 'bar']], // check the value is not one of these
isIn: [['foo', 'bar']], // check the value is one of these
notContains: 'bar', // don't allow specific substrings
len: [2,10], // only allow values with length between 2 and 10
isUUID: 4, // only allow uuids
isDate: true, // only allow date strings
isAfter: "2011-11-05", // only allow date strings after a specific date
isBefore: "2011-11-05", // only allow date strings before a specific date
max: 23, // only allow values <= 23
min: 23, // only allow values >= 23
isCreditCard: true, // check for valid credit card numbers

// Examples of custom validators:
isEven(value) {
if (parseInt(value) % 2 !== 0) {
throw new Error('Only even values are allowed!');
}
}
isGreaterThanOtherField(value) {
if (parseInt(value) <= parseInt(this.otherField)) {
throw new Error('Bar must be greater than otherField.');
}
}
}
}
}, { sequelize });

注意:验证函数有些地方需要传入多个参数,传入的参数必须是数组。但是如果要传入一个数组参数,例如 isIn, 将会被解释成多个字符串参数而不是一个数组参数。解决方法是传入一个单一长度的数组,就像上面例子中的 [['one', 'two']]

用自定义错误信息代替 validator.js中提供的默认信息,需要使用一个对象而不是纯值或参数数组。例如不需要参数的验证器可以设置如下自定义信息:

1
2
3
isInt: {
msg: "Must be an integer number of pennies"
}

或者如果需要参数,可以加上 args 属性:

1
2
3
4
isIn: {
args: [['en', 'zh']],
msg: "Must be English or Chinese"
}

当使用自定义验证器函数时,错误消息将是抛出的 Error 对象所持有的任何消息。

有关内置验证方法的更多详细信息,请参阅 validator.js project .

*提示: *你还可以为日志记录部分定义自定义函数. 只是传递一个方法. 第一个参数将是记录的字符串.

属性验证器 与 allowNull

如果模型的特定字段设置为不允许为空(使用allowNull:false)并且该值已经被设置为 null,则所有的验证器都将跳过并抛出 ValidationError异常。

另一方面,如果设置为允许为空(使用 allowNull:true)并且该值已经被设置为 null,则只会跳过内置验证器,而自定义验证器仍将执行。

这意味着可以使用一个字符串字段来验证其长度在5到10个字符之间,但也允许为 null(因为当值为 null 时,将自动跳过长度验证器):

1
2
3
4
5
6
7
8
9
10
class User extends Model {}
User.init({
username: {
type: Sequelize.STRING,
allowNull: true,
validate: {
len: [5, 10]
}
}
}, { sequelize });

你也能利用自定义验证器有条件的允许 null 值,因为它不会被跳过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User extends Model {}
User.init({
age: Sequelize.INTEGER,
name: {
type: Sequelize.STRING,
allowNull: true,
validate: {
customValidator(value) {
if (value === null && this.age !== 10) {
throw new Error("name can't be null unless age is 10");
}
})
}
}
}, { sequelize });

通过设置 notNull 验证器,你能自定义 allowNull 的错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
class User extends Model {}
User.init({
name: {
type: Sequelize.STRING,
allowNull: false,
validate: {
notNull: {
msg: 'Please enter your name'
}
}
}
}, { sequelize });

模型范围验证

验证器也可以在特定字段验证器之后用来定义检查模型。例如,你可以确保纬度经度都不设置,或者两者都设置。如果设置了一个而另一个未设置则验证失败。

模型验证器方法与模型对象的上下文一起调用,如果它们抛出错误,则认为失败,否则认为通过。这与自定义字段特定的验证器是一样的。

所收集的任何错误消息都将与验证结果对象一起放在字段验证错误中。这个错误使用在validate参数对象中以失败的验证方法的键来命名。即便在任何一个时刻,每个模型验证方法只能有一个错误消息,它会在数组中显示为单个字符串错误。以最大化与字段错误的一致性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Pub extends Model {}
Pub.init({
name: { type: Sequelize.STRING },
address: { type: Sequelize.STRING },
latitude: {
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: null,
validate: { min: -90, max: 90 }
},
longitude: {
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: null,
validate: { min: -180, max: 180 }
},
}, {
validate: {
bothCoordsOrNone() {
if ((this.latitude === null) !== (this.longitude === null)) {
throw new Error('Require either both latitude and longitude or neither')
}
}
},
sequelize,
})

在这种简单情况下,如果给定纬度或经度,而不是同时包含两者,则验证失败。 如果我们尝试构建一个超范围的纬度和经度,那么raging_bullock_arms.validate()可能会返回

1
2
3
4
{
'latitude': ['Invalid number: latitude'],
'bothCoordsOrNone': ['Require either both latitude and longitude or neither']
}

这样的验证也可以通过在单个属性上定义的自定义验证器(例如latitude属性,通过检查(value === null) !== (this.longitude === null))来完成,但模型范围的验证方法更清晰.

配置(configuration)

你可以通过Sequelize handles影响列名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Bar extends Model {}
Bar.init({ /* bla */ }, {
// model名. 在`sequelize.models`下面用这个名字存储此model
// 默认类名,如在这个例子中是Bar。控制自动生成的外键和联合查询的名字
modelName: 'bar',

// 不自动添加timestamp属性(updatedAt, createdAt)
timestamps: false,

// 不删除数据库记录,而是设置一个删除属性deletedAt到当前数据(当删除完毕时)
// paranoid只有在timestamps启用时才起作用
paranoid: true,

// 自动设置下划线.
// 字段如果已经设置了则不会重写
underscored: true,

// 不会自动在表名加上s
freezeTableName: true,

// 定义数据库表名
tableName: 'my_very_custom_table_name',

// 使用乐观锁.
// 启用时,sequelize将增加一个version count属性到model上,
// 并且当脏读保存时,会抛出OptimisticLockingError错误
// 当你想启用时,设置成true或者一个属性名字符串。
version: true,

// Sequelize实例
sequelize,
})

当你想使用timestamps,但是只想使用一部分,或者使用另外的名字时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Foo extends Model {}
Foo.init({ /* bla */ }, {
// 不要忘了启用timestamps!
timestamps: true,

// 不启用createdAt
createdAt: false,

// updatedAt的字段名是updateTimestamp
updatedAt: 'updateTimestamp',

// deletedAt字段名是destroyTime (启用paranoid才能生效)
deletedAt: 'destroyTime',
paranoid: true,

sequelize,
})

你也能更改数据库engine,比如MyISAM。默认值是InnoDB

1
2
3
4
5
6
7
8
9
10
class Person extends Model {}
Person.init({ /* attributes */ }, {
engine: 'MYISAM',
sequelize
})

// or globally
const sequelize = new Sequelize(db, user, pw, {
define: { engine: 'MYISAM' }
})

最后,你可以在MySQL和PG中给表增加注释

1
2
3
4
5
class Person extends Model {}
Person.init({ /* attributes */ }, {
comment: "I'm a table comment!",
sequelize
})

乐观锁(Optimistic Locking)

Sequelize通过version原生支持乐观锁。默认是不启用的,可以通过设置version启用

乐观锁允许并发修改操作并且防止overwriting data冲突。当检测到冲突时,会抛出OptimisticLockError错误

数据库同步

当开始一个新的项目时,你还不会有一个数据库结构,并且使用Sequelize你也不需要它。只需指定你的模型结构,并让库完成其余操作。目前支持的是创建和删除表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Create the tables:
Project.sync()
Task.sync()

// Force the creation!
Project.sync({force: true}) // this will drop the table first and re-create it afterwards

// drop the tables:
Project.drop()
Task.drop()

// event handling:
Project.[sync|drop]().then(() => {
// ok ... everything is nice!
}).catch(error => {
// oooh, did you enter wrong database credentials?
})

因为同步和删除所有的表可能要写很多行,你也可以让Sequelize来为做这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Sync all models that aren't already in the database
sequelize.sync()

// Force sync all models
sequelize.sync({force: true})

// Drop all tables
sequelize.drop()

// emit handling:
sequelize.[sync|drop]().then(() => {
// woot woot
}).catch(error => {
// whooops
})

因为.sync({ force: true })是具有破坏性的操作,可以使用match参数作为附加的安全检查。

match参数可以通知Sequelize,以便在同步之前匹配正则表达式与数据库名称 - 在测试中使用force:true但不使用实时代码的情况下的安全检查。

1
2
// This will run .sync() only if database name ends with '_test'
sequelize.sync({ force: true, match: /_test$/ });

扩展model

Sequelize 模型是ES6类。你可以轻松添加自定义实例或类级别的方法

1
2
3
4
5
6
7
8
9
10
11
12
class User extends Model {
// Adding a class level method
static classLevelMethod() {
return 'foo';
}

// Adding an instance level method
instanceLevelMethod() {
return 'bar';
}
}
User.init({ firstname: Sequelize.STRING }, { sequelize });

当然,你还可以访问实例的数据并生成虚拟的getter:

1
2
3
4
5
6
7
8
9
class User extends Model {
getFullname() {
return [this.firstname, this.lastname].join(' ');
}
}
User.init({ firstname: Sequelize.STRING, lastname: Sequelize.STRING }, { sequelize });

// Example:
User.build({ firstname: 'foo', lastname: 'bar' }).getFullname() // 'foo bar'

索引

Model.sync()或者sequelize.sync同步时,可以增加索引到模型定义中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class User extends Model {}
User.init({}, {
indexes: [
// Create a unique index on email
{
unique: true,
fields: ['email']
},

// Creates a gin index on data with the jsonb_path_ops operator
{
fields: ['data'],
using: 'gin',
operator: 'jsonb_path_ops'
},

// By default index name will be [table]_[fields]
// Creates a multi column partial index
{
name: 'public_by_author',
fields: ['author', 'status'],
where: {
status: 'public'
}
},

// A BTREE index with a ordered field
{
name: 'title_index',
using: 'BTREE',
fields: ['author', {attribute: 'title', collate: 'en_US', order: 'DESC', length: 5}]
}
],
sequelize
});