1:mybatis一级缓存:级别是session级别的,如果是同一个线程,同一个session,同一个查询条件,则只会查询数据库一次
2:mybatis二级缓存:级别是sessionfactory级别的,是针对于各个线程发出的sql查询条件
3:spring 关闭了mybatis的一级缓存,每一次查询都会建立一次连接,创建新的session,源码类有:
MapperFactoryBean、SqlSessionDaoSupport、SqlSessionTemplate;在SqlSessionTemplate中invoke方法中,有提交,关闭连接等方法
invoke方法是增强方法,实现原理也是动态代理
4:spring集成mybatis的二级缓存中,有很多的坑:总结如下
对于查询多commit少且用户对查询结果实时性要求不高,此时采用mybatis二级缓存技术降低数据库访问量,提高访问速度。
但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。
二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的。
举个简单的例子:
订单和订单详情,orderMapper、orderDetailMapper。在查询订单详情时我们需要把订单信息也查询出来,那么这个订单详情的信息被二级缓存在orderDetailMapper的namespace中,这个时候有人要修改订单的基本信息,那就是在orderMapper的namespace下修改,他是不会影响到orderDetailMapper的缓存的,那么你再次查找订单详情时,拿到的是缓存的数据,这个数据其实已经是过时的。
根据以上,想要使用二级缓存时需要想好两个问题:
1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。
----------------------------------源码篇------------------------------------------------
从configuaration中获取到MappedStatement,调用Executor的query方法实现执行
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
先查询二级缓存,是否有缓存,没有的话调用delegate.query方法,delegate是BaseExecutor执行器
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++;
// 查询一级缓存,没有的话就去查询数据库 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
如果一级缓存中没有该结果,会调用queryFromDatabase查询数据库得到数据,然后再缓存到一级缓存中,下次查询的时候相同的sql语句直接走一级缓存不会查询数据库。
Mybatis一级与二级缓存
一级缓存
mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中用一个HashMap来存储数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象是无法访问的
流程:
- 第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来,key是查询的完成sql语句,里面有参数,不同的参数是不同的key
- 第二次执行select会从缓存中查询数据,如果select相同并且参数一样,那么就能从缓存汇总返回数据,不用去查数据库了,从而提高效率
注意事项:如果SqlSession执行了insert,update,delete并commit了,那么mybatis就会清空当前SqlSession中所有的一级缓存数据,这样可以保证缓存中存的数据永远和数据库一致,避免出现脏读。当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis是默认开启一级缓存的,不需要配置。当服务器集群的时候,每个sqlSesssion有自己独立的缓存,相互之间不共享,所以每个在服务器集群的时候mybatis的一级缓存会产生数据冲突问题。
如何禁止一级缓存
方案1 在sql语句上 随机生成 不同的参数 存在缺点:map集合可能爆
方案2 开启二级缓存
二级缓存
二级缓存是mapper级别的缓存,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个缓存区域。
springboot项目中使用的配置
在启动类中加上@EnableCaching注解
在需要缓存的mapper中加上@CacheNamespace(implementation = MybatisRedisCache.class)这个注解和二级缓存的类就可以了
TransactionalCache类
继承Cache接口,主要作用是保存SqlSession在事务中需要向某个二级缓存提交的缓存数据(因为事务提交过程中 数据可能会回滚,所以不能直接把数据提交到二级缓存,而是暂存在TransactionCache中,在事务提交后再将过程中存放在其中的数据提交到二级缓存,如果事务回滚,则将数据清除)
private Cache delegate; 对应的二级缓存对象
private boolean clearOnCommit; 是否在commit时清除二级缓存的标记
private Map<Object, Object> entriesToAddOnCommit;
private Set entriesMissedInCache;
TransactionalCacheManager
用于管理CachingExecutor使用的二级缓存对象,只定义了一个transactionCaches字段。