abstract
缓存的作用
对于变动不频繁的数据,我们通常使用缓存的方式来将数据存起来,从而减少程序对数据库的访问次数,提升查找效率。
Mybatis一级缓存
一级缓存是Sqlsession级别的。默认开启。
一个Sqlsession对象代表一次数据库会话。一次会话中有可能频繁执行相同查询语句,往往结果短时间内不会变动,对于这种不会变动的数据。Sqlsession会把每次查询的结果放在本地缓存(local cache),当发现相同查询时,直接从本地缓存中取。减少了对数据库的io和数据库的查询。
Mybatis一级缓存相关接口类
SqlSession将它的工作交给了Executor执行器这个角色来完成,负责完成对数据库的各种操作。
缓存信息也由Executor执行器维护。
MyBatis将缓存和对缓存相关的操作封装成了Cache接口中。SqlSession、Executor、Cache之间的关系如下列类图所示:
- BaseExecutor类实现了Executor接口
- PerpetualCache类实现了Cache接口
- BaseExecutor类的queryFromDatabase等方法可以操纵PerpetualCache类。从而实现了对缓存的操控。
PerpetualCache实现原理其实很简单,其内部就是通过一个简单的HashMap<k,v> 来实现的,没有其他的任何限制。如下是PerpetualCache的实现代码:
MyBatis一级缓存工作流程
- 对于某个查询,根据statementId,params,rowBounds等来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
- 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
- 如果命中,则直接将缓存结果返回;
- 如果没命中:
- 去数据库中查询数据,得到查询结果;
- 将key和查询到的结果分别作为key,value对存储到Cache中;
- 将查询结果返回;
如何判断两次查询是相同的呢
结论
- 判断[statementId + rowBounds + 传递给JDBC的SQL + 传递给JDBC的参数值]组成的CacheKey是否相同。
判断依据
- 传入的statementId,对于MyBatis而言,你要使用它,必须需要一个statementId,它代表着你将执行什么样的Sql;
- MyBatis自身提供的分页功能是通过RowBounds来实现的,它通过rowBounds.offset和rowBounds.limit来过滤查询出来的结果集,这种分页功能是基于查询结果的再过滤,而不是进行数据库的物理分页;
- 由于MyBatis底层还是依赖于JDBC实现的,那么,对于两次完全一模一样的查询,MyBatis要保证对于底层JDBC而言,也是完全一致的查询才行。而对于JDBC而言,两次查询,只要传入给JDBC的SQL语句完全一致,传入的参数也完全一致,就认为是两次查询是完全一致的。
- 上述的第3个条件正是要求保证传递给JDBC的SQL语句完全一致;第4条则是保证传递给JDBC的参数也完全一致;MyBatis会将上述的SQL中的#{} 转化成?,通过JDBC的PreparedStatement的参数值进行替换,如果参数值相同,则满足自四条
Mybatis一级缓存的生命周期
生成
- 随Sqlsession的创建而创建
消亡
- SqlSession的close()方法会释放缓存,缓存不可用
- SqlSession的clearCache()方法会清空缓存中的内容
- SqlSession执行update()、delete()、insert()方法时,会清空缓存中的内容
Mybatis一级缓存注意事项
对于数据更新频繁的SQL,应该注意Sqlsession的生存时间,或是再mapper文件中禁用一级缓存
Mybatis一级缓存扩展
PerpetualCache类
1 | /* |
BaseExecutor类的queryFromDatabase方法
1 | private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { |
Mybatis二级缓存
二级缓存是mapper级别的。