news 2026/6/18 16:24:24

【MyBatis】从入门到精通(下):缓存机制与MyBatis-Plus

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【MyBatis】从入门到精通(下):缓存机制与MyBatis-Plus

目录

六、缓存机制

6.1 为什么需要缓存

6.2 一级缓存(Local Cache)

6.3 二级缓存(Global Cache)

6.4 缓存执行顺序

6.5 什么时候用缓存

七、MyBatis-Plus增强

7.1 是什么

7.2 核心特性

7.3 快速集成

7.4 BaseMapper — 基础CRUD

7.5 LambdaQueryWrapper — 条件构造器

7.6 IService — 通用Service

7.7 分页插件

7.8 逻辑删除

7.9 代码生成器

八、总结

8.1 本篇要点回顾

8.2 MyBatis vs MyBatis-Plus

8.3 面试高频考点

8.4 学习建议

8.5 完整系列


本文是MyBatis系列的下篇,将介绍缓存机制和MyBatis-Plus的增强功能。


六、缓存机制

6.1 为什么需要缓存

每次查询都访问数据库,性能开销很大。缓存的作用是:将查询结果暂存,下次查询相同数据时直接从缓存获取,减少数据库访问。

┌──────────┐ ┌──────────┐ ┌──────────┐ │ 应用程序 │ ←→ │ 缓存 │ ←→ │ 数据库 │ └──────────┘ └──────────┘ └──────────┘ 第一次查询:缓存没有 → 查数据库 → 结果存入缓存 第二次查询:缓存有 → 直接返回(不查数据库)

MyBatis 提供了两级缓存:

缓存级别作用范围默认状态生命周期
一级缓存SqlSession级别✅ 开启SqlSession关闭即失效
二级缓存Mapper级别❌ 关闭应用级别,全局有效

6.2 一级缓存(Local Cache)

一级缓存是 SqlSession 级别的缓存,同一个 SqlSession 中执行相同的查询,第二次会直接从缓存获取。

// 同一个 SqlSession SqlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class); // 第一次查询:查数据库,结果存入缓存 User user1 = mapper.findById(1L); System.out.println(user1); // SQL: SELECT * FROM user WHERE id = 1 // 第二次查询:命中缓存,不查数据库 User user2 = mapper.findById(1L); System.out.println(user2); // 无 SQL 执行 // user1 和 user2 是同一个对象 System.out.println(user1 == user2); // true

缓存失效条件

  1. SqlSession 关闭
  2. 执行增删改操作(会清空当前 SqlSession 的缓存)
  3. 手动调用clearCache()
SqlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class); // 第一次查询 User user1 = mapper.findById(1L); // 执行更新操作 mapper.update(new User(1L, "新名字", 25)); // 第二次查询:缓存已失效,重新查数据库 User user2 = mapper.findById(1L);

源码分析

一级缓存底层是一个 HashMap:

// PerpetualCache 类 public class PerpetualCache implements Cache { private Map<Object, Object> cache = new HashMap<>(); @Override public void putObject(Object key, Object value) { cache.put(key, value); } @Override public Object getObject(Object key) { return cache.get(key); } }

6.3 二级缓存(Global Cache)

二级缓存是 Mapper 级别的缓存,多个 SqlSession 共享同一个 Mapper 的缓存。

┌─────────────┐ │ Mapper │ ← 二级缓存(所有 SqlSession 共享) └──────┬──────┘ │ ┌────┴────┐ │ │ ▼ ▼ ┌─────┐ ┌─────┐ │ SS1 │ │ SS2 │ ← SqlSession(各自有一级缓存) └─────┘ └─────┘

开启二级缓存

步骤1:在 Mapper.xml 中开启

<mapper namespace="com.example.mapper.UserMapper"> <!-- 开启二级缓存 --> <cache/> <!-- 或者配置详细参数 --> <cache eviction="LRU" <!-- 缓存回收策略:LRU、FIFO、SOFT、WEAK --> flushInterval="60000" <!-- 刷新间隔(毫秒) --> size="1024" <!-- 缓存对象数量 --> readOnly="true"/> <!-- 是否只读 --> </mapper>

步骤2:实体类实现 Serializable 接口

