乐趣区

关于java:MyBatis-的一级缓存与二级缓存

一、简述
MyBatis 的一级缓存是基于数据库会话(SqlSession 对象) 的,默认开启。二级缓存是基于全局 (nameSpace) 的,开启须要配置。
二、MyBatis 的次要层次结构
应用 MyBatis 对数据库操作的代码,可能看见的就是这个 SqlSession 对象。实际上,这只是 MyBatis 对外裸露的接口,整个 MyBatis 核心部件是上面的这么一堆接口和类:

1、SqlSession:MyBatis 工作的次要顶层 API,示意和数据库交互的会话,实现必要数据库增删改查性能。
2️、Executor:MyBatis 执行器,整个 MyBatis 调度的外围,负责 SQL 语句的生成和查问缓存的保护。
3️、StatementHandler:封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将 Statement 后果集转换成 List 汇合。
4️、ParameterHandler:负责对用户传递的参数转换成 JDBC Statement 所须要的参数。
5️、ResultSetHandler:负责将 JDBC 返回的 ResultSet 后果集对象转换成 List 类型的汇合。
6️、TypeHandler:负责 Java 数据类型和 jdbc 数据类型之间的映射和转换。
7️、MappedStatement:MappedStatement 保护了一条节点的封装。
8️、SqlSource:负责依据用户传递的 parameterObject,动静地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回。
9️、BoundSql:示意动静生成的 SQL 语句以及相应的参数信息。
10、Configuration:MyBatis 所有的配置信息都维持在 Configuration 对象之中。
下面这堆接口和类的档次关系如图:

MyBatis 对外裸露的接口是 SqlSession,而最重要的是 Executor 接口。Executor 的实现类 BaseExecutor 中领有一个 Cache 接口的实现类 PerpetualCache:

PerpetualCache 中则有一个 HashMap 属性
总结:

MyBatis 封装了 JDBC 操作,对外裸露了 SqlSession 接口进行数据库的操作。然而理论 MyBatis 最外围的接口是 Executor,它负责 SQL 语句的生成和查问缓存的保护。如果没有缓存就查数据库,有缓存就应用的是 PerpetualCache 中的 HashMap 保留的数据缓存。MyBatis 的一级缓存其实就保留在一个 HashMap 中。HashMap 如何判断查询方法是否雷同?其实次要是通过 HashMap 的 key 值
BaseExecutor:

public CacheKey createCacheKey(MappedStatement ms, 
                               Object parameterObject, 
                               RowBounds rowBounds, 
                               BoundSql boundSql) {if (this.closed) {throw new ExecutorException("Executor was closed.");
        } else {CacheKey cacheKey = new CacheKey();
            cacheKey.update(ms.getId());
            cacheKey.update(rowBounds.getOffset());
            cacheKey.update(rowBounds.getLimit());
            cacheKey.update(boundSql.getSql());
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
            Iterator var8 = parameterMappings.iterator();
            while(var8.hasNext()) {ParameterMapping parameterMapping = (ParameterMapping)var8.next();
                if (parameterMapping.getMode() != ParameterMode.OUT) {String propertyName = parameterMapping.getProperty();
                    Object value;
                    if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = this.configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    cacheKey.update(value);
                }
            }
            if (this.configuration.getEnvironment() != null) {cacheKey.update(this.configuration.getEnvironment().getId());
            }
            return cacheKey;
        }
    }

从代码中能够看出,如果上面条件一样,就能够判断为两个查问雷同:

1️、tatementId
2️、RowBounds 的 offset、limit 的后果集分页属性
3️、SQL 语句
4️、传给 JDBC 的参数值
三、MyBatis 的一级缓存
1️、一级缓存最简略的组织模式
MyBatis 在一次会话的示意——一个 SqlSession 对象中创立一个本地缓存(local cache),对于每一次查问,都会尝试依据查问的条件去本地缓存中查找是否在缓存中,如果在,就间接从缓存中取出,而后返回给用户;否则,从数据库读取数据,将查问后果存入缓存并返回给用户。

相似于最开始保留的形式,只是从一个简略的对象,换成了封装好了的更加简单的 Local Cache 对象。

实际上,SqlSession 只是一个 MyBatis 对外的接口,SqlSession 将它的工作交给了 Executor 执行器这个角色来实现,负责实现对数据库的各种操作。当创立了一个 SqlSession 对象时,MyBatis 会为这个 SqlSession 对象创立一个新的 Executor 执行器,而缓存信息就被保护在这个 Executor 执行器中,MyBatis 将缓存和对缓存相干的操作封装在 Cache 接口中。它们之间的组织关系,大略如下图:

2、一级缓存的生命周期

MyBatis 在开启一个数据库会话时,会创立一个新的 SqlSession 对象,SqlSession 对象中会有一个新的 Executor 对象,Executor 对象中持有一个新的 PerpetualCache 对象(Cache 接口的实现类);当会话完结时,SqlSession 对象及其外部的 Executor 对象还有 PerpetualCache 对象也一并开释掉。
如果 SqlSession 调用了 close(),会开释掉一级缓存 PerpetualCache 对象,一级缓存将不可用。
如果 SqlSession 调用了 clearCache(),会清空 PerpetualCache 对象中的数据,然而 SqlSession 对象仍可应用。
SqlSession 中执行任何一个增 / 删 / 改操作之后执行事务提交 commit(),都会清空 PerpetualCache 对象的数据,然而 SqlSession 对象能够持续应用。
四、MyBatis 的二级缓存

