问题描述
在MyBatis中,不同SqlSession作用域中开启了两个相同的查询操作。但是在控制台的输出中一直显示没有命中缓存,持续进行SQL查询操作。
代码如下:
public class DaoTest {
private static final Logger LOGGER = Logger.getLogger(DaoTest.class);
private SqlSessionFactory getFactory() throws IOException {
InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
return factory;
}
@Test
public void testGlobalCache() throws IOException {
SqlSession sess1 = getFactory().openSession();
SqlSession sess2 = getFactory().openSession();
SqlSession sess3 = getFactory().openSession();
try {
EmployeeMapper mapper1 = sess1.getMapper(EmployeeMapper.class);
Employee emp1 = mapper1.getEmpByID(1);
sess1.close();
EmployeeMapper mapper2 = sess2.getMapper(EmployeeMapper.class);
Employee emp2 = mapper2.getEmpByID(1);
sess2.close();
EmployeeMapper mapper3 = sess3.getMapper(EmployeeMapper.class);
mapper3.insertEmp(new Employee(null, "XX", "F", "XXX"));
Employee emp3 = mapper3.getEmpByID(1);
sess3.close();
LOGGER.info("emp1 == emp2: " + String.valueOf(emp1 == emp2));
LOGGER.info("emp1 == emp3: " + String.valueOf(emp1 == emp3));
LOGGER.info("emp2 == emp3: " + String.valueOf(emp2 == emp3));
} finally {
}
}
}
控制台输出:
[DEBUG] 2020-03-22 00:57:23,381 method:org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60)
Cache Hit Ratio [xx.xxx.xxx.dao.EmployeeMapper]: 0.0
[DEBUG] 2020-03-22 00:57:23,837 method:org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60)
Cache Hit Ratio [xx.xxx.xxx.dao.EmployeeMapper]: 0.0
[DEBUG] 2020-03-22 00:57:23,932 method:org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60)
Cache Hit Ratio [xx.xxx.xxx.dao.EmployeeMapper]: 0.0
[INFO ] 2020-03-22 00:57:23,961 method:xx.xxx.xxx.DaoTest.testGlobalCache(DaoTest.java:86)
emp1 == emp2: false
[INFO ] 2020-03-22 00:57:23,961 method:xx.xxx.xxx.DaoTest.testGlobalCache(DaoTest.java:87)
emp1 == emp3: false
[INFO ] 2020-03-22 00:57:23,961 method:xx.xxx.xxx.DaoTest.testGlobalCache(DaoTest.java:88)
emp2 == emp3: false
问题排查
- 是否在mybatis-conifg.xml中开启了全局缓存的设置。
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--全局缓存:开启-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 是否在mapper.xml文件中打开了cache设置。
<cache eviction="LRU" flushInterval="600000" size="1024" readOnly="true"/>
<!--<cache/>-->
<!--
eviction: 缓存的回收策略,包括FIFO/LRU/SOFT/WEAK,默认是LRU
flushInterval: 缓存清空间隔,以毫秒为单位
readOnly: 是否只读
true 只读,mybatis从缓存中获取的数据不会修改
不安全,速度快
false 非只读,mybatis通过序列化&反序列化的方式克隆进行数据获取
安全,速度慢
size: 缓存多少元素
type: 自定义缓存全类名(org.apache.ibatis.cache.Cache)
-->
- Bean对象是否可以序列化
public class Employee implements Serializable{
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private String gender;
private String email;
}
上述步骤才可以打开二级缓存,现在在控制台中已经有Cache Hit Ratio 0的输出,证明二级缓存已经打开,但是由于SQL的相关问题,导致的缓存没有命中。继续排查……
4. SQL 语句是否一致(一致)
5. 是否在新的SQL查询之前没有关闭上一个SqlSession,导致命中了一级缓存而不需要查询二级缓存(已关闭)。
最终结论
对上述问题都进行了排查,但是依旧没有找出问题,上网上看了半天,发现了问题:
二级缓存存在于 SqlSessionFactory 生命周期中。
我一直以为二级缓存针对的对象是一个Mapper对象,只要是针对同一个Mapper的操作,都可以实现二级缓存。但是前提是操作必须在同一个SqlSessionFactory 中进行。
在一开始的代码中,通过调用三次getFactory()并打开session,实例化了三个不同的SqlSessionFactory 对象,这样在后续的SQL操作中,是不可能命中二级缓存的。
修改如下
public class DaoTest {
private static final Logger LOGGER = Logger.getLogger(DaoTest.class);
private SqlSessionFactory getFactory() throws IOException {
InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
return factory;
}
@Test
public void testGlobalCache() throws IOException {
SqlSessionFactory factory = getFactory();
SqlSession sess1 = factory.openSession();
SqlSession sess2 = factory.openSession();
SqlSession sess3 = factory.openSession();
try {
EmployeeMapper mapper1 = sess1.getMapper(EmployeeMapper.class);
Employee emp1 = mapper1.getEmpByID(1);
sess1.close();
EmployeeMapper mapper2 = sess2.getMapper(EmployeeMapper.class);
Employee emp2 = mapper2.getEmpByID(1);
sess2.close();
EmployeeMapper mapper3 = sess3.getMapper(EmployeeMapper.class);
mapper3.insertEmp(new Employee(null, "XX", "F", "XXX"));
Employee emp3 = mapper3.getEmpByID(1);
sess3.close();
LOGGER.info("emp1 == emp2: " + String.valueOf(emp1 == emp2));
LOGGER.info("emp1 == emp3: " + String.valueOf(emp1 == emp3));
LOGGER.info("emp2 == emp3: " + String.valueOf(emp2 == emp3));
} finally {
}
}
}
问题解决!