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

📅 2026/6/18 16:24:26
【MyBatis】从入门到精通(下):缓存机制与MyBatis-Plus
目录六、缓存机制6.1 为什么需要缓存6.2 一级缓存Local Cache6.3 二级缓存Global Cache6.4 缓存执行顺序6.5 什么时候用缓存七、MyBatis-Plus增强7.1 是什么7.2 核心特性7.3 快速集成7.4 BaseMapper — 基础CRUD7.5 LambdaQueryWrapper — 条件构造器7.6 IService — 通用Service7.7 分页插件7.8 逻辑删除7.9 代码生成器八、总结8.1 本篇要点回顾8.2 MyBatis vs MyBatis-Plus8.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缓存失效条件SqlSession 关闭执行增删改操作会清空当前 SqlSession 的缓存手动调用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 MapObject, 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 namespacecom.example.mapper.UserMapper !-- 开启二级缓存 -- cache/ !-- 或者配置详细参数 -- cache evictionLRU !-- 缓存回收策略LRU、FIFO、SOFT、WEAK -- flushInterval60000 !-- 刷新间隔毫秒 -- size1024 !-- 缓存对象数量 -- readOnlytrue/ !-- 是否只读 -- /mapper步骤2实体类实现 Serializable 接口Data public class User implements Serializable { private Long id; private String name; private Integer age; }步骤3在 MyBatis 配置中开启settings setting namecacheEnabled valuetrue/ /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时回收注意事项二级缓存是事务性的SqlSession 关闭或提交后数据才会写入二级缓存增删改会清空缓存同一 Mapper 下的增删改操作会清空二级缓存多表查询慎用如果多个 Mapper 关联查询可能导致缓存不一致6.4 缓存执行顺序查询请求 ↓ 二级缓存如果开启 ↓ 命中直接返回 一级缓存 ↓ 命中直接返回 数据库查询 ↓ 结果写入一级缓存 ↓ SqlSession 关闭后写入二级缓存6.5 什么时候用缓存适合用缓存的场景查询频繁数据变化少对实时性要求不高数据量不大不适合用缓存的场景数据频繁变化对实时性要求高数据量很大缓存最佳实践一级缓存默认开启无需额外配置二级缓存按需开启适合读多写少的场景避免多表查询缓存可能导致数据不一致合理设置缓存策略根据业务特点选择 LRU/FIFO七、MyBatis-Plus增强7.1 是什么MyBatis-Plus简称MP是MyBatis的增强工具包只做增强不做改变。它解决了MyBatis的一个痛点单表CRUD代码太多。// MyBatis中每个Mapper都要写这些基础方法 public interface UserMapper { User findById(Long id); ListUser findAll(); int insert(User user); int update(User user); int deleteById(Long id); } // MyBatis-Plus中继承BaseMapper自动拥有这些方法 public interface UserMapper extends BaseMapperUser { // 不需要写任何方法即可拥有完整的CRUD能力 }7.2 核心特性无侵入引入 MP 不会影响现有 MyBatis 代码损耗小启动即自动注入基本 CRUD性能无损耗强大的 CRUD内置通用 Mapper 和 Service条件构造器类型安全的条件构建代码生成器快速生成代码分页插件内置分页支持7.3 快速集成添加依赖dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.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 BaseMapperUser { // 不需要写任何方法 }查询操作Service public class UserService { Autowired private UserMapper userMapper; // 根据ID查询 public User getById(Long id) { return userMapper.selectById(id); } // 查询列表 public ListUser list() { return userMapper.selectList(null); } // 条件查询 public ListUser listByAge(Integer age) { QueryWrapperUser wrapper new QueryWrapper(); wrapper.eq(age, age); return userMapper.selectList(wrapper); } // 分页查询 public IPageUser page(int pageNum, int pageSize) { PageUser 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) { UpdateWrapperUser 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(ListLong ids) { userMapper.deleteBatchIds(ids); }7.5 LambdaQueryWrapper — 条件构造器条件构造器是 MyBatis-Plus 最强大的特性之一基本用法// QueryWrapper字符串方式 QueryWrapperUser 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 ListUser users userMapper.selectList(wrapper);LambdaQueryWrapper推荐使用方法引用避免字符串拼写错误// LambdaQueryWrapper类型安全 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getAge, 25) .like(User::getUserName, 张) .between(User::getAge, 18, 30) .isNotNull(User::getEmail) .orderByDesc(User::getCreateTime); ListUser users userMapper.selectList(wrapper);动态条件// 条件为false时不加入SQL LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(name), User::getUserName, name) .eq(age ! null, User::getAge, age) .ge(startTime ! null, User::getCreateTime, startTime);复杂查询// 分组查询 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.select(User::getAge, User::getStatus) .groupBy(User::getAge, User::getStatus) .having(COUNT(*) 1); // 嵌套条件 LambdaQueryWrapperUser 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)); // 子查询 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.inSql(User::getId, SELECT user_id FROM orders WHERE amount 1000);7.6 IService — 通用ServiceMyBatis-Plus 还提供了IService接口提供更多便捷方法// Service接口 public interface UserService extends IServiceUser { // 自定义方法 } // Service实现 Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { // 继承的方法包括 // save() - 插入 // saveBatch() - 批量插入 // remove() - 删除 // update() - 更新 // getById() - 根据ID查询 // list() - 查询列表 // page() - 分页查询 // count() - 计数 // saveOrUpdate() - 保存或更新 }使用示例Service public class UserController { Autowired private UserService userService; public void demo() { // 批量插入 ListUser users Arrays.asList( new User(张三, 25, zhangsanexample.com), new User(李四, 30, lisiexample.com) ); userService.saveBatch(users, 100); // 保存或更新根据ID判断 User user new User(1L, 张三, 26, zhangsanexample.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 IPageUser listUsers(RequestParam int pageNum, RequestParam int pageSize) { PageUser page new Page(pageNum, pageSize); LambdaQueryWrapperUser 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 07.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 本篇要点回顾缓存机制一级缓存默认开启、二级缓存按需开启MyBatis-PlusMyBatis的增强工具包BaseMapper开箱即用的CRUDLambdaQueryWrapper类型安全的条件构造IService更多便捷方法分页插件简单易用8.2 MyBatis vs MyBatis-Plus特性MyBatisMyBatis-PlusCRUD手写SQL自动生成条件查询手写SQL条件构造器分页手动实现内置插件逻辑删除手动实现注解支持代码生成无内置生成器8.3 面试高频考点一级缓存与二级缓存的区别MyBatis-Plus的BaseMapper提供了哪些方法LambdaQueryWrapper的使用MyBatis的执行流程8.4 完整系列上篇MyBatis从入门到精通上核心概念与基础使用下篇本文参考资料MyBatis官方文档MyBatis-Plus官方文档