2、二级缓存应用场景
相似于统计排行榜的查问,可能会波及到多张表很多字段的查问统计排序,是十分费时费力的。如果每次都去数据库查问显示一次排行榜数据,那到排行榜这里,必定会卡顿很久,而且这种卡顿是用户不能忍耐的。做成一级缓存也是不可行的,每次 SqlSession 申请,每个客户上来难道都要卡顿一次吗?所以,这种查问必定要做成全局的缓存,当利用启动的时候就缓存这种查问数据,而后每一周刷新一次这种数据就能够了。
由此,简略总结二级缓存的特点和应用场景:二级缓存作用于全局,对于一些相当耗费性能的,并且对于时效性不敏感的查问能够应用二级缓存。留神,如果开启了二级缓存,查问的程序是二级缓存 → 一级缓存 → 数据库。
MyBatis 二级缓存的配置
在 MyBatis 中应用二级缓存就必须要进行配置了,必须要有上面的步骤能力失常应用二级缓存:
在全局设置中开启二级缓存

<settings>...
  <!-- 开启二级缓存 -->
  <setting name="cacheEnabled" value="true"/>
  ...
</settings>

在 xxxMapper.xml 中开启 标签

<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024">
</cache>

能够简写为:
<cache/>
这样就示意在 xxxMapper.xml 中开启二级缓存了,因为 标签的每个属性都有默认值。cache 标签属性:

eviction:缓存回收策略,这个属性又有上面几个值
LRU – 最近起码应用的。移除最长工夫不被应用的对象。
FIFO – 先进先出。按对象进入缓存的程序来移除它们。
SOFT – 软援用。移除基于垃圾回收器状态和软援用规定的对象。
WEAK – 弱援用。更踊跃地移除基于垃圾收集器状态和弱援用规定的对象。
默认是 LRU
flushInterval:刷新距离,能够被设置为任意的正整数, 而且它们代表一个正当的毫秒 模式的时间段。默认状况是不设置, 也就是没有刷新距离, 缓存仅仅调用语句时刷新。
size:援用数目,能够被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。
readOnly:只读属性能够被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的雷同实例。因而这些对象不能被批改。这提供了很重要的性能劣势。可读写的缓存会返回缓存对象的拷贝 (通过序列化)。这会慢一些,然而平安,因而默认是 false。
相干实体类须要序列化
放入二级缓存中保留的 JavaBean 须要实现 Serializable 接口。序列化的意思就是从内存中的数据传到硬盘中。反序列化意思相同。MyBatis 的二级缓存,实际上就是将数据放进了硬盘文件中去了。
如果要应用 MyBatis 的二级缓存,除了要在须要缓存的 mapper.xml 中开启以外,还须要指标实体类实现序列化的接口。当实体类有父类或级联属性,也必须实现序列化。
useCache 和 flushCache
这一步不是必须的。这两个都是属于查问标签 的属性
userCache 是用来设置是否禁用二级缓存的,在 statement 中设置 useCache=false 能够禁用以后 select 语句的二级缓存,即每次查问都会收回 sql 去查问,默认状况是 true,即该 sql 应用二级缓存。
flushCache 属性,默认状况下为 true,即刷新缓存,如果改成 false 则不会刷新。应用缓存时如果手动批改数据库表中的查问数据会呈现脏读。
五、Mybatis 波及的设计模式
1️、Builder 模式,例如 SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder
2️、工厂模式,例如 SqlSessionFactory、ObjectFactory、MapperProxyFactory
3️、单例模式,例如 ErrorContext 和 LogFactory
4️、代理模式,Mybatis 实现的外围,比方 MapperProxy、ConnectionLogger,用的 jdk 的动静代理;还有 executor.loader 包应用了 cglib 或者 javassist 达到提早加载的成果
5️、组合模式,例如 SqlNode 和各个子类 ChooseSqlNode 等
6️、模板办法模式,例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的子类例如 IntegerTypeHandler
7️、适配器模式,例如 Log 的 Mybatis 接口和它对 jdbc、log4j 等各种日志框架的适配实现
8️、装璜者模式,例如 Cache 包中的 cache.decorators 子包中等各个装璜者的实现
9️、迭代器模式,例如迭代器模式 PropertyTokenizer
六、总结
进行 select 后,调用 SqlSession.close(),会将其一级缓存的数据放进二级缓存中,此时一级缓存随着 SqlSession 的敞开也就不存在了。
进行 select 后,调用 SqlSession.commit(),会将其一级缓存的数据放进二级缓存中,并清空一级缓存。
对 SqlSession 执行更新 (insert、delete、update) 后,同时不调用 SqlSession.commit/SqlSession.close(),这时只会清空其本身的一级缓存,对二级缓存没有影响。
对 SqlSession 执行更新 (insert、delete、update) 后,执行 SqlSession.commit(),不仅清空其本身的一级缓存 (执行更新操作的后果),也清空二级缓存(执行 commit() 的成果)。
对 SqlSession 执行更新 (insert、delete、update) 后,执行 SqlSession.close()(没有执行 SqlSession.commit()),需分两类状况。当 autoCommit 为 false 时,只会清空其本身的一级缓存(执行更新操作的成果),对二级缓存没有影响。当 autoCommit 为 true 时,会清空二级缓存。

退出移动版