Mybatis的使用(五)

Mybatis的一级缓存

Mybatis默认的一级缓存是SqlSession缓存,也就是说在同一个SqlSession中执行相同的SQL,只有第一次会去查询数据库,接下来再进行该查询都是从Mybatis的缓存中去获取的。当执行增删改操作时,Mybatis会清空SqlSession的缓存。

一级缓存除了有SqlSession级别,还有statement级别,如果需要更改缓存级别,可以在Mybatis的主配置文件(核心配置文件)中进行修改,当我们将级别设置为statement后就等于关闭了一级缓存:

1
2
3
4
5
6
7
<settings>
<!--默认的-->
<!--<setting name="localCacheScope" value="SESSION"/>-->

<!--注意两个value值都是大写,写小写会报错-->
<setting name="localCacheScope" value="STATEMENT"/>
</settings>

当localCacheScope=SESSION时,在同一个SqlSession中进行两次相同的查询:

1
2
3
4
5
6
@Test
public void loadTypeTest(){
List<Husband> allHusband = husbandDao.findAllHusband();
List<Husband> allHusband2 = husbandDao.findAllHusband();

}
1
2
3
[main] DEBUG c.w.m.HusbandDao.findAllHusband - ==>  Preparing: SELECT * FROM husband  
[main] DEBUG c.w.m.HusbandDao.findAllHusband - ==> Parameters:
[main] DEBUG c.w.m.HusbandDao.findAllHusband - <== Total: 4

从打印信息可以看出,调用了两次查询但只执行了一次数据库查询,说明第二次查询是从缓存中进行读取的

当localCacheScope=STATEMENT时,重复进行上述测试结果为:

1
2
3
4
5
6
[main] DEBUG c.w.m.HusbandDao.findAllHusband - ==>  Preparing: SELECT * FROM husband  
[main] DEBUG c.w.m.HusbandDao.findAllHusband - ==> Parameters:
[main] DEBUG c.w.m.HusbandDao.findAllHusband - <== Total: 4
[main] DEBUG c.w.m.HusbandDao.findAllHusband - ==> Preparing: SELECT * FROM husband
[main] DEBUG c.w.m.HusbandDao.findAllHusband - ==> Parameters:
[main] DEBUG c.w.m.HusbandDao.findAllHusband - <== Total: 4

这里进行了两次数据库查询,并没有进行缓存

Mybatis的二级缓存

一级缓存为local cache,二级缓存为cache。cache是跨session的,既在一个SqlSession中缓存的查询结果,在另外一个SqlSession中也是可以使用的,范围比一级缓存更大。

开启二级缓存需要进行以下几步操作,首先在主配置文件中将cacheEnabled设置为true:

1
2
3
<settings>
<setting name="cacheEnabled" value="true" />
</settings>

然后在需要使用二级缓存的mapper.xml文件中添加标签:

1
2
3
<mapper namespace="com.example.mapper.HusbandDao">
<cache/>
</mapper>

如果要让某个statement不进行二级缓存,就将该statement的useCache标签设置为false:

1
2
3
4
<!--开启二级缓存之后,useCache默认为true--> 
<select id="findAllHusband" resultType="Husband" useCache="false">
SELECT * FROM husband
</select>

实体类实现序列化接口,这样做的目的是二级缓存的存储介质多种多样,不一定要存储在内存中,可能会进行序列化存储在本地磁盘中:

1
2
3
4
5
6
7
8
9
10
11
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Husband implements Serializable { // 这里要实现序列化接口
private int hid;
private String hname;
private Wife wife;
private List<Son> sons;

}

二级缓存的工作机制:

  • 在一个会话中查询数据,查询成功后当前会话会将数据放入一级缓存中,此时不同的会话还无法共享数据
  • 如果当前会话关闭或者提交,该会话中的一级缓存就会被清空,并将一级缓存刷入二级缓存中
  • 其他会话在进行数据查询时,会先从二级缓存中查询是否有数据,如果没有再在一级缓存中查询,如果还是没有则去数据库中查询

进行二级缓存的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void loadTypeTest() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession sqlSession1 = factory.openSession(); // 开启session1
SqlSession sqlSession2 = factory.openSession(); // 开启session2
HusbandDao husbandDao1 = sqlSession1.getMapper(HusbandDao.class);
HusbandDao husbandDao2 = sqlSession2.getMapper(HusbandDao.class);
Husband husband1 = husbandDao1.findAnHusbandById(1001);
sqlSession1.close(); // 关闭session1,此时将数据刷入二级缓存
Husband husband2 = husbandDao2.findAnHusbandById(1001); // 从二级缓存中读取数据,跨session
sqlSession2.close();

}
1
2
3
4
5
[main] DEBUG com.woniuxy.mapper.HusbandDao - Cache Hit Ratio [com.woniuxy.mapper.HusbandDao]: 0.0 
[main] DEBUG c.w.m.HusbandDao.findAnHusbandById - ==> Preparing: SELECT * FROM husband WHERE hid = ?
[main] DEBUG c.w.m.HusbandDao.findAnHusbandById - ==> Parameters: 1001(Integer)
[main] DEBUG c.w.m.HusbandDao.findAnHusbandById - <== Total: 1
[main] DEBUG com.woniuxy.mapper.HusbandDao - Cache Hit Ratio [com.woniuxy.mapper.HusbandDao]: 0.5

从上述日志信息可以看出,sqlSession1在执行第一次查询时,由于一级缓存二级缓存中都没有数据,所以去数据库中查询。在sqlSession1关闭之后,sqlSession2进行了同样的查询,此时二级缓存中是有数据的,所以直接从缓存中获取数据。

###