Skip to content

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 模型定义的各个方面:

  • ✅ 各种数据类型的使用场景
  • ✅ 内置和自定义验证器
  • ✅ 字段选项和约束配置
  • ✅ 模型选项和索引设置
  • ✅ 实战技巧和最佳实践

掌握了这些知识,你就能够:

  • 根据业务需求选择合适的数据类型
  • 设计合理的数据验证规则
  • 优化数据库性能
  • 避免常见的设计陷阱

下一篇文章,我们将学习模型实例的各种操作方法,包括创建、更新、删除等。这是真正开始操作数据的第一步!


相关文章推荐:

有问题欢迎留言讨论,我会及时回复大家!