@Data public class User implements Serializable { private Long id; private String name; private Integer age; }

步骤3:在 MyBatis 配置中开启

<settings> <setting name="cacheEnabled" value="true"/> </settings>

使用示例

// SqlSession 1 SqlSession session1 = sqlSessionFactory.openSession(); UserMapper mapper1 = session1.getMapper(UserMapper.class); User user1 = mapper1.findById(1L); // 查数据库,存入二级缓存 session1.close(); // 关闭 SqlSession,数据提交到二级缓存 // SqlSession 2 SqlSession session2 = sqlSessionFactory.openSession(); UserMapper mapper2 = session2.getMapper(UserMapper.class); User user2 = mapper2.findById(1L); // 命中二级缓存,不查数据库 session2.close();

缓存回收策略

策略说明
LRU最近最少使用(默认)
FIFO先进先出
SOFT软引用,内存不足时回收
WEAK弱引用,GC时回收

注意事项

  1. 二级缓存是事务性的:SqlSession 关闭或提交后,数据才会写入二级缓存
  2. 增删改会清空缓存:同一 Mapper 下的增删改操作会清空二级缓存
  3. 多表查询慎用:如果多个 Mapper 关联查询,可能导致缓存不一致

6.4 缓存执行顺序

查询请求 ↓ 二级缓存(如果开启) ↓ 命中?直接返回 一级缓存 ↓ 命中?直接返回 数据库查询 ↓ 结果写入一级缓存 ↓ SqlSession 关闭后写入二级缓存

6.5 什么时候用缓存

适合用缓存的场景

  • 查询频繁,数据变化少
  • 对实时性要求不高
  • 数据量不大

不适合用缓存的场景

  • 数据频繁变化
  • 对实时性要求高
  • 数据量很大

缓存最佳实践

  1. 一级缓存:默认开启,无需额外配置
  2. 二级缓存:按需开启,适合读多写少的场景
  3. 避免多表查询缓存:可能导致数据不一致
  4. 合理设置缓存策略:根据业务特点选择 LRU/FIFO

七、MyBatis-Plus增强

7.1 是什么

MyBatis-Plus(简称MP)是MyBatis的增强工具包,只做增强,不做改变

它解决了MyBatis的一个痛点:单表CRUD代码太多

// MyBatis中:每个Mapper都要写这些基础方法 public interface UserMapper { User findById(Long id); List<User> findAll(); int insert(User user); int update(User user); int deleteById(Long id); } // MyBatis-Plus中:继承BaseMapper,自动拥有这些方法 public interface UserMapper extends BaseMapper<User> { // 不需要写任何方法,即可拥有完整的CRUD能力 }

7.2 核心特性

  1. 无侵入:引入 MP 不会影响现有 MyBatis 代码
  2. 损耗小:启动即自动注入基本 CRUD,性能无损耗
  3. 强大的 CRUD:内置通用 Mapper 和 Service
  4. 条件构造器:类型安全的条件构建
  5. 代码生成器:快速生成代码
  6. 分页插件:内置分页支持

7.3 快速集成

添加依赖

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency>

配置

mybatis-plus: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true global-config: db-config: id-type: auto # 主键策略:自增

实体类注解

@Data @TableName("sys_user") // 表名映射 public class User { @TableId(type = IdType.AUTO) // 主键自增 private Long id; @TableField("user_name") // 字段映射 private String userName; private Integer age; @TableLogic // 逻辑删除 private Integer deleted; @TableField(fill = FieldFill.INSERT) // 自动填充 private LocalDateTime createTime; }

7.4 BaseMapper — 基础CRUD

继承BaseMapper即可拥有完整的单表 CRUD 能力:

