一、核心概念与区别

方法 适用场景 特点
updateById 按主键更新单条记录 实体对象必须包含主键值,更新非空字段
update 按条件更新一条或多条记录 需配合 UpdateWrapper,可灵活指定更新字段和条件,支持无实体更新

二、详细操作步骤

1. 基础实体类与 Mapper

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    @Version  // 乐观锁字段
    private Integer version;
}

@Mapper
public interface UserMapper extends BaseMapper<User> {}

2. updateById 使用示例

// 1. 先查询要修改的实体
User user = userMapper.selectById(1L);

// 2. 修改字段值(非空字段会被更新)
user.setName("UpdatedName");
user.setAge(25);

// 3. 执行更新
int rows = userMapper.updateById(user); // 返回受影响行数

3. update 使用示例

场景1:按条件更新部分字段
// 创建 UpdateWrapper
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper
  .set("age", 30)  // 直接设置字段值
  .set("email", "new@example.com")
  .eq("name", "John");  // WHERE name = 'John'

// 执行更新(实体对象传null)
int rows = userMapper.update(null, wrapper);

生成 SQL
UPDATE user SET age=30, email='new@example.com' WHERE name='John'

场景2:实体对象 + 条件更新
User user = new User();
user.setAge(35);  // 只设置需要更新的字段

LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getName, "John");  // WHERE name = 'John'

int rows = userMapper.update(user, wrapper);

生成 SQL
UPDATE user SET age=35 WHERE name='John'

场景3:链式 Lambda 写法
boolean success = new LambdaUpdateChainWrapper<>(userMapper)
    .set(User::getAge, 40)
    .set(User::getEmail, "lambda@example.com")
    .eq(User::getId, 2L)
    .update();  // 返回布尔值表示是否更新成功

三、常见错误与解决方案

错误现象 原因 解决方案
字段更新为 null 失效 默认更新策略忽略 null 1. 字段加 @TableField(updateStrategy = FieldStrategy.IGNORED)
2. 用 UpdateWrapper.set() 强制更新
乐观锁版本号不匹配 并发更新时版本号已被修改 捕获 OptimisticLockException 重试或提示用户
updateWHERE 条件导致全表更新 未设置条件构造器条件 启用防全表更新插件 BlockAttackInnerInterceptor
更新后实体对象版本号未刷新 乐观锁字段需手动刷新 更新后重新 selectById 获取最新版本号

四、注意事项

  1. 字段更新策略

    • 默认行为(FieldStrategy.NOT_NULL):忽略实体对象中 null 字段
    • 强制更新:@TableField(updateStrategy = FieldStrategy.IGNORED)
  2. 乐观锁使用规范

    // 必须查询最新版本号
    User user = userMapper.selectById(1L);
    user.setName("NewName");
    userMapper.updateById(user);  // 自动校验版本号
    
  3. 全表更新防护

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); // 阻止无 WHERE 的更新
        return interceptor;
    }
    

五、使用技巧

1. 动态字段更新

LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(User::getAge, 30);

// 根据条件动态添加更新字段
if (updateEmail) {
    wrapper.set(User::getEmail, "dynamic@example.com");
}

userMapper.update(null, wrapper);

2. 字段自增/自减

new LambdaUpdateWrapper<User>()
    .setSql("age = age + 1")  // 直接写 SQL 片段
    .eq(User::getId, 1L)
    .update();

3. 批量更新处理

List<User> users = userMapper.selectList(...); 

// 方案1:循环 updateById(带乐观锁)
users.forEach(user -> {
    user.setStatus(1);
    userMapper.updateById(user);
});

// 方案2:一次 update + IN 条件(无乐观锁)
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(User::getStatus, 1)
       .in(User::getId, ids);  // WHERE id IN (1,2,3)
userMapper.update(null, wrapper);

六、最佳实践与性能优化

  1. 更新性能优化

    • 批量更新:优先用 IN 条件单次更新(无乐观锁时),减少 SQL 执行次数
    • 索引优化:确保 WHERE 条件字段有索引,避免全表扫描
  2. 事务控制

    @Transactional(rollbackFor = Exception.class)
    public void batchUpdateUsers(List<Long> ids) {
        // 批量更新操作
    }
    
  3. 更新字段选择

    • 避免 SELECT * 后全字段更新:只查询和更新必要字段
      // 仅查询 id 和 version
      User user = userMapper.selectOne(
          new LambdaQueryWrapper<User>()
             .select(User::getId, User::getVersion)
             .eq(User::getId, 1L)
      );
      
  4. 逻辑删除兼容

    • 自动过滤已删除数据:配置逻辑删除后,更新操作默认忽略 deleted=1 的记录

七、复杂场景处理

联表更新

// 1. 自定义 Mapper 方法
@Update("UPDATE user u, dept d " +
        "SET u.dept_name=d.name " +
        "WHERE u.dept_id=d.id AND d.id=#{deptId}")
void updateDeptName(@Param("deptId") Long deptId);

// 2. 通过 Service 调用
userService.updateDeptName(101L);

条件更新 + 子查询

LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(User::getVipLevel, 2)
       .inSql(User::getId, 
           "SELECT user_id FROM order WHERE amount > 1000");  // 子查询
userMapper.update(null, wrapper);

总结

  • updateById:主键更新首选,自动处理乐观锁
  • update:灵活条件更新,配合 UpdateWrapper 动态控制字段
  • 避坑指南
    • 全表更新防护插件 必须启用
    • 字段更新 null 值需特殊配置
    • 批量更新优先选 IN 条件
  • 性能关键
    • 更新字段最小化
    • WHERE 条件必须走索引
    • 大数量更新分批处理

按此规范使用,可兼顾开发效率与系统安全性,避免 90% 的更新操作陷阱。