Mybatis 学习总结10 mybatis的缓存机制
mybatis的缓存机制
前文引用 https://blog.****.net/lovezhaohaimig/article/details/80398568
讲述个人认为比较详细
缓存的概念
当用户频繁查询某些固定的数据时,第一次将这些数据从数据库中查询出来,保存在缓存(内存,高速磁盘)中.当下次用户再次查询这些数据时,不用再通过数据库查询,而是去缓存里面查询.
减少网络连接和数据库查询带来的损耗,从而提高我们的查询效率,减少高并发访问带来的系统性能问题.
什么是Mybatis缓存
使用缓存可以减少Java Application与数据库的交互次数,从而提升程序的运行效率。比如,查询id=1的user对象,第一次查询出来之后,会自动将该对象保存到缓存中。下一次查询该对象时,就可以直接从缓存中获取,不需要发送SQL查询数据库了。
Mybatis缓存分类
一级缓存:SqlSession级别,默认开启,且不能关闭。
mybatis的一级缓存是SqlSession级别的缓存,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于存储缓存数据,不同的SqlSession之间缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是SqlSession范围的,当在同一个SqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存)中,第二次查询时会从缓存中获取数据,不再去底层进行数据库查询,从而提高了查询效率。需要注意的是:如果SqlSession执行了DML操作(insert、update、delete),并执行commit()操作,mybatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存数据中存储的是最新的信息,避免出现脏读现象。
当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了,Mybatis默认开启一级缓存,不需要进行任何配置。
二级缓存:Mapper级别,默认关闭,可以手动开启。
二级缓存是Mapper级别的缓存,使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMapper进行数据存储,相比一级缓存SqlSession,二级缓存的范围更大,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的SqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。
代码自测(以下为本人自测,也可以查看上文连接)
测试代码准备
sys_country 库表
CountryBO 实体类
public class CountryBO implements Serializable {
private Long id;
private String countryName;
private Date addTime;
private Date modTime;
......
mapper.xml
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapping.CountryMapper">
<select id="getCountryById" resultType="Model.CountryBO">
SELECT * FROM sys_country WHERE id = #{id}
</select>
</mapper>
CountryMapper mapper接口
public interface CountryMapper extends BaseMapper {
CountryBO getCountryById(Long id);
}
一级缓存(scope :SqlSession)
我们首先编写一个测试类,测试简单的调用两次查询方法
public static void main(String[] args) throws IOException {
SqlSession sqlSession = Common.getSqlSession(false);
CountryMapper countryMapper = sqlSession.getMapper(CountryMapper.class);
CountryBO ct1 = countryMapper.getCountryById(1L);
System.out.println(ct1);
CountryBO ct2 = countryMapper.getCountryById(1L);
System.out.println(ct2);
}
执行debug,查看SqlSession实例,可以看到有一个localCacje对象,这个就是本地缓存的对象。
mybatis的本地缓存基于PerpetualCache类,源码如下
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap();
public PerpetualCache(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
public int getSize() {
return this.cache.size();
}
public void putObject(Object key, Object value) {
this.cache.put(key, value);
}
public Object getObject(Object key) {
return this.cache.get(key);
}
public Object removeObject(Object key) {
return this.cache.remove(key);
}
public void clear() {
this.cache.clear();
}
public ReadWriteLock getReadWriteLock() {
return null;
}
public boolean equals(Object o) {
if (this.getId() == null) {
throw new CacheException("Cache instances require an ID.");
} else if (this == o) {
return true;
} else if (!(o instanceof Cache)) {
return false;
} else {
Cache otherCache = (Cache)o;
return this.getId().equals(otherCache.getId());
}
}
public int hashCode() {
if (this.getId() == null) {
throw new CacheException("Cache instances require an ID.");
} else {
return this.getId().hashCode();
}
}
}
可以看到mybatis的缓存是基于PerpetualCache 的以键值对的形式进行存储。具体的缓存生成流程,可以跟随源码解读,不多赘述。
直接看执行结果
我们发现执行了两次查询,只和数据库进行了一次交互。说明第二次是从缓存中读取的。
我们改一下代码,在第一次执行结束后清空sesion的缓存。再测试一下。
SqlSession sqlSession = Common.getSqlSession(false);
CountryMapper countryMapper = sqlSession.getMapper(CountryMapper.class);
CountryBO ct1 = countryMapper.getCountryById(1L);
System.out.println(ct1);
sqlSession.clearCache();
CountryBO ct2 = countryMapper.getCountryById(1L);
System.out.println(ct2);
在清空之前查看一下SqlSession实例,
可以看到 key 值 为 类名+方法 + 查询语句 +参数组成的字符串。键值则存储了查询结果。
查询结果
可以看到这里执行了两次数据读取。返回两条结果。
二级缓存(scope:mapper)
二级缓存仅需要在想要配置二级缓存的mapper.xml文件中添加如下配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapping.CountryMapper">
<!--开启二级缓存 -->
<cache/>
<select id="getCountryById" resultType="Model.CountryBO">
SELECT * FROM sys_country WHERE id = #{id}
</select>
</mapper>
然后在配置文件中 设置 cacheEnabled 为 ture ,实际上mybatis默认这个配置就是true 我们还是展示一下
<settings>
<!--全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 默认开启-->
<setting name="cacheEnabled" value="true"/>
</settings>
需要注意,二级缓存的持久层交互对象需要实现Serializable接口,否则报如下异常
rror committing transaction. Cause: org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException:
我们在执行一下刚刚的测试代码
可以从日志中看到,二级缓存生效了。即使我们在第一次执行后清空了一级缓存,但是仍然可以从二级缓存中取到同样查询语句的结果。
我们在测试一下
SqlSession sqlSession = Common.getSqlSession(false);
CountryMapper countryMapper = sqlSession.getMapper(CountryMapper.class);
CountryBO ct1 = countryMapper.getCountryById(1L);
System.out.println(ct1);
sqlSession.clearCache();
CountryBO ct2 = countryMapper.getCountryById(1L);
System.out.println(ct2);
sqlSession.commit();
CountryBO ct3 = countryMapper.getCountryById(1L);
System.out.println(ct3);
结果
可以看到,二级缓存依然生效了。