Sequelize 模型实例操作 - 创建、更新、删除的各种方法
发布时间:2024-02-05
作者:一介布衣
标签:Sequelize, 模型实例, CRUD操作, 数据操作
前言
上一篇文章我们详细学习了模型定义的各种细节,今天咱们来学习如何操作这些模型实例。说白了,就是怎么对数据进行增删改查。
我记得刚开始用 Sequelize 的时候,总是搞不清楚什么时候用 create
,什么时候用 build
,什么时候用 save
。还有各种更新方法,update
、save
、increment
等等,真的是让人头大。后来慢慢摸索,发现每种方法都有自己的使用场景。
今天我就把这些实例操作的方法整理出来,让大家能够根据不同场景选择最合适的方法。
创建实例的多种方式
1. create() - 直接创建并保存
这是最常用的方法,一步到位创建并保存到数据库:
javascript
// 基本用法
const user = await User.create({
name: '张三',
email: 'zhangsan@example.com',
age: 25
});
console.log(user.id); // 自动生成的ID
console.log(user.createdAt); // 自动生成的创建时间
适用场景:大部分情况下的数据创建
2. build() + save() - 分步创建
有时候我们需要在保存前做一些处理:
javascript
// 先构建实例,不保存到数据库
const user = User.build({
name: '李四',
email: 'lisi@example.com'
});
// 可以在这里做一些处理
user.age = 30;
user.status = 'active';
// 手动保存到数据库
await user.save();
适用场景:需要在保存前进行复杂处理的情况
3. bulkCreate() - 批量创建
当需要一次性创建多条记录时:
javascript
const users = await User.bulkCreate([
{ name: '用户1', email: 'user1@example.com' },
{ name: '用户2', email: 'user2@example.com' },
{ name: '用户3', email: 'user3@example.com' }
], {
validate: true, // 启用验证
ignoreDuplicates: true // 忽略重复数据
});
console.log(`创建了 \${users.length} 个用户`);
性能对比:
javascript
// 慢:逐个创建
for (const userData of userList) {
await User.create(userData);
}
// 快:批量创建
await User.bulkCreate(userList);
4. findOrCreate() - 查找或创建
这个方法特别实用,避免重复创建:
javascript
const [user, created] = await User.findOrCreate({
where: { email: 'test@example.com' },
defaults: {
name: '测试用户',
age: 25,
status: 'active'
}
});
if (created) {
console.log('创建了新用户');
} else {
console.log('用户已存在');
}
实际应用场景:
javascript
// 社交登录场景
async function handleSocialLogin(socialData) {
const [user, created] = await User.findOrCreate({
where: { socialId: socialData.id },
defaults: {
name: socialData.name,
email: socialData.email,
avatar: socialData.avatar,
provider: 'wechat'
}
});
if (!created) {
// 更新最后登录时间
await user.update({ lastLoginAt: new Date() });
}
return user;
}
更新实例的各种方法
1. save() - 实例方法
修改实例属性后调用 save():
javascript
const user = await User.findByPk(1);
// 修改属性
user.name = '新名字';
user.age = 30;
// 保存到数据库
await user.save();
// 只保存特定字段
await user.save({ fields: ['name'] });
注意事项:
javascript
// 错误的做法:直接修改数据库字段
user.createdAt = new Date(); // 这样修改不会保存
// 正确的做法:使用 setDataValue
user.setDataValue('createdAt', new Date());
await user.save();
2. update() - 实例方法
直接更新实例:
javascript
const user = await User.findByPk(1);
await user.update({
name: '更新的名字',
age: 35
});
// 带选项的更新
await user.update({
name: '新名字'
}, {
fields: ['name'], // 只更新指定字段
validate: true // 启用验证
});
3. Model.update() - 类方法
批量更新多条记录:
javascript
// 更新所有符合条件的记录
const [affectedCount] = await User.update(
{ status: 'inactive' }, // 要更新的数据
{
where: { lastLoginAt: null }, // 更新条件
validate: true // 启用验证
}
);
console.log(`更新了 \${affectedCount} 条记录`);
实际应用:
javascript
// 批量激活用户
async function activateUsers(userIds) {
const [count] = await User.update(
{
status: 'active',
activatedAt: new Date()
},
{
where: {
id: userIds,
status: 'pending'
}
}
);
return count;
}
4. increment() / decrement() - 数值操作
对数值字段进行增减操作:
javascript
const user = await User.findByPk(1);
// 增加
await user.increment('loginCount'); // +1
await user.increment('loginCount', { by: 5 }); // +5
await user.increment(['loginCount', 'score']); // 多字段 +1
// 减少
await user.decrement('score', { by: 10 });
// 复杂操作
await user.increment({
loginCount: 1,
score: 10,
level: 1
});
实际应用场景:
javascript
// 文章浏览量统计
async function incrementViewCount(articleId) {
await Article.increment('viewCount', {
where: { id: articleId }
});
}
// 用户积分操作
async function updateUserScore(userId, points) {
const user = await User.findByPk(userId);
if (points > 0) {
await user.increment('score', { by: points });
} else {
await user.decrement('score', { by: Math.abs(points) });
}
}
5. reload() - 重新加载
从数据库重新加载实例数据:
javascript
const user = await User.findByPk(1);
// 其他地方可能修改了数据
// 重新从数据库加载
await user.reload();
// 只重新加载特定字段
await user.reload({ attributes: ['name', 'email'] });
删除实例的方法
1. destroy() - 实例方法
删除单个实例:
javascript
const user = await User.findByPk(1);
// 软删除(如果启用了 paranoid)
await user.destroy();
// 硬删除(永久删除)
await user.destroy({ force: true });
2. Model.destroy() - 类方法
批量删除:
javascript
// 删除符合条件的记录
const deletedCount = await User.destroy({
where: {
status: 'inactive',
lastLoginAt: {
[Op.lt]: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000) // 一年前
}
}
});
console.log(`删除了 \${deletedCount} 个用户`);
3. restore() - 恢复软删除
如果启用了软删除,可以恢复被删除的记录:
javascript
// 恢复单个实例
const user = await User.findByPk(1, { paranoid: false }); // 包含已删除的
if (user && user.deletedAt) {
await user.restore();
}
// 批量恢复
await User.restore({
where: { email: { [Op.like]: '%@company.com' } }
});
实例状态管理
检查实例状态
javascript
const user = User.build({ name: '测试' });
console.log(user.isNewRecord); // true - 新记录
console.log(user.changed()); // [] - 没有修改的字段
user.name = '修改后的名字';
console.log(user.changed()); // ['name'] - 修改的字段
console.log(user.changed('name')); // true - name字段是否修改
await user.save();
console.log(user.isNewRecord); // false - 已保存
获取原始值
javascript
const user = await User.findByPk(1);
user.name = '新名字';
console.log(user.name); // '新名字'
console.log(user.previous('name')); // 原来的名字
console.log(user.getDataValue('name')); // '新名字'
重置修改
javascript
const user = await User.findByPk(1);
user.name = '修改的名字';
user.age = 30;
// 重置所有修改
user.reload();
// 或者手动重置
user.setDataValue('name', user.previous('name'));
事务中的实例操作
基本事务操作
javascript
const transaction = await sequelize.transaction();
try {
// 在事务中创建
const user = await User.create({
name: '张三',
email: 'zhangsan@example.com'
}, { transaction });
// 在事务中更新
await user.update({
status: 'active'
}, { transaction });
// 提交事务
await transaction.commit();
} catch (error) {
// 回滚事务
await transaction.rollback();
throw error;
}
自动管理事务
javascript
await sequelize.transaction(async (t) => {
const user = await User.create({
name: '李四',
email: 'lisi@example.com'
}, { transaction: t });
await user.update({
status: 'active'
}, { transaction: t });
// 自动提交或回滚
});
实例钩子(Hooks)
常用钩子
javascript
const User = sequelize.define('User', {
name: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
}, {
hooks: {
// 创建前
beforeCreate: async (user, options) => {
console.log('即将创建用户:', user.name);
},
// 创建后
afterCreate: async (user, options) => {
console.log('用户创建成功:', user.id);
// 发送欢迎邮件
await sendWelcomeEmail(user.email);
},
// 更新前
beforeUpdate: async (user, options) => {
if (user.changed('email')) {
// 邮箱变更需要重新验证
user.emailVerified = false;
}
},
// 删除前
beforeDestroy: async (user, options) => {
// 清理相关数据
await user.getPosts().then(posts => {
return Promise.all(posts.map(post => post.destroy()));
});
}
}
});
实例级钩子
javascript
const user = await User.findByPk(1);
// 为特定实例添加钩子
user.addHook('beforeUpdate', 'checkPermission', async (user, options) => {
if (!options.bypassPermission) {
// 检查权限
throw new Error('没有权限修改此用户');
}
});
// 更新时会触发钩子
await user.update({ name: '新名字' }, { bypassPermission: true });
性能优化技巧
1. 批量操作优化
javascript
// 慢:逐个操作
for (const userData of userList) {
await User.create(userData);
}
// 快:批量操作
await User.bulkCreate(userList);
// 更快:使用原始查询
await sequelize.query(`
INSERT INTO users (name, email) VALUES
\${userList.map(u => `('${u.name}', '\${u.email}')`).join(',')}
`);
2. 选择性更新
javascript
// 只更新修改的字段
const user = await User.findByPk(1);
user.name = '新名字';
await user.save({ fields: ['name'] });
// 批量更新时指定字段
await User.update(
{ lastLoginAt: new Date() },
{
where: { id: userIds },
fields: ['lastLoginAt'] // 只更新这个字段
}
);
3. 避免 N+1 查询
javascript
// 错误:N+1 查询
const users = await User.findAll();
for (const user of users) {
const posts = await user.getPosts(); // 每个用户都查询一次
}
// 正确:预加载关联
const users = await User.findAll({
include: ['posts'] // 一次查询获取所有数据
});
常见问题解决
1. 并发更新问题
javascript
// 使用版本控制避免并发问题
const User = sequelize.define('User', {
name: DataTypes.STRING,
version: DataTypes.INTEGER
}, {
version: true
});
// 更新时检查版本
try {
await user.update({ name: '新名字' });
} catch (error) {
if (error.name === 'SequelizeOptimisticLockError') {
console.log('数据已被其他用户修改,请刷新后重试');
}
}
2. 大数据量处理
javascript
// 分批处理大量数据
async function processLargeDataset() {
const batchSize = 1000;
let offset = 0;
while (true) {
const users = await User.findAll({
limit: batchSize,
offset: offset,
raw: true // 提高性能
});
if (users.length === 0) break;
// 处理这批数据
await processBatch(users);
offset += batchSize;
}
}
3. 内存泄漏预防
javascript
// 及时释放大对象
async function processUsers() {
let users = await User.findAll({
include: ['posts', 'comments']
});
// 处理数据
await processData(users);
// 释放内存
users = null;
}
实战案例
用户积分系统
javascript
class UserScoreService {
// 增加积分
static async addScore(userId, points, reason) {
const transaction = await sequelize.transaction();
try {
const user = await User.findByPk(userId, { transaction });
// 增加积分
await user.increment('score', {
by: points,
transaction
});
// 记录积分变更日志
await ScoreLog.create({
userId,
points,
reason,
type: 'add'
}, { transaction });
await transaction.commit();
return user.reload();
} catch (error) {
await transaction.rollback();
throw error;
}
}
// 检查并升级用户等级
static async checkLevelUp(userId) {
const user = await User.findByPk(userId);
const currentLevel = user.level;
// 根据积分计算等级
const newLevel = Math.floor(user.score / 1000) + 1;
if (newLevel > currentLevel) {
await user.update({ level: newLevel });
// 发送升级通知
await this.sendLevelUpNotification(user, newLevel);
}
}
}
总结
今天我们深入学习了 Sequelize 模型实例的各种操作方法:
- ✅ 创建实例的多种方式和使用场景
- ✅ 更新实例的各种方法和性能考虑
- ✅ 删除和恢复操作
- ✅ 实例状态管理和钩子使用
- ✅ 事务中的实例操作
- ✅ 性能优化技巧和最佳实践
掌握了这些知识,你就能够:
- 根据不同场景选择最合适的操作方法
- 避免常见的性能问题
- 正确处理并发和事务
- 构建健壮的数据操作逻辑
下一篇文章,我们将学习 Sequelize 的基础查询操作,包括各种查询条件和方法的使用。
相关文章推荐:
有问题欢迎留言讨论,我会及时回复大家!