一级缓存
使用原生生成sqlsession来请求则可以走一级缓存
public class Test {
public static void main(String[] args) throws IOException {
String config = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(config);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = factory.openSession();
System.out.println(session.selectOne("selectUserByID", 1));
// 同一个session的相同sql查询,将会使用一级缓存
System.out.println(session.selectOne("selectUserByID", 1));
// 参数改变,需要重新查询
System.out.println(session.selectOne("selectUserByID", 2));
// 清空缓存后需要重新查询
session.clearCache();
System.out.println(session.selectOne("selectUserByID", 1));
// session close以后,仍然使用同一个db connection
session.close();
session = factory.openSession();
System.out.println(session.selectOne("selectUserByID", 1));
}
}
通过spring集成获取mapper接口则不行。虽然Mapper是通过MapperFactoryBean单例生成的,但是依然没有一级缓存,因为spring集成mybatis则会动态代理sqlSessionProxy生成sqlsession导致取到的不一致。
然而如果有事务的话,事务里的sqlsession是同一个。
一级缓存的脏数据产生原因,另外一个sqlsession更新了数据所以拿到了脏数据。
总结
- MyBatis一级缓存的生命周期和SqlSession一致。
- MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
- MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
二级缓存
mapper级缓存,即作用于全局的缓存,在mybatis配置文件中开启,默认是关闭的。可以多个sqlsession实现共享。该缓存作用于mapper的namespace下,而实现机制和mysql一致,及sql相同则返回缓存数据。
但是不同的namespace下的请求即使sql相同也是不能走到缓存的,一级缓存及二级缓存走缓存的共同点是通过Statement Id + Offset + Limmit + Sql + Params来判断是否走缓存。
当多表查询时关联表被其他namespace更新时无法知悉,所以读缓存得到的是脏数据,可以使用Cache ref,让 ClassMapper 引用 StudenMapper 命名空间,这样两个映射文件对应的SQL操作都使用的是同一块缓存了。不过这样会导致缓存粒度变大。
总结
- MyBatis的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时粒度更加的细,能够到namespace 级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
- MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
- 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。