Skip to content

Sequelize 模型实例操作 - 创建、更新、删除的各种方法

发布时间:2024-02-05
作者:一介布衣
标签:Sequelize, 模型实例, CRUD操作, 数据操作

前言

上一篇文章我们详细学习了模型定义的各种细节,今天咱们来学习如何操作这些模型实例。说白了,就是怎么对数据进行增删改查。

我记得刚开始用 Sequelize 的时候,总是搞不清楚什么时候用 create,什么时候用 build,什么时候用 save。还有各种更新方法,updatesaveincrement 等等,真的是让人头大。后来慢慢摸索,发现每种方法都有自己的使用场景。

今天我就把这些实例操作的方法整理出来,让大家能够根据不同场景选择最合适的方法。

创建实例的多种方式

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 的基础查询操作,包括各种查询条件和方法的使用。


相关文章推荐:

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