Sequelize 教程
项目概述
Sequelize 是一个基于 Promise 的 Node.js ORM(对象关系映射)库,支持 PostgreSQL、MySQL、SQLite 和 Microsoft SQL Server 等多种关系型数据库。它提供了强大的模型定义、查询构建、关联关系处理、事务支持等功能,大幅简化了数据库操作的复杂性。
- 项目链接:https://github.com/sequelize/sequelize
- 官方网站:https://sequelize.org/
- GitHub Stars:28k+
- 适用环境:Node.js 项目、多种关系型数据库
安装设置
1. 安装 Sequelize
# 安装 Sequelize 核心包
npm install sequelize
# 安装对应的数据库驱动(选择其一)
# PostgreSQL
npm install pg pg-hstore
# MySQL
npm install mysql2
# SQLite
npm install sqlite3
# Microsoft SQL Server
npm install tedious2. 连接数据库
const { Sequelize } = require('sequelize');
// 方法 1: 传递连接参数
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'postgres' // 或 'mysql', 'sqlite', 'mssql'
});
// 方法 2: 传递连接 URL
const sequelize = new Sequelize('postgres://username:password@localhost:5432/database');
// 测试连接
async function testConnection() {
try {
await sequelize.authenticate();
console.log('数据库连接成功');
} catch (error) {
console.error('数据库连接失败:', error);
}
}
testConnection();3. 配置选项
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'postgres',
dialectOptions: {
// 额外的方言特定选项
},
pool: {
max: 5, // 最大连接数
min: 0, // 最小连接数
acquire: 30000, // 连接超时时间(毫秒)
idle: 10000 // 空闲连接超时时间(毫秒)
},
logging: console.log, // 日志记录选项
define: {
timestamps: true, // 自动添加 createdAt 和 updatedAt 字段
underscored: false, // 使用驼峰命名法
freezeTableName: false // 表名自动复数
}
});核心功能
1. 模型定义
const { DataTypes } = require('sequelize');
// 定义 User 模型
const User = sequelize.define('User', {
// 定义字段
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [2, 50] // 长度验证
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true // 邮箱验证
}
},
age: {
type: DataTypes.INTEGER,
validate: {
min: 0,
max: 150
}
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
// 模型选项
tableName: 'users', // 自定义表名
timestamps: true // 自动添加时间戳
});
// 同步模型到数据库
async function syncModels() {
try {
// 创建表(如果不存在)
await User.sync({ force: false }); // force: true 会删除现有表并重新创建
console.log('模型同步成功');
} catch (error) {
console.error('模型同步失败:', error);
}
}
syncModels();2. 基本 CRUD 操作
创建记录
// 方法 1: 使用 create 方法
const user = await User.create({
name: '张三',
email: 'zhangsan@example.com',
age: 25
});
console.log('创建用户成功:', user.toJSON());
// 方法 2: 先构建实例,再保存
const user = User.build({
name: '李四',
email: 'lisi@example.com',
age: 30
});
await user.save();
console.log('创建用户成功:', user.toJSON());查询记录
// 查询所有记录
const users = await User.findAll();
console.log('所有用户:', users.map(user => user.toJSON()));
// 根据条件查询单个记录
const user = await User.findOne({
where: {
name: '张三'
}
});
console.log('查询用户:', user?.toJSON());
// 根据主键查询
const user = await User.findByPk(1);
console.log('根据主键查询:', user?.toJSON());
// 带条件的查询
const users = await User.findAll({
where: {
age: {
[Op.gte]: 18 // 年龄大于等于 18
},
isActive: true
},
order: [
['age', 'DESC'] // 按年龄降序排序
],
limit: 10, // 限制返回 10 条
offset: 0 // 偏移量
});更新记录
// 方法 1: 更新单个实例
const user = await User.findByPk(1);
if (user) {
user.name = '张三(更新)';
user.age = 26;
await user.save();
console.log('更新用户成功:', user.toJSON());
}
// 方法 2: 批量更新
const result = await User.update(
{ isActive: false },
{
where: {
age: {
[Op.lt]: 18
}
}
}
);
console.log('批量更新成功,影响行数:', result[0]);删除记录
// 方法 1: 删除单个实例
const user = await User.findByPk(1);
if (user) {
await user.destroy();
console.log('删除用户成功');
}
// 方法 2: 批量删除
const result = await User.destroy({
where: {
isActive: false
}
});
console.log('批量删除成功,影响行数:', result);3. 关联关系
一对一关系
// 定义 Profile 模型
const Profile = sequelize.define('Profile', {
bio: DataTypes.TEXT,
avatar: DataTypes.STRING
});
// 定义一对一关系
User.hasOne(Profile, {
foreignKey: 'userId',
as: 'profile'
});
Profile.belongsTo(User, {
foreignKey: 'userId',
as: 'user'
});
// 创建关联数据
const user = await User.create({
name: '张三',
email: 'zhangsan@example.com',
profile: {
bio: '这是个人简介',
avatar: 'avatar.jpg'
}
}, {
include: 'profile'
});
// 查询关联数据
const userWithProfile = await User.findByPk(1, {
include: 'profile'
});
console.log('用户及其资料:', userWithProfile.toJSON());一对多关系
// 定义 Post 模型
const Post = sequelize.define('Post', {
title: DataTypes.STRING,
content: DataTypes.TEXT
});
// 定义一对多关系
User.hasMany(Post, {
foreignKey: 'userId',
as: 'posts'
});
Post.belongsTo(User, {
foreignKey: 'userId',
as: 'user'
});
// 创建关联数据
const user = await User.create({
name: '张三',
email: 'zhangsan@example.com'
});
await Post.create({
title: '第一篇文章',
content: '这是内容',
userId: user.id
});
// 查询关联数据
const userWithPosts = await User.findByPk(1, {
include: 'posts'
});
console.log('用户及其文章:', userWithPosts.toJSON());多对多关系
// 定义 Tag 模型
const Tag = sequelize.define('Tag', {
name: DataTypes.STRING
});
// 定义多对多关系
const PostTag = sequelize.define('PostTag', {
postId: {
type: DataTypes.INTEGER,
references: {
model: 'Posts',
key: 'id'
}
},
tagId: {
type: DataTypes.INTEGER,
references: {
model: 'Tags',
key: 'id'
}
}
});
Post.belongsToMany(Tag, {
through: PostTag,
as: 'tags'
});
Tag.belongsToMany(Post, {
through: PostTag,
as: 'posts'
});
// 创建关联数据
const post = await Post.create({
title: '第一篇文章',
content: '这是内容'
});
const tag1 = await Tag.create({ name: '技术' });
const tag2 = await Tag.create({ name: '教程' });
await post.addTags([tag1, tag2]);
// 查询关联数据
const postWithTags = await Post.findByPk(1, {
include: 'tags'
});
console.log('文章及其标签:', postWithTags.toJSON());4. 事务
// 使用事务
async function transferMoney(fromUserId, toUserId, amount) {
const transaction = await sequelize.transaction();
try {
// 查找发送方
const fromUser = await User.findByPk(fromUserId, { transaction });
if (!fromUser || fromUser.balance < amount) {
throw new Error('余额不足');
}
// 查找接收方
const toUser = await User.findByPk(toUserId, { transaction });
if (!toUser) {
throw new Error('接收方不存在');
}
// 更新余额
fromUser.balance -= amount;
toUser.balance += amount;
await fromUser.save({ transaction });
await toUser.save({ transaction });
// 提交事务
await transaction.commit();
console.log('转账成功');
return true;
} catch (error) {
// 回滚事务
await transaction.rollback();
console.error('转账失败:', error);
return false;
}
}
// 使用
await transferMoney(1, 2, 100);5. 数据验证
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [2, 50],
notEmpty: true
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true,
isUnique: async function(value) {
const existingUser = await User.findOne({
where: { email: value }
});
if (existingUser) {
throw new Error('邮箱已被使用');
}
}
}
},
age: {
type: DataTypes.INTEGER,
validate: {
min: 0,
max: 150,
isInt: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [6, Infinity],
isStrongPassword: function(value) {
// 自定义密码强度验证
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
throw new Error('密码必须包含大小写字母和数字');
}
}
}
}
});
// 尝试创建无效用户
try {
const user = await User.create({
name: 'A', // 长度不足
email: 'invalid-email', // 无效邮箱
age: -1, // 年龄为负数
password: '123' // 密码强度不足
});
} catch (error) {
console.error('验证失败:', error.errors.map(err => err.message));
}高级功能
1. 钩子(Hooks)
const User = sequelize.define('User', {
// 字段定义
});
// 钩子示例
User.beforeCreate(async (user) => {
// 创建前加密密码
if (user.password) {
user.password = await hashPassword(user.password);
}
});
User.afterCreate(async (user) => {
// 创建后发送欢迎邮件
await sendWelcomeEmail(user.email);
});
User.beforeUpdate(async (user) => {
// 更新前检查
if (user.changed('email')) {
// 邮箱变更时的处理
}
});
User.afterDestroy(async (user) => {
// 删除后清理相关数据
await cleanupUserRelatedData(user.id);
});2. 作用域(Scopes)
const User = sequelize.define('User', {
// 字段定义
}, {
scopes: {
active: {
where: {
isActive: true
}
},
adult: {
where: {
age: {
[Op.gte]: 18
}
}
},
withProfile: {
include: 'profile'
}
}
});
// 使用作用域
const activeUsers = await User.scope('active').findAll();
const adultUsersWithProfile = await User.scope(['adult', 'withProfile']).findAll();3. 原始查询
// 执行原始 SQL 查询
const [results, metadata] = await sequelize.query(
'SELECT * FROM users WHERE age > :age',
{
replacements: { age: 18 },
type: QueryTypes.SELECT
}
);
console.log('原始查询结果:', results);
// 执行插入操作
const [id, metadata] = await sequelize.query(
'INSERT INTO users (name, email) VALUES (:name, :email)',
{
replacements: { name: '王五', email: 'wangwu@example.com' },
type: QueryTypes.INSERT
}
);
console.log('插入成功,ID:', id);4. 迁移
初始化迁移
# 安装迁移工具
npm install --save-dev sequelize-cli
# 初始化迁移配置
npx sequelize-cli init创建迁移文件
# 创建用户表迁移
npx sequelize-cli model:generate --name User --attributes name:string,email:string,age:integer
# 创建帖子表迁移
npx sequelize-cli model:generate --name Post --attributes title:string,content:text,userId:integer运行迁移
# 运行所有待执行的迁移
npx sequelize-cli db:migrate
# 回滚最后一次迁移
npx sequelize-cli db:migrate:undo
# 回滚所有迁移
npx sequelize-cli db:migrate:undo:all种子数据
# 创建种子文件
npx sequelize-cli seed:generate --name demo-user
# 运行种子
npx sequelize-cli db:seed:all
# 回滚种子
npx sequelize-cli db:seed:undo:all实际应用场景
1. 用户认证系统
const bcrypt = require('bcrypt');
const User = sequelize.define('User', {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [6, Infinity]
}
}
});
// 密码加密钩子
User.beforeCreate(async (user) => {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
});
User.beforeUpdate(async (user) => {
if (user.changed('password')) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
});
// 验证密码方法
User.prototype.validatePassword = async function(password) {
return await bcrypt.compare(password, this.password);
};
// 登录方法
async function login(username, password) {
try {
const user = await User.findOne({
where: {
[Op.or]: [
{ username },
{ email: username }
]
}
});
if (!user) {
return { success: false, message: '用户不存在' };
}
const isValidPassword = await user.validatePassword(password);
if (!isValidPassword) {
return { success: false, message: '密码错误' };
}
return { success: true, user: user.toJSON() };
} catch (error) {
console.error('登录失败:', error);
return { success: false, message: '登录失败' };
}
};
// 使用
const result = await login('zhangsan', 'password123');
console.log(result);2. 博客系统
// 定义模型
const User = sequelize.define('User', {
name: DataTypes.STRING,
email: DataTypes.STRING
});
const Post = sequelize.define('Post', {
title: DataTypes.STRING,
content: DataTypes.TEXT,
status: DataTypes.ENUM('draft', 'published')
});
const Comment = sequelize.define('Comment', {
content: DataTypes.TEXT,
status: DataTypes.ENUM('approved', 'pending', 'rejected')
});
// 定义关联
User.hasMany(Post, { as: 'posts' });
Post.belongsTo(User, { as: 'author' });
Post.hasMany(Comment, { as: 'comments' });
Comment.belongsTo(Post, { as: 'post' });
Comment.belongsTo(User, { as: 'author' });
User.hasMany(Comment, { as: 'comments' });
// 创建博客文章
async function createPost(userId, title, content) {
try {
const post = await Post.create({
title,
content,
status: 'draft',
authorId: userId
});
return post;
} catch (error) {
console.error('创建文章失败:', error);
throw error;
}
}
// 获取文章详情(包含作者和评论)
async function getPostById(postId) {
try {
const post = await Post.findByPk(postId, {
include: [
{ model: User, as: 'author' },
{
model: Comment,
as: 'comments',
include: [{ model: User, as: 'author' }],
where: { status: 'approved' },
required: false
}
]
});
return post;
} catch (error) {
console.error('获取文章失败:', error);
throw error;
}
}
// 发布文章
async function publishPost(postId) {
try {
const post = await Post.findByPk(postId);
if (post) {
post.status = 'published';
await post.save();
return post;
}
return null;
} catch (error) {
console.error('发布文章失败:', error);
throw error;
}
}3. 电商系统
// 定义模型
const User = sequelize.define('User', {
name: DataTypes.STRING,
email: DataTypes.STRING
});
const Product = sequelize.define('Product', {
name: DataTypes.STRING,
price: DataTypes.DECIMAL(10, 2),
stock: DataTypes.INTEGER
});
const Order = sequelize.define('Order', {
total: DataTypes.DECIMAL(10, 2),
status: DataTypes.ENUM('pending', 'paid', 'shipping', 'delivered', 'cancelled')
});
const OrderItem = sequelize.define('OrderItem', {
quantity: DataTypes.INTEGER,
price: DataTypes.DECIMAL(10, 2)
});
// 定义关联
User.hasMany(Order, { as: 'orders' });
Order.belongsTo(User, { as: 'user' });
Order.belongsToMany(Product, {
through: OrderItem,
as: 'products'
});
Product.belongsToMany(Order, {
through: OrderItem,
as: 'orders'
});
Order.hasMany(OrderItem, { as: 'items' });
OrderItem.belongsTo(Order, { as: 'order' });
Product.hasMany(OrderItem, { as: 'orderItems' });
OrderItem.belongsTo(Product, { as: 'product' });
// 创建订单
async function createOrder(userId, products) {
const transaction = await sequelize.transaction();
try {
// 计算总金额
let total = 0;
const orderItems = [];
for (const { productId, quantity } of products) {
const product = await Product.findByPk(productId, { transaction });
if (!product) {
throw new Error(`产品 ${productId} 不存在`);
}
if (product.stock < quantity) {
throw new Error(`产品 ${product.name} 库存不足`);
}
// 减少库存
product.stock -= quantity;
await product.save({ transaction });
// 计算金额
const itemTotal = product.price * quantity;
total += itemTotal;
orderItems.push({
productId,
quantity,
price: product.price
});
}
// 创建订单
const order = await Order.create({
userId,
total,
status: 'pending'
}, { transaction });
// 创建订单项
for (const item of orderItems) {
await OrderItem.create({
...item,
orderId: order.id
}, { transaction });
}
// 提交事务
await transaction.commit();
return order;
} catch (error) {
// 回滚事务
await transaction.rollback();
console.error('创建订单失败:', error);
throw error;
}
}
// 使用
await createOrder(1, [
{ productId: 1, quantity: 2 },
{ productId: 2, quantity: 1 }
]);代码优化建议
- 连接池配置:根据应用规模配置合理的连接池大小,避免连接过多或过少
- 索引优化:为频繁查询的字段添加索引,提高查询性能
- 批量操作:对于大量数据操作,使用批量 API 减少数据库请求次数
- 延迟加载:使用延迟加载(懒加载)减少初始查询的数据量
- 分页查询:对于大量数据的查询,使用分页减少内存消耗
- 事务使用:在涉及多个操作的业务逻辑中使用事务,确保数据一致性
- 缓存策略:对频繁访问但不经常变化的数据使用缓存
- 错误处理:实现统一的错误处理机制,提高代码可维护性
- 日志记录:合理配置日志级别,便于调试和监控
- 代码组织:
- 将模型定义分离到单独的文件
- 使用服务层封装业务逻辑
- 实现中间件处理通用逻辑