@Mapper public interface UserMapper extends BaseMapper<User> { // 不需要写任何方法 }

查询操作

@Service public class UserService { @Autowired private UserMapper userMapper; // 根据ID查询 public User getById(Long id) { return userMapper.selectById(id); } // 查询列表 public List<User> list() { return userMapper.selectList(null); } // 条件查询 public List<User> listByAge(Integer age) { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("age", age); return userMapper.selectList(wrapper); } // 分页查询 public IPage<User> page(int pageNum, int pageSize) { Page<User> page = new Page<>(pageNum, pageSize); return userMapper.selectPage(page, null); } // 查询总记录数 public Long count() { return userMapper.selectCount(null); } }

插入操作

// 插入单条 public void insert(User user) { userMapper.insert(user); // 自增ID会回填到user对象 System.out.println("插入后ID: " + user.getId()); }

更新操作

// 根据ID更新(null字段不会更新) public void updateById(User user) { userMapper.updateById(user); } // 条件更新 public void updateAgeByName(String userName, Integer newAge) { UpdateWrapper<User> wrapper = new UpdateWrapper<>(); wrapper.eq("user_name", userName) .set("age", newAge); userMapper.update(null, wrapper); }

删除操作

// 根据ID删除 public void deleteById(Long id) { userMapper.deleteById(id); } // 批量删除 public void deleteByIds(List<Long> ids) { userMapper.deleteBatchIds(ids); }

7.5 LambdaQueryWrapper — 条件构造器

条件构造器是 MyBatis-Plus 最强大的特性之一:

基本用法

// QueryWrapper(字符串方式) QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("age", 25) // age = 25 .like("name", "张") // name LIKE '%张%' .between("age", 18, 30) // age BETWEEN 18 AND 30 .orderByDesc("create_time"); // ORDER BY create_time DESC List<User> users = userMapper.selectList(wrapper);

LambdaQueryWrapper(推荐)

使用方法引用,避免字符串拼写错误:

// LambdaQueryWrapper(类型安全) LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getAge, 25) .like(User::getUserName, "张") .between(User::getAge, 18, 30) .isNotNull(User::getEmail) .orderByDesc(User::getCreateTime); List<User> users = userMapper.selectList(wrapper);

动态条件

// 条件为false时不加入SQL LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(name), User::getUserName, name) .eq(age != null, User::getAge, age) .ge(startTime != null, User::getCreateTime, startTime);

复杂查询

// 分组查询 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.select(User::getAge, User::getStatus) .groupBy(User::getAge, User::getStatus) .having("COUNT(*) > 1"); // 嵌套条件 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.and(w -> w.eq(User::getStatus, 1).or().eq(User::getStatus, 2)) .and(w -> w.ge(User::getAge, 18).le(User::getAge, 60)); // 子查询 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.inSql(User::getId, "SELECT user_id FROM orders WHERE amount > 1000");

7.6 IService — 通用Service

MyBatis-Plus 还提供了IService接口,提供更多便捷方法:

// Service接口 public interface UserService extends IService<User> { // 自定义方法 } // Service实现 @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { // 继承的方法包括: // save() - 插入 // saveBatch() - 批量插入 // remove() - 删除 // update() - 更新 // getById() - 根据ID查询 // list() - 查询列表 // page() - 分页查询 // count() - 计数 // saveOrUpdate() - 保存或更新 }

使用示例:

@Service public class UserController { @Autowired private UserService userService; public void demo() { // 批量插入 List<User> users = Arrays.asList( new User("张三", 25, "zhangsan@example.com"), new User("李四", 30, "lisi@example.com") ); userService.saveBatch(users, 100); // 保存或更新(根据ID判断) User user = new User(1L, "张三", 26, "zhangsan@example.com"); userService.saveOrUpdate(user); } }

7.7 分页插件

配置

@Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 分页插件 PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setMaxLimit(500L); interceptor.addInnerInterceptor(paginationInterceptor); // 乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }

使用

@GetMapping("/users") public IPage<User> listUsers(@RequestParam int pageNum, @RequestParam int pageSize) { Page<User> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.orderByDesc(User::getCreateTime); return userService.page(page, wrapper); }

7.8 逻辑删除

// 实体类配置 @Data public class User { @TableId(type = IdType.AUTO) private Long id; private String userName; @TableLogic // 逻辑删除字段 private Integer deleted; } // 调用deleteById时,实际执行: // UPDATE user SET deleted = 1 WHERE id = ? AND deleted = 0 // 查询时自动过滤已删除数据: // SELECT * FROM user WHERE deleted = 0

7.9 代码生成器

根据数据库表结构快速生成代码:

public class CodeGenerator { public static void main(String[] args) { // 数据库配置 DataSourceConfig dataSourceConfig = new DataSourceConfig .Builder("jdbc:mysql://localhost:3306/demo") .username("root") .password("password") .build(); // 全局配置 GlobalConfig globalConfig = new GlobalConfig.Builder() .outputDir("D:/project/src/main/java") .author("developer") .enableSwagger() .build(); // 包配置 PackageConfig packageConfig = new PackageConfig.Builder() .parent("com.example") .moduleName("system") .build(); // 策略配置 StrategyConfig strategyConfig = new StrategyConfig.Builder() .addInclude("sys_user", "sys_role") // 包含的表 .addTablePrefix("sys_") // 表前缀 .entityBuilder().enableLombok().build() .mapperBuilder().enableMapperAnnotation().build() .controllerBuilder().enableRestStyle().build() .build(); // 执行生成 new AutoGenerator(dataSourceConfig) .global(globalConfig) .packageInfo(packageConfig) .strategy(strategyConfig) .execute(); } }

八、总结

8.1 本篇要点回顾

