MyBatis-Plus06:IService接口Lambda基本用法

一、LambdaQuery 复杂查询

LambdaQuery 用法:构建复杂查询;

若是简单的根据id的增删改查,还是建议用IService中的xxxById()这类传统方法。

示例:

一般做法:

要加很多if条件判断,很繁琐。

使用LambdaQuery方法:

构建where查询条件的时候,可以加条件!!!

最后,根据你的查询要求:

查询一个:.one()

查询多个:list()

查询数量:count()

分页查询......

二、LambdaUpate 复杂更新

做更新的时候,可以加上condition条件,条件满足才更新。

示例:

【注意】:

LambdaUpdate 最后一定要跟上update()方法,才能执行!

三、乐观锁与悲观锁

示例:

3-1、超市购物的故事

想象你和朋友都想买超市里最后一瓶可乐:

1、悲观锁的做法(悲观派)

  • 你一进超市,就把可乐所在的货架锁起来
  • 只有你拿完可乐,其他人才能靠近
  • 缺点:其他人只能干等着,效率低

2、乐观锁的做法(乐观派)

  • 大家都可以自由看货架
  • 你看到可乐时记住:"哦,现在有 1 瓶"
  • 去收银台结账时,收银员检查:"现在还是 1 瓶吗?"
    • 如果还是 1 瓶 → 卖给你,数量改成 0
    • 如果已经是 0 瓶(被别人买走了)→ 告诉你"不好意思,已经卖完了"

3-2、在数据库中的应用

假设商品表有这样一条数据:

复制代码
商品ID: 1
商品名: iPhone
库存: 10
版本号: 1  ← 关键字段

不用乐观锁的问题

小明和小红同时买 iPhone:

  1. 小明查询:库存 = 10
  2. 小红查询:库存 = 10
  3. 小明买 1 个,更新库存 = 9
  4. 小红买 1 个,更新库存 = 9(错了!应该是 8)

结果:卖了 2 个,但库存只减了 1 个!

使用乐观锁(版本号机制)

sql 复制代码
// 1. 查询商品,记住版本号
SELECT id, stock, version FROM product WHERE id = 1;
// 结果:stock=10, version=1

// 2. 更新时检查版本号
UPDATE product 
SET stock = stock - 1,    -- 库存减 1
    version = version + 1  -- 版本号加 1
WHERE id = 1 
  AND version = 1;  ← 关键:只有版本号没变才更新

执行过程:

  • 小明执行更新:version = 1 ✓ 更新成功,库存变 9,版本变 2
  • 小红执行更新:version = 1 ✗ 失败(版本号已经是 2 了)
  • 小红需要重新查询,拿到新的版本号再试

3-3、MyBatis-Plus 中的使用

java 复制代码
@TableName("product")
public class Product {
    private Long id;
    private Integer stock;
    
    @Version  // 就这么简单!
    private Integer version;
}

// 使用时
Product product = productMapper.selectById(1);
product.setStock(product.getStock() - 1);
productMapper.updateById(product);  
// MyBatis-Plus 会自动检查版本号

乐观锁不一定要用 version 字段,可以直接用业务字段来做比较

3-4、业务字段做乐观锁

1、用余额字段做乐观锁

java 复制代码
// 用户表
public class User {
    private Long id;
    private BigDecimal balance;  // 直接用余额做比较
}

// 扣款操作
public boolean deduct(Long userId, BigDecimal amount) {
    // 1. 先查询当前余额
    User user = userMapper.selectById(userId);
    BigDecimal oldBalance = user.getBalance();
    
    // 2. 计算新余额
    BigDecimal newBalance = oldBalance.subtract(amount);
    if (newBalance.compareTo(BigDecimal.ZERO) < 0) {
        return false; // 余额不足
    }
    
    // 3. 更新时比较余额(乐观锁)
    int rows = userMapper.update(null, 
        new UpdateWrapper<User>()
            .set("balance", newBalance)
            .eq("id", userId)
            .eq("balance", oldBalance)  // 关键:用余额做比较
    );
    
    return rows > 0; // 返回是否更新成功
}

SQL 语句:

sql 复制代码
UPDATE user 
SET balance = 900 
WHERE id = 1 
  AND balance = 1000;  -- 只有余额还是 1000 才更新

2、两种方式对比

方式1:用 version 字段
sql 复制代码
UPDATE user 
SET balance = balance - 100,
    version = version + 1
WHERE id = 1 
  AND version = 5;

优点:

  • 通用性强,适用于所有更新场景
  • 版本号递增,便于追踪修改次数
  • MyBatis-Plus 自动支持(@Version 注解)

缺点:

  • 需要额外的字段
方式2:用业务字段(余额)
sql 复制代码
UPDATE user 
SET balance = 900
WHERE id = 1 
  AND balance = 1000;  -- 直接用余额比较

优点:

  • 不需要额外字段
  • 更直观,业务语义清晰
  • 同时解决了"余额不能为负"的问题

缺点:

  • 只适用于特定场景
  • 需要手动编写代码
3、更简洁的 SQL 写法

其实用余额做乐观锁还可以更简单:

sql 复制代码
-- 直接在 SQL 中判断余额是否足够
UPDATE user 
SET balance = balance - 100
WHERE id = 1 
  AND balance >= 100;  -- 确保余额足够才扣款
java 复制代码
// MyBatis-Plus 写法
int rows = userMapper.update(null,
    new UpdateWrapper<User>()
        .setSql("balance = balance - " + amount)
        .eq("id", userId)
        .ge("balance", amount)  // balance >= amount
);

