Sequelize 模型基础与定义 - 数据类型和验证详解
发布时间:2024-01-29
作者:一介布衣
标签:Sequelize, 模型定义, 数据类型, 数据验证
前言
上一篇文章我们通过一个用户管理系统初步体验了 Sequelize 的魅力,今天咱们来深入学习模型定义的各种细节。说实话,模型定义是 Sequelize 的核心,掌握好了这部分,后面的开发就会事半功倍。
我记得刚开始学 Sequelize 的时候,光是各种数据类型就搞得我头晕,什么 STRING、TEXT、INTEGER、DECIMAL,还有各种验证规则,真的是眼花缭乱。后来慢慢摸索,发现其实是有规律可循的。今天我就把这些经验分享给大家。
模型定义基础
在 Sequelize 中,模型就是数据库表的 JavaScript 表示。每个模型对应一张表,模型的属性对应表的字段。
基本语法
javascript
const { DataTypes } = require('sequelize');
const User = sequelize.define('User', {
// 字段定义
name: DataTypes.STRING,
age: DataTypes.INTEGER
}, {
// 模型选项
tableName: 'users',
timestamps: true
});
完整的字段定义
一个完整的字段定义包含以下部分:
javascript
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER, // 数据类型
primaryKey: true, // 主键
autoIncrement: true, // 自增
allowNull: false, // 不允许为空
unique: true, // 唯一约束
defaultValue: 1, // 默认值
comment: '用户ID', // 字段注释
validate: { // 验证规则
min: 1
}
}
});
数据类型详解
Sequelize 提供了丰富的数据类型,我们来逐一了解。
字符串类型
javascript
const Product = sequelize.define('Product', {
// 短字符串,默认255字符
name: DataTypes.STRING,
// 指定长度的字符串
code: DataTypes.STRING(50),
// 长文本,适合存储大量文字
description: DataTypes.TEXT,
// 指定长度的文本
summary: DataTypes.TEXT('tiny'), // TINYTEXT
content: DataTypes.TEXT('medium'), // MEDIUMTEXT
detail: DataTypes.TEXT('long'), // LONGTEXT
// 固定长度字符串
status: DataTypes.CHAR(10),
// 二进制数据
avatar: DataTypes.BLOB,
file: DataTypes.BLOB('long') // LONGBLOB
});
使用建议:
- 用户名、邮箱等用
STRING(100)
- 商品名称等用
STRING(200)
- 文章内容用
TEXT
- 状态码用
CHAR(10)
数字类型
javascript
const Order = sequelize.define('Order', {
// 整数类型
quantity: DataTypes.INTEGER,
// 大整数
totalViews: DataTypes.BIGINT,
// 小整数
priority: DataTypes.SMALLINT,
// 极小整数(-128 到 127)
status: DataTypes.TINYINT,
// 浮点数
rating: DataTypes.FLOAT,
// 双精度浮点数
latitude: DataTypes.DOUBLE,
// 精确小数(推荐用于金额)
price: DataTypes.DECIMAL(10, 2), // 10位数字,2位小数
amount: DataTypes.DECIMAL(15, 4),
// 无符号整数
userId: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false
}
});
金额处理最佳实践:
javascript
// 推荐:使用 DECIMAL 存储金额
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
validate: {
min: 0
},
get() {
// 返回时转换为数字
return parseFloat(this.getDataValue('price'));
},
set(value) {
// 存储时保持精度
this.setDataValue('price', parseFloat(value).toFixed(2));
}
}
日期时间类型
javascript
const Event = sequelize.define('Event', {
// 日期时间(包含时区)
startTime: DataTypes.DATE,
// 只有日期
eventDate: DataTypes.DATEONLY,
// 只有时间
duration: DataTypes.TIME,
// 创建时间(自动设置)
createdAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
// 自定义日期格式
publishedAt: {
type: DataTypes.DATE,
get() {
const date = this.getDataValue('publishedAt');
return date ? date.toISOString().split('T')[0] : null;
}
}
});
布尔类型
javascript
const User = sequelize.define('User', {
// 布尔值
isActive: DataTypes.BOOLEAN,
// 带默认值的布尔值
isVerified: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
// 三态布尔(true/false/null)
isVip: {
type: DataTypes.BOOLEAN,
allowNull: true,
defaultValue: null
}
});
枚举类型
javascript
const User = sequelize.define('User', {
// 简单枚举
gender: DataTypes.ENUM('male', 'female', 'other'),
// 复杂枚举
status: {
type: DataTypes.ENUM,
values: ['pending', 'active', 'suspended', 'deleted'],
defaultValue: 'pending',
validate: {
isIn: {
args: [['pending', 'active', 'suspended', 'deleted']],
msg: '状态值不正确'
}
}
}
});
JSON 类型
javascript
const Product = sequelize.define('Product', {
// JSON 数据
specifications: DataTypes.JSON,
// 带默认值的 JSON
settings: {
type: DataTypes.JSON,
defaultValue: {},
get() {
const value = this.getDataValue('settings');
return value || {};
}
},
// 数组类型(PostgreSQL)
tags: DataTypes.ARRAY(DataTypes.STRING),
// 复杂 JSON 结构
metadata: {
type: DataTypes.JSON,
validate: {
isValidMetadata(value) {
if (value && typeof value !== 'object') {
throw new Error('metadata 必须是对象类型');
}
}
}
}
});
UUID 类型
javascript
const User = sequelize.define('User', {
// UUID 主键
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
// 外键 UUID
organizationId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'organizations',
key: 'id'
}
}
});
数据验证详解
Sequelize 提供了强大的数据验证功能,可以在数据保存前进行各种检查。
内置验证器
javascript
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
validate: {
isEmail: true, // 邮箱格式
notEmpty: true, // 不能为空字符串
len: [5, 100] // 长度范围
}
},
age: {
type: DataTypes.INTEGER,
validate: {
min: 0, // 最小值
max: 120, // 最大值
isInt: true // 必须是整数
}
},
website: {
type: DataTypes.STRING,
validate: {
isUrl: true // URL 格式
}
},
phone: {
type: DataTypes.STRING,
validate: {
is: /^1[3-9]\d{9}$/ // 正则表达式
}
},
username: {
type: DataTypes.STRING,
validate: {
isAlphanumeric: true, // 只能包含字母和数字
notIn: [['admin', 'root']] // 不能是这些值
}
}
});
自定义验证器
javascript
const User = sequelize.define('User', {
password: {
type: DataTypes.STRING,
validate: {
// 自定义验证函数
isStrongPassword(value) {
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
throw new Error('密码必须包含大小写字母和数字');
}
},
// 异步验证
async isUniqueEmail(value) {
const user = await User.findOne({ where: { email: value } });
if (user && user.id !== this.id) {
throw new Error('邮箱已被使用');
}
}
}
},
birthDate: {
type: DataTypes.DATEONLY,
validate: {
isAdult(value) {
const today = new Date();
const birth = new Date(value);
const age = today.getFullYear() - birth.getFullYear();
if (age < 18) {
throw new Error('用户必须年满18岁');
}
}
}
}
});
模型级验证
javascript
const User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: DataTypes.STRING
}, {
validate: {
// 模型级验证,可以访问多个字段
bothNamesOrNone() {
if ((this.firstName === null) !== (this.lastName === null)) {
throw new Error('姓和名必须同时提供或同时为空');
}
},
// 复杂业务逻辑验证
async emailDomainCheck() {
if (this.email) {
const domain = this.email.split('@')[1];
const allowedDomains = ['company.com', 'partner.com'];
if (!allowedDomains.includes(domain)) {
throw new Error('只允许公司邮箱注册');
}
}
}
}
});
字段选项详解
约束选项
javascript
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true, // 主键
autoIncrement: true // 自增
},
email: {
type: DataTypes.STRING,
allowNull: false, // 不允许为空
unique: true, // 唯一约束
// 或者命名唯一约束
unique: 'email_unique'
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: {
name: 'username_unique',
msg: '用户名已存在'
}
},
organizationId: {
type: DataTypes.INTEGER,
references: { // 外键约束
model: 'organizations',
key: 'id'
},
onUpdate: 'CASCADE', // 更新时级联
onDelete: 'SET NULL' // 删除时设为空
}
});
默认值选项
javascript
const Post = sequelize.define('Post', {
status: {
type: DataTypes.STRING,
defaultValue: 'draft' // 静态默认值
},
publishedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW // 动态默认值
},
viewCount: {
type: DataTypes.INTEGER,
defaultValue: 0
},
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4 // UUID 默认值
},
metadata: {
type: DataTypes.JSON,
defaultValue: {} // 对象默认值
}
});
Getter 和 Setter
javascript
const User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
// 虚拟字段
fullName: {
type: DataTypes.VIRTUAL,
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
const names = value.split(' ');
this.setDataValue('firstName', names[0]);
this.setDataValue('lastName', names[1]);
}
},
// 自定义 getter
email: {
type: DataTypes.STRING,
get() {
const email = this.getDataValue('email');
return email ? email.toLowerCase() : null;
}
},
// 自定义 setter
password: {
type: DataTypes.STRING,
set(value) {
// 自动加密密码
const hash = bcrypt.hashSync(value, 10);
this.setDataValue('password', hash);
}
}
});
模型选项配置
表名和字段名配置
javascript
const User = sequelize.define('User', {
// 字段定义
}, {
tableName: 'sys_users', // 自定义表名
freezeTableName: true, // 禁用表名复数化
underscored: true, // 使用下划线命名
timestamps: true, // 启用时间戳
createdAt: 'created_at', // 自定义创建时间字段名
updatedAt: 'updated_at', // 自定义更新时间字段名
deletedAt: 'deleted_at', // 自定义删除时间字段名
paranoid: true, // 启用软删除
version: true // 启用版本控制
});
索引配置
javascript
const User = sequelize.define('User', {
email: DataTypes.STRING,
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
status: DataTypes.STRING
}, {
indexes: [
// 单字段索引
{
fields: ['email']
},
// 复合索引
{
fields: ['firstName', 'lastName']
},
// 唯一索引
{
unique: true,
fields: ['email']
},
// 命名索引
{
name: 'user_status_index',
fields: ['status']
},
// 部分索引(PostgreSQL)
{
fields: ['email'],
where: {
status: 'active'
}
}
]
});
实战技巧
1. 动态模型定义
javascript
function createUserModel(tableName) {
return sequelize.define('User', {
name: DataTypes.STRING,
email: DataTypes.STRING
}, {
tableName: tableName,
timestamps: true
});
}
const AdminUser = createUserModel('admin_users');
const RegularUser = createUserModel('regular_users');
2. 模型继承
javascript
// 基础模型
const BaseModel = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE
};
// 继承基础模型
const User = sequelize.define('User', {
...BaseModel,
name: DataTypes.STRING,
email: DataTypes.STRING
});
const Product = sequelize.define('Product', {
...BaseModel,
title: DataTypes.STRING,
price: DataTypes.DECIMAL(10, 2)
});
3. 条件字段定义
javascript
const userFields = {
name: DataTypes.STRING,
email: DataTypes.STRING
};
// 根据环境添加不同字段
if (process.env.NODE_ENV === 'development') {
userFields.debugInfo = DataTypes.JSON;
}
const User = sequelize.define('User', userFields);
常见问题解决
1. 字段类型选择
问题:不知道该用什么数据类型
解决方案:
javascript
// 用户ID:INTEGER 或 UUID
id: DataTypes.INTEGER.UNSIGNED
// 金额:DECIMAL
price: DataTypes.DECIMAL(10, 2)
// 长文本:TEXT
content: DataTypes.TEXT
// 状态:ENUM
status: DataTypes.ENUM('active', 'inactive')
// 配置信息:JSON
settings: DataTypes.JSON
2. 验证错误处理
javascript
try {
await user.save();
} catch (error) {
if (error.name === 'SequelizeValidationError') {
const errors = error.errors.map(err => ({
field: err.path,
message: err.message,
value: err.value
}));
console.log('验证错误:', errors);
}
}
3. 性能优化
javascript
// 只查询需要的字段
const users = await User.findAll({
attributes: ['id', 'name', 'email']
});
// 使用原始查询提高性能
const users = await User.findAll({
raw: true, // 返回原始数据,不创建实例
attributes: ['id', 'name']
});
总结
今天我们深入学习了 Sequelize 模型定义的各个方面:
- ✅ 各种数据类型的使用场景
- ✅ 内置和自定义验证器
- ✅ 字段选项和约束配置
- ✅ 模型选项和索引设置
- ✅ 实战技巧和最佳实践
掌握了这些知识,你就能够:
- 根据业务需求选择合适的数据类型
- 设计合理的数据验证规则
- 优化数据库性能
- 避免常见的设计陷阱
下一篇文章,我们将学习模型实例的各种操作方法,包括创建、更新、删除等。这是真正开始操作数据的第一步!
相关文章推荐:
有问题欢迎留言讨论,我会及时回复大家!