最新互联网大厂面试真题、Java程序员面试策略(面试前的准备、面试中的技巧)请移步GitHubjava
通常咱们在系统中使用缓存技术是为了提高数据查询的效率。当咱们从数据库中查询到一批数据后将其放入到混存中(简单理解就是一块内存区域),下次再查询相同数据的时候就直接从缓存中获取数据就好了。这样少了一步和数据库的交互,能够提高查询的效率。git
可是一个硬币都具备两面性,缓存在带来性能提高的同时也“悄悄”引入了不少问题,好比缓存同步、缓存失效、缓存雪崩等等。固然这些问题不是本文讨论的重点。程序员
本文主要讨论MyBatis缓存这个比较鸡肋的功能。虽说MyBatis的缓存功能比较鸡肋,可是为了全面了解MyBatis这个框架,学习下缓存这个功能仍是挺有必要的。MyBatis的缓存分为一级缓存和二级缓存,下面就分别来介绍下这两个特性。github
在应用运行过程当中,咱们有可能在一次数据库会话中,执行屡次查询条件彻底相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,若是是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提升性能。面试
1. 什么是MyBatis一级缓存spring
一级缓存是 SqlSession级别 的缓存。在操做数据库时须要构造 sqlSession 对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不一样的 sqlSession 之间的缓存数据区域(HashMap)是互相不影响的。sql
在应用运行过程当中,咱们有可能在一次数据库会话中,执行屡次查询条件彻底相同的SQL,MyBatis 提供了一级缓存的方案优化这部分场景,若是是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提升性能。数据库
2. 怎么开启一级缓存缓存
MyBatis中一级缓存默认是开启的,不须要咱们作额外的操做。安全
若是你须要关闭一级缓存的话,能够在Mapper映射文件中将flushCache属性设置为true,这种作法只会针对单个SQL操做生效
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap" flushCache="true"> select <include refid="Base_Column_List" /> from cbondissuer where OBJECT_ID = #{objectId,jdbcType=VARCHAR} </select>还有一种作法是在MyBatis的主配置文件中,关闭全部的一级缓存
默认是SESSION,也就是开启一级缓存 <setting name="localCacheScope" value="STATEMENT"/>
下面咱们来写代码验证下MyBatis的一级缓存。
String id = "123"; SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); //同一个sqlSession建立的Mapper CbondissuerMapper cbondissuerMapper10 = sqlSession1.getMapper(CbondissuerMapper.class); CbondissuerMapper cbondissuerMapper11 = sqlSession1.getMapper(CbondissuerMapper.class); //另一个sqlSession建立的Mapper CbondissuerMapper cbondissuerMapper20 = sqlSession2.getMapper(CbondissuerMapper.class); //同一个Mapper,一样的SQL查了两次 Cbondissuer cbondissuer10 = cbondissuerMapper10.selectByPrimaryKey(id); Cbondissuer cbondissuer101 = cbondissuerMapper10.selectByPrimaryKey(id); //同一个sqlSession建立的Mapper,又查询了一次一样的SQL Cbondissuer cbondissuer11 = cbondissuerMapper11.selectByPrimaryKey(id); //不同的sqlSession建立的Mapper查询了一次一样的SQL Cbondissuer cbondissuer20 = cbondissuerMapper20.selectByPrimaryKey(id); System.out.println("cbondissuer10 equals cbondissuer101 :"+(cbondissuer10==cbondissuer101)); System.out.println("cbondissuer10 equals cbondissuer11 :"+(cbondissuer10==cbondissuer11)); System.out.println("cbondissuer10 equals cbondissuer21 :"+(cbondissuer10==cbondissuer20)); sqlSession1.close(); sqlSession2.close(); System.out.println("end...");
上面进行了四次查询,若是你观察日志的话。会发现只进行了两个数据库查询。由于第二和第三次的查询都查询了一级缓存,查出的实际上是缓存中的结果。因此输出的结果是
cbondissuer10 equals cbondissuer101 :true cbondissuer10 equals cbondissuer11 :true cbondissuer10 equals cbondissuer21 :false
3. 哪些因素会使一级缓存失效
上面的一级缓存初探让咱们感觉到了 MyBatis 中一级缓存的存在,那么如今你或许就会有疑问了,那么何时缓存失效呢?
4. 一级缓存源码解析
其实MyBatis一级缓存的实质就是一个Executor的一个相似Map的属性,分析源码的方法就是看在哪些地方从这个Map中查询了缓存,又是在哪些清空了这些缓存。
①. 查询时使用缓存分析
public abstract class BaseExecutor implements Executor { private static final Log log = LogFactory.getLog(BaseExecutor.class); protected Transaction transaction; protected Executor wrapper; protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; //这个localCache变量就是一级缓存变量 protected PerpetualCache localCache; protected PerpetualCache localOutputParameterCache; protected Configuration configuration; //..省略下面代码 }
全局搜索代码中哪些地方使用了这个变量,很容易找到BaseExecutor.query方法使用了这个缓存:
public abstract class BaseExecutor implements Executor { // 省略其余代码 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; } //..省略下面代码 }
上面的代码展现了,BaseExecutor的query方法使用缓存的过程。须要注意的是查询缓存时是根据cacheKey进行查询的,咱们能够将这个key简单的
理解为sql语句,不一样的sql语句能查出不一样的缓存。(注意sql语句中的参数不一样也会被认为是不一样的sql语句)。
②. 致使一级缓存失效的代码分析
查看BaseExecutor的代码,咱们很容易发现是下面的方法清空了一级缓存。(不要问我是怎么发现这个代码的,看代码能力须要本身慢慢提高)
@Override public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } }
那么咱们只要查看哪些地方调用了这个方法就知道哪些状况下会致使一级缓存失效了。跟踪下来,最后发现下面三处地方会使得一级缓存失效
BaseExecutor的update方法,使用MyBatis的接口进行增、删、改操做都会调用到这个方法,这个也印证了上面的说法。
@Override public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } clearLocalCache(); return doUpdate(ms, parameter); }
BaseExecutor的commit方法,事务提交会致使一级缓存失败。若是咱们使用Spring的话,通常事务都是自动提交的,因此好像MyBatis的一级缓存一直没怎么被考虑过
@Override public void commit(boolean required) throws SQLException { if (closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } }
BaseExecutor的rollback方法,事务回滚也会致使一级缓存失效。
@Override public void rollback(boolean required) throws SQLException { if (!closed) { try { clearLocalCache(); flushStatements(true); } finally { if (required) { transaction.rollback(); } } } }
5. 一级缓存使用建议
平时使用MyBatis时都是和Spring结合使用的,在整个Spring容器中通常只有一个SqlSession实现类。而Spring通常都是主动提交事务的,因此说一级缓存常常失效。
还有就是咱们也不多在一个事务范围内执行同一个SQL两遍,上面的这些缘由致使咱们在开发过程当中不多注意到MyBatis一级缓存的存在。
不怎么用并非说不用,做为一个合格的开发者须要对这些心知肚明,要清楚的知道MyBatis一级缓存的工做流程。
1. 什么是MyBatis二级缓存
MyBatis 一级缓存最大的共享范围就是一个SqlSession内部,那么若是多个 SqlSession 须要共享缓存,则须要开启二级缓存,开启二级缓存后,会使用 CachingExecutor 装饰 Executor,
进入一级缓存的查询流程前,先在CachingExecutor 进行二级缓存的查询,具体的工做流程以下所示:
当二级缓存开启后,同一个命名空间(namespace) 全部的操做语句,都影响着一个 共同的 cache(一个Mapper映射文件对应一个Cache),也就是二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
从上面的图能够看出,MyBatis的二级缓存实现能够有不少种,能够是MemCache、Ehcache等。也能够是Redis等,可是须要额外的Jar包。
2. 怎么开启二级缓存
二级缓存默认是不开启的,须要手动开启二级缓存,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。开启二级缓存的条件也是比较简单,
step1:经过直接在 MyBatis 配置文件中经过
<settings> <setting name = "cacheEnabled" value = "true" /> </settings>
step2: 在 Mapper 的xml 配置文件中加入 标签
cache标签下面有下面几种可选项
cache-ref表明引用别的命名空间的Cache配置,两个命名空间的操做使用的是同一个Cache。
3. 哪些因素会使二级缓存失效
从上面的介绍能够知道MyBatis的二级缓存主要是为了SqlSession之间共享缓存设计的。可是咱们平时开发过程当中都是结合Spring来进行MyBatis的开发。在Spring环境下通常也只有一个SqlSession实例,因此二级缓存使用到的机会很少。因此下面就简单描述下Mybatis的二级缓存。
仍是以上面的列子为列
String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}"; SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); //同一个sqlSession建立的Mapper CbondissuerMapper cbondissuerMapper10 = sqlSession1.getMapper(CbondissuerMapper.class); CbondissuerMapper cbondissuerMapper11 = sqlSession1.getMapper(CbondissuerMapper.class); //另一个sqlSession建立的Mapper CbondissuerMapper cbondissuerMapper20 = sqlSession2.getMapper(CbondissuerMapper.class); //同一个Mapper,一样的SQL查了两次 Cbondissuer cbondissuer10 = cbondissuerMapper10.selectByPrimaryKey(id); Cbondissuer cbondissuer101 = cbondissuerMapper10.selectByPrimaryKey(id); //同一个sqlSession建立的Mapper,又查询了一次一样的SQL Cbondissuer cbondissuer11 = cbondissuerMapper11.selectByPrimaryKey(id); //这边须要提交事务才能让二级缓存生效 sqlSession1.commit(); //不同的sqlSession建立的Mapper查询了一次一样的SQL Cbondissuer cbondissuer20 = cbondissuerMapper20.selectByPrimaryKey(id); System.out.println("cbondissuer10 equals cbondissuer101 :"+(cbondissuer10==cbondissuer101)); System.out.println("cbondissuer10 equals cbondissuer11 :"+(cbondissuer10==cbondissuer11)); System.out.println("cbondissuer10 equals cbondissuer21 :"+(cbondissuer10==cbondissuer20));
4. 二级缓存使用建议
我的以为MyBatis的二级缓存实用性不是很大。一个缘由就是Spring环境下,一本只有一个SqlSession,不存在sqlSession之间共享缓存;还有就是
MyBatis的缓存都不能作到分布式,因此对于MyBatis的二级缓存以了解为主。
1. 一级缓存
2. 二级缓存
整体来讲,MyBatis的缓存功能比较鸡肋。想要使用缓存的话仍是建议使用spring-cache等框架。