真实场景:商城【商品分类】
电商网站 / APP:
首页永远要加载:手机、电脑、服饰、零食…分类列表
- 数据几个月都不改一次
- 几百万用户,每个人打开首页都要查这张表
- 不加二级缓存:每次请求都查数据库,数据库压力爆炸
加 MyBatis 二级缓存后
- 第一个用户访问:查数据库,把分类数据存到全局共享缓存
- 后面所有用户、所有接口请求:不查数据库,直接读缓存
- 只有运营后台修改/新增/删除分类时:自动清空缓存,自动刷新新数据
二级缓存介绍
1. 定义
MyBatis二级缓存是Mapper命名空间(namespace)级别的全局缓存,跨 SqlSession 共享。
多个数据库会话、多个请求,访问同一个 Mapper 下的相同查询,可命中缓存,减少数据库查询。
2. 范围与特性
- 层级:介于一级缓存(SqlSession 本地) 与 第三方分布式缓存中间;
- 作用域:绑定 Mapper.xml 的 namespace,不同namespace缓存相互隔离;
- 生效时机:
- 查询数据先存一级缓存;
- 当前
SqlSession关闭/提交后,数据才会刷新到二级缓存;
- 触发清空:当前 namespace 执行
insert/update/delete,自动清空该命名空间全部二级缓存; - 强制要求:缓存实体类必须实现
Serializable序列化接口; - 开关:全局总开关 + Mapper 单独开启,默认整体关闭。
3. 查询执行顺序
二级缓存 → 一级缓存 → 数据库
优先走缓存,极大降低DB压力。
4. 和一级缓存关键区别
- 一级缓存:单会话私有,会话关闭即销毁,无法跨请求;
- 二级缓存:全局共享,跨会话、跨请求,生命周期更长。
应用场景
✅ 适合使用
读多写少业务
基础数据、静态数据、变动极少的数据,频繁查询、极少修改。
例:字典表、地区数据、商品分类、系统配置、角色权限、常量参数。数据实时性要求低
允许短时间缓存延迟(秒/分钟级不一致),不影响业务逻辑。单应用单体项目
非集群、非分布式部署,无多实例缓存不同步问题,使用原生二级缓存无脏数据风险。高频重复查询
首页展示、列表页、基础信息下拉选项,大量重复SQL查询,需要减轻数据库负载。
不适合使用(禁用场景)
强数据一致性业务
订单、库存、支付、金融、账务、秒杀等,不能容忍缓存延迟、脏数据。分布式/集群部署
多台服务实例,原生二级缓存是单机内存缓存,各节点缓存独立:
A服务修改数据,B服务缓存不刷新,必然出现脏数据。高频更新表
频繁增删改的业务表,缓存会频繁失效、清空,缓存命中率极低,反而增加性能开销。多表关联跨namespace查询
多Mapper联查,单表更新只清空自身缓存,关联数据缓存残留,引发数据错乱。
使用
指 SqlSessionFactory对象中的缓存
由同一个 SqlSessionFactory 对象创建的 SqlSession,共享缓存,即:都可以使用
特点
默认不开启。需要额外配置才能使用
存放的是序列化后的数据,而不是对象(所以从缓存中提取的对象,并不是原对象)
实体类必须实现
Serializable接口
开启二级缓存
修改 mybatis-config.xml
增加:
<setting name="cacheEnabled" value="true"/>
完整配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 给实体类起别名 -->
<typeAliases>
<package name="org.malaoshi.entity" />
</typeAliases>
<!-- 配置环境 。default名字随意起-->
<environments default="mysql">
<!-- id名字必须同上面default名 -->
<environment id="mysql">
<!-- 配置事务管理器类型,JDBC 是 JdbcTransactionFactory.class 的别名 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源。POOLED 是 PooledDataSourceFactory.class 的别名 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3308/mybatis"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/userMapper.xml"></mapper>
<mapper resource="mapper/postMapper.xml"></mapper>
</mappers>
</configuration>
修改 userMapper.xml
加入以下配置:
<!-- 该mapper开启二级缓存 -->
<cache />
详细配置在下面
修改实体类
实体类必须实现 Serializable 接口
package org.malaoshi.entity;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private String id;
private String username;
private String password;
private Integer age;
private String sex;
private String status;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
测试
注意: 要 关闭1个 sqlsession,另一个sqlsession才会用二级缓存
@Test
public void testSelectById() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
User u=mapper.selectById("10001");
String info = String.format("id:%s,用户名:%s,密码:%s", u.getId(), u.getUsername(), u.getPassword());
System.out.println(info);
sqlSession.close();
//重新创建SqlSession
sqlSession = sqlSessionFactory.openSession();
mapper = sqlSession.getMapper(IUserMapper.class);
u=mapper.selectById("10001");
info = String.format("id:%s,用户名:%s,密码:%s", u.getId(), u.getUsername(), u.getPassword());
System.out.println(info);
sqlSession.close();
inputStream.close();
}
在控制台中打印1条查询sql,说明重新创建 SqlSession 后,也没有查询数据库
<cache />配置说明
<cache
eviction="LRU"
flushInterval="60000"
size="1024"
readOnly="true"
/>
缓存淘汰算法
eviction 是缓存的淘汰算法,可选值有:
“LRU”:最近最少使用的:移除最长时间不被使用的对象,默认
见:https://www.malaoshi.top/show_1EF5nwVYkx07.html“FIFO”:先进先出:按对象进入缓存的顺序来移除它们
见:https://www.malaoshi.top/show_1EF5nwVYkx07.html“SOFT”:软引用:移除基于垃圾回收器状态和软引用规则的对象
“WEAK”: 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
缓存过期时间
flashInterval指缓存过期时间,单位为毫秒,60000即为60秒,缺省值为空,即只要容量足够,永不过期
缓存数量
size指缓存多少个对象,默认值为1024
是否只读
readOnly是否只读,如果为true,则所有相同的sql语句返回的是同一个对象(有助于提高性能,但并发操作同一条数据时,可能不安全),如果设置为false,则相同的sql,后面访问的是cache的clone副本。
注意
需要注意的是global caching的作用域是针对Mapper的Namespace而言的,也就是说只在有在这个Namespace内的查询才能共享这个cache
<select useCache="true"> 属性说明
增加 useCache="false" 属性
<select ... useCache="true">
useCache属性,默认为true,即开启二级缓存。通过该属性可以局部控制,比如设置userCache=”false”禁用二级缓存
useCache="false"表示该select语句不使用缓存(即使xml最开头的全局cache启用)
<select flushCache="true"> 属性说明
默认情况下,如果开启二级缓存,insert、update、delete 成功后,会自动刷新相关的缓存
当为select语句时:
flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。当为insert、update、delete语句时:
flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。
一般执行inser操作时,需要刷新缓存,否则会新插入的数据查询不出来。
测试
@Test
public void test() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
User u=mapper.selectById("10001");
String info = String.format("id:%s,用户名:%s,密码:%s", u.getId(), u.getUsername(), u.getPassword());
System.out.println(info);
User user=new User();
user.setId("1000");
user.setUsername("admin2");
user.setPassword("12345678");
int num=mapper.updateById(user);
System.out.println("更新 "+num+" 条记录");
sqlSession.commit();
u=mapper.selectById("10001");
info = String.format("id:%s,用户名:%s,密码:%s", u.getId(), u.getUsername(), u.getPassword());
System.out.println(info);
sqlSession.close();
inputStream.close();
}
由于执行了 update 语句,所以再次查询相同sql时,会查询数据库:
二级缓存的危害
参看以下文章:
http://blog.csdn.net/isea533/article/details/44566257
参考文章,非常感谢:
http://www.cnblogs.com/QQParadise/articles/5109633.html
http://blog.csdn.net/lycemail/article/details/50923245
http://www.360doc.com/content/15/1205/07/29475794_518018352.shtml

