在 Java 后端开发中,数据层的设计直接影响系统性能和用户体验。MyBatis-Plus、MySQL 和 Redis 三者的组合,已经成为高性能业务系统的标配技术栈。它们并非孤立存在,而是通过明确的分工和协作,共同解决 “数据存储”、“操作效率” 和 “访问速度” 三大核心问题。本文将从实际开发场景出发,详解这三者如何协同工作,并给出可落地的实践方案。
一、技术定位:为什么需要这三个技术?
在开始讨论工作流程前,我们首先要明确这三个技术在系统中的角色定位。就像一个高效的仓库系统需要 “存储区”、“快速取货区” 和 “搬运工具”,这三个技术也分别承担着类似的职责。
MySQL:数据的 “最终存储中心”
MySQL 作为关系型数据库,是系统数据的 “唯一可信源”。它就像一个大型仓库的主存储区,负责:
-
持久化存储所有核心业务数据(用户信息、订单记录、商品详情等)
-
通过事务保证数据一致性(比如订单创建和库存扣减必须同时成功或失败)
-
支持复杂查询和关联分析(通过 JOIN、索引、聚合函数等)
但 MySQL 也有明显短板:基于磁盘 IO 操作,在高并发查询场景下响应速度较慢,频繁访问容易成为系统瓶颈。这也是我们需要 Redis 的核心原因。
Redis:数据的 “高速缓存区”
Redis 作为内存数据库,相当于仓库的 “快速取货区”,专门存储高频访问的数据。它的核心价值在于:
-
基于内存操作,读写速度可达 10 万级 QPS(是 MySQL 的 10-100 倍)
-
支持多种数据结构(String、Hash、List 等),满足不同缓存需求
-
可设置过期时间,自动清理不再需要的缓存数据
需要注意的是,Redis 中的数据是 “临时副本”,并非最终存储。它的存在是为了减少对 MySQL 的直接访问,就像我们会把常用工具放在桌面而不是仓库深处。
MyBatis-Plus:数据操作的 “高效工具”
MyBatis-Plus 是基于 MyBatis 的增强工具,相当于连接应用与 MySQL 的 “智能搬运车”。它并不直接参与缓存逻辑,而是通过简化数据访问层代码,让开发者更专注于业务逻辑:
-
自动生成 CRUD 基础操作,无需编写重复 SQL
-
提供条件构造器,动态构建查询条件
-
内置分页、逻辑删除等常用功能
简单说,MyBatis-Plus 让我们操作 MySQL 变得更简单,而 Redis 让我们查询数据变得更快,三者各司其职又紧密配合。
二、核心工作流程:以商品查询为例
理解三者协同机制的最佳方式是通过实际业务场景。我们以 “电商系统商品详情查询” 为例,看看从用户发起请求到获取结果的完整过程中,这三个技术如何配合。
正常查询流程:缓存优先策略
商品详情是典型的 “读多写少” 场景,非常适合使用缓存。
正常查询流程图
用户发起查询请求
│
▼
到达Service层
│
▼
检查Redis缓存
├── 有缓存 → 直接返回结果给用户
│
└── 无缓存 → 调用MyBatis-Plus查询MySQL
│
▼
获取MySQL查询结果
│
▼
将结果写入Redis(设置过期时间)
│
▼
返回结果给用户
完整流程如下:
-
用户发起查询请求:用户在 APP 中点击商品,请求到达 Service 层
-
先查 Redis 缓存:
public ProductDTO getProductDetail(Long productId) {
// 1. 定义缓存key(建议使用业务前缀+唯一标识)
String cacheKey = "product:detail:" + productId;
// 2. 尝试从Redis获取缓存
ProductDTO cachedProduct = redisTemplate.opsForValue().get(cacheKey);
if (cachedProduct != null) {
log.info("缓存命中,直接返回商品:{}", productId);
return cachedProduct; // 缓存命中,直接返回
}
// 后续步骤:缓存未命中时的处理
}
- 缓存未命中,查询 MySQL:当 Redis 中没有对应数据时,通过 MyBatis-Plus 查询 MySQL
// 3. 缓存未命中,查询数据库
Product product = productMapper.selectById(productId);
if (product == null) {
// 处理商品不存在的情况
return null;
}
// 4. 转换为DTO(领域模型转数据传输对象)
ProductDTO productDTO = convertToDTO(product);
- 写入 Redis 缓存:将查询结果存入 Redis,设置合理的过期时间
// 5. 写入缓存(设置30分钟过期,避免缓存永久有效)
redisTemplate.opsForValue().set(
cacheKey,
productDTO,
30,
TimeUnit.MINUTES
);
// 6. 返回结果
return productDTO;
- 结果返回:数据从 Service 层返回给 Controller,最终展示给用户
整个过程中,MyBatis-Plus 负责与 MySQL 交互(productMapper.selectById),Redis 负责缓存数据,而业务代码则协调两者的调用顺序。
数据更新流程:缓存一致性保障
当商品信息发生变更(如价格调整、库存更新)时,需要同步更新缓存,否则会出现 “缓存与数据库数据不一致” 的问题。
数据更新流程图
用户发起更新请求
│
▼
到达Service层
│
▼
调用MyBatis-Plus更新MySQL数据
│
▼
判断更新是否成功(通过受影响行数)
├── 失败 → 抛出异常
│
└── 成功 → 处理Redis缓存
├── 方案A:直接更新Redis缓存(适合更新频率低场景)
│
└── 方案B:删除Redis旧缓存(适合更新频繁场景)
│
▼
返回更新成功结果给用户
更新流程需遵循 “先更数据库,再更缓存” 的原则:
public void updateProduct(ProductUpdateDTO updateDTO) {
// 1. 转换为实体对象
Product product = convertToEntity(updateDTO);
// 2. 先更新数据库(通过MyBatis-Plus)
int rows = productMapper.updateById(product);
if (rows <= 0) {
throw new BusinessException("商品更新失败");
}
// 3. 再更新缓存(两种方案可选)
String cacheKey = "product:detail:" + product.getId();
// 方案A:直接更新缓存(适合更新频率低的场景)
ProductDTO newProductDTO = convertToDTO(productMapper.selectById(product.getId()));
redisTemplate.opsForValue().set(cacheKey, newProductDTO, 30, TimeUnit.MINUTES);
// 方案B:删除旧缓存(适合更新频繁的场景,下次查询自动重建)
// redisTemplate.delete(cacheKey);
}
这里的关键是先保证数据库数据正确,再处理缓存。因为数据库有事务支持,而 Redis 是内存操作,先更新数据库可以最大限度保证数据一致性。
三、进阶实践:解决缓存常见问题
在实际应用中,缓存与数据库的配合会遇到各种问题,需要针对性设计解决方案。结合 MyBatis-Plus 和 Redis 的特性,我们可以处理这些典型场景。
缓存穿透:如何应对不存在的数据查询
缓存穿透是指查询不存在的数据(如查询 ID 为 - 1 的商品),导致每次请求都穿透到数据库,给 MySQL 带来压力。
缓存穿透解决方案流程图
用户查询不存在的数据
│
▼
到达Service层查询Redis
│
▼
Redis无对应缓存(因数据不存在从未缓存)
│
▼
调用MyBatis-Plus查询MySQL
│
▼
MySQL返回空结果(数据不存在)
│
▼
将空结果写入Redis(设置较短过期时间)
│
▼
返回空结果给用户
(后续同ID查询将从Redis获取空结果,不再访问MySQL)
解决方案是 “缓存空结果”:
public ProductDTO getProductDetail(Long productId) {
String cacheKey = "product:detail:" + productId;
// 先查缓存
ProductDTO cachedProduct = redisTemplate.opsForValue().get(cacheKey);
if (cachedProduct != null) {
// 注意:如果是缓存的空对象,也直接返回
return cachedProduct;
}
// 查数据库
Product product = productMapper.selectById(productId);
if (product == null) {
// 缓存空结果(设置较短过期时间,如5分钟)
redisTemplate.opsForValue().set(cacheKey, new ProductDTO(), 5, TimeUnit.MINUTES);
return null;
}
// 正常缓存
ProductDTO productDTO = convertToDTO(product);
redisTemplate.opsForValue().set(cacheKey, productDTO, 30, TimeUnit.MINUTES);
return productDTO;
}
缓存击穿:热点商品的缓存保护
缓存击穿指一个热点 key 突然过期时,大量请求同时穿透到数据库。比如某款热销商品的缓存过期瞬间,大量请求同时查询 MySQL。
缓存击穿的解决方案有很多,但互斥锁方案是最通用、最易实现的一种。其核心思想是:在缓存失效时,只允许一个请求去查询数据库并重建缓存,其他请求等待缓存重建完成后再从缓存获取数据。
互斥锁方案的工作逻辑
就像多人要喝同一壶水:
-
正常时:大家直接从水壶(缓存)取水
-
水壶空了(缓存过期):只允许一个人去打水(查数据库)
-
其他人:等待打水完成后再取水(等缓存重建)
缓存击穿解决方案流程图
热点商品缓存过期
│
▼
大量请求同时到达Service层查询Redis
│
▼
Redis无缓存(已过期)
│
▼
请求竞争获取Redis互斥锁
├── 成功获取锁 → 调用MyBatis-Plus查询MySQL
│ │
│ ▼
│ 将新数据写入Redis
│ │
│ ▼
│ 释放互斥锁
│ │
│ ▼
│ 返回结果
│
└── 未获取锁 → 等待一段时间后重新查询Redis
│
▼
从Redis获取到新缓存(由获锁请求写入)
│
▼
返回结果
解决方案是 “互斥锁”:
public ProductDTO getProductDetail(Long productId) {
String cacheKey = "product:detail:" + productId;
// 先查缓存
ProductDTO cachedProduct = redisTemplate.opsForValue().get(cacheKey);
if (cachedProduct != null) {
return cachedProduct;
}
// 缓存未命中,获取锁
String lockKey = "lock:product:" + productId;
try {
// 尝试获取锁(设置3秒过期,避免死锁)
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
if (locked) {
// 获得锁,查询数据库
Product product = productMapper.selectById(productId);
// ... 处理逻辑 ...
return productDTO;
} else {
// 未获得锁,等待后重试(简单自旋)
Thread.sleep(100);
return getProductDetail(productId); // 递归重试
}
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
缓存雪崩:避免大量缓存同时过期
缓存雪崩指大量缓存 key 在同一时间过期,导致数据库压力骤增。
缓存雪崩解决方案流程图
设置缓存时
│
▼
确定基础过期时间(如30分钟)
│
▼
生成随机偏移量(如-5到5分钟)
│
▼
计算最终过期时间(基础时间+随机偏移量)
│
▼
将数据写入Redis(使用最终过期时间)
│
▼
不同key的过期时间分散
(避免大量key同时过期)
解决方案是 “过期时间随机化”:
// 设置过期时间时添加随机值(30±5分钟)
int baseExpire = 30;
int random = new Random().nextInt(10) - 5; // -5到5之间的随机数
redisTemplate.opsForValue().set(
cacheKey,
productDTO,
baseExpire + random,
TimeUnit.MINUTES
);
四、总结:三者协同的核心原则
MyBatis-Plus、MySQL 和 Redis 的组合之所以成为主流,是因为它们完美解决了数据层的三大核心问题:数据存储(MySQL)、操作效率(MyBatis-Plus)和访问速度(Redis)。总结它们的协同原则:
-
数据流向清晰:查询时 “先缓存后数据库”,更新时 “先数据库后缓存”
-
职责边界明确:MySQL 存核心数据,Redis 存热点数据,MyBatis-Plus 处理数据访问
-
缓存策略灵活:根据业务场景选择缓存粒度、过期时间和更新方式
-
异常场景覆盖:针对穿透、击穿、雪崩等问题设计防护机制
在实际开发中,我们不需要刻意记忆复杂的理论,只需记住一个核心思想:用 MySQL 保证数据正确性,用 Redis 提升访问速度,用 MyBatis-Plus 简化开发工作。这三个技术的组合,让我们能够在保证系统稳定性的同时,轻松应对高并发场景下的性能挑战。
2798

被折叠的 条评论
为什么被折叠?