if (rows == 0) {
    // 更新失败,可能是余额不足或并发冲突
}

乐观锁的核心思想是:更新时检查数据是否被修改过

  • version 只是一种实现方式
  • 任何能反映数据状态的字段都可以用来做乐观锁
  • 对于余额、库存这类数值字段,直接用它们做比较更自然、更高效

这种方式在高并发扣款、扣库存的场景中非常常用!

3-5、总结

乐观锁 = 先不锁,做事的时候再检查有没有被别人改过

  • 优点:不用等待,效率高,适合读多写少的场景
  • 缺点:如果冲突多,需要反复重试
  • 关键:用版本号(或时间戳)来判断数据有没有被修改过

乐观锁的核心思想是:更新时检查数据是否被修改过 。compare and udate

四、为什么乐观锁适合适合读多写少的场景?

4-1、用抢票的例子来说明

场景1:演唱会门票(读多写少)

  • 10000 人在看票(读操作)
  • 只有 100 人能抢到票(写操作)

用乐观锁:

  • 10000 人都能自由查看余票信息,不用等待 ✓
  • 100 人抢票时才检查版本号,大部分人能成功
  • 少数人失败了重试一下就好
  • 效率高

场景2:秒杀活动(读少写多)

  • 10000 人同时抢 1 件商品
  • 几乎所有人都在写操作(下单)

用乐观锁:

复制代码
用户1 尝试购买 → 版本号1,成功!库存=0,版本=2
用户2 尝试购买 → 版本号1,失败!(版本已经是2了)
用户3 尝试购买 → 版本号1,失败!
用户4 尝试购买 → 版本号1,失败!
... 
用户10000 尝试购买 → 版本号1,失败!

问题:9999 人都失败了,需要重新查询、重新尝试,反复循环

  • 大量的数据库查询
  • 大量的更新失败
  • CPU 和数据库压力巨大
  • 效率很低

4-2、数据对比

假设 100 个人操作同一条数据:

读多写少(90 读 + 10 写)

复制代码
乐观锁:
- 90 次读操作:顺畅完成 ✓
- 10 次写操作:大概 8-9 次成功,1-2 次重试 ✓
- 总操作:约 100 次

悲观锁:
- 100 个人都要排队等锁
- 每次都要加锁解锁
- 总操作:100 次,但都要等待

写多读少(10 读 + 90 写)

复制代码
乐观锁:
- 10 次读操作:顺畅完成
- 90 次写操作:只有 1 次成功,89 次失败
- 89 次重试 → 只有 1 次成功,88 次失败
- 88 次重试 → 只有 1 次成功...
- 总操作:可能需要几百次甚至上千次 ✗

悲观锁:
- 100 个人排队,一个一个来
- 总操作:100 次,虽然慢但稳定 ✓

4-3、实际业务举例

适合乐观锁:商品详情页

java 复制代码
// 每天 10000 次浏览,只有 50 次购买
@GetMapping("/product/{id}")
public Product getProduct(@PathParam Long id) {
    return productService.getById(id);  // 读操作,不加锁
}

@PostMapping("/buy")
public void buy(@RequestBody Order order) {
    productService.buy(order);  // 写操作,用乐观锁
}
  • 大部分人只是看看
  • 少数人购买时才竞争
  • 冲突概率低,效率高 ✓

不适合乐观锁:秒杀活动

java 复制代码
// 10000 人同时抢 10 件商品
@PostMapping("/seckill")
public void seckill(@RequestBody Order order) {
    // 用乐观锁会导致 9990 人失败重试
    // 数据库压力爆炸 ✗
}

应该用:

  • 悲观锁(排队)
  • 消息队列(削峰)
  • Redis 分布式锁

4-4、总结

为什么乐观锁适合读多写少?

  1. 读操作不加锁,大家都能自由读取,性能高
  2. 写冲突少,失败重试的次数少,成本低
  3. 写冲突多时,大量失败重试,数据库压力大,性能反而变差

记住一句话: 乐观锁假设"一般不会有冲突",所以适合真的不怎么冲突的场景(读多写少)。如果冲突很多(写多),这个假设就错了,效率会很差。

相关推荐
ruleslol1 天前
MyBatis-Plus02: 常用注解
mybatis-plus
ruleslol3 天前
MyBatis-Plus05:IService接口基本用法
mybatis-plus
ruleslol3 天前
MyBatis-Plus04:自定义SQL
mybatis-plus
识君啊7 天前
MyBatis-Plus 逻辑删除导致唯一索引冲突的解决方案
java·spring boot·mybatis·mybatis-plus·唯一索引·逻辑删除
独断万古他化15 天前
【MyBatis-Plus 进阶】注解配置、条件构造器与自定义 SQL的复杂操作详解
sql·mybatis·mybatis-plus·条件构造器
子沫20201 个月前
使用mybatis-plus、mybatis插入数据库时加密,查询数据库时解密,自定义TypeHandler 加解密使用
数据库·mybatis·mybatis-plus
silence2502 个月前
MyBatis-Plus 报错 Invalid bound statement(insert)?其实是 SqlSessionFactoryBean 踩坑了
mybatis·mybatis-plus
爱学习的小可爱卢2 个月前
JavaEE进阶——MyBatis-Plus新手完全攻略
java·mybatis-plus
言一木2 个月前
mybatis-plus分表实现案例
springboot·mybatis-plus·水平分表