  1. 缓存机制:一级缓存(默认开启)、二级缓存(按需开启)
  2. MyBatis-Plus:MyBatis的增强工具包
  3. BaseMapper:开箱即用的CRUD
  4. LambdaQueryWrapper:类型安全的条件构造
  5. IService:更多便捷方法
  6. 分页插件:简单易用

8.2 MyBatis vs MyBatis-Plus

特性MyBatisMyBatis-Plus
CRUD手写SQL自动生成
条件查询手写SQL条件构造器
分页手动实现内置插件
逻辑删除手动实现注解支持
代码生成内置生成器

8.3 面试高频考点

  • 一级缓存与二级缓存的区别
  • MyBatis-Plus的BaseMapper提供了哪些方法
  • LambdaQueryWrapper的使用
  • MyBatis的执行流程

8.4 完整系列


参考资料

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/18 16:18:43

MAE掩码自编码器:从视觉自监督到ViT大模型预训练实战

1. 项目概述&#xff1a;从“看图补画”到视觉大模型的新范式如果你玩过“你画我猜”或者小时候做过“根据局部猜整体”的题目&#xff0c;那你已经对“掩码自编码器”的核心思想有了最朴素的直觉。在计算机视觉领域&#xff0c;我们一直希望机器能像人一样&#xff0c;通过观察…

作者头像 李华
网站建设 2026/6/18 16:14:01

从零构建个人知识管理系统:轻量级信息抓取与聚合实践

1. 项目概述&#xff1a;从“Claw”到个人知识管理系统的蜕变最近在整理自己的数字生活时&#xff0c;发现了一个普遍存在的痛点&#xff1a;信息碎片化。我们每天在微信、浏览器、Kindle、PDF文档、甚至聊天记录里&#xff0c;会接触到大量有价值的信息片段——一句深刻的观点…

作者头像 李华
网站建设 2026/6/18 16:04:51

OpenCore Legacy Patcher完整指南:免费让老旧Mac焕发新生

OpenCore Legacy Patcher完整指南&#xff1a;免费让老旧Mac焕发新生 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 你知道吗&#xff1f;那些被苹果官方&qu…

作者头像 李华
网站建设 2026/6/18 16:03:39

全球AI素养政策大不同:中国重战略,各国关切各有侧重

各国AI素养举措概况2025年8月&#xff0c;韩国国会通过《初等及中等教育法》修正案&#xff0c;将此前推行的 "AI数字教科书" 从官方教科书降级为教学参考资料&#xff0c;地方学校面临经费收紧和课时削减。同年9月&#xff0c;爱沙尼亚启动AI Leap 2025&#xff0c;…

作者头像 李华
网站建设 2026/6/18 16:02:02

形态学操作实战指南:图像二值化与结构元设计

1. 项目概述&#xff1a;为什么形态学操作是图像处理里最被低估的“清洁工”在计算机视觉的实际项目里&#xff0c;我见过太多人把精力全砸在模型结构、损失函数或者数据增强上&#xff0c;结果一跑推理&#xff0c;输出图上全是毛刺、断线、噪点斑块——就像刚洗完澡没擦干就穿…

作者头像 李华