前言

使用ORM框架我们更多的是使用其查询功能,那么查询海量数据则又离不开性能,那么这篇中我们就看下mybatis高级应用之延迟加载、一级缓存、二级缓存。使用时需要注意延迟加载必须使用resultMap,resultType不具有延迟加载功能。

一、延迟加载

延迟加载已经是老生常谈的问题,什么最大化利用数据库性能之类之类的,也懒的列举了,总是我一提到延迟加载脑子里就会想起来了Hibernate get和load的区别。OK,废话少说,直接看代码。 先来修改配置项xml。

注意,编写mybatis.xml时需要注意配置节点的先后顺序,settings在最前面,否则会报错。

 <settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

前面提到延迟加载只能通过association、collection来实现,因为只有存在关联关系映射的业务场景里你才需要延迟加载,也叫懒加载,也就是常说的用的时候再去加载。OK,那么我们来配一个association来实现:

我来编写一个加载博客列表的同时加载出博客额作者, 主要功能点在id为blogAuthorResumtMap这个resultmap上,其中使用了association,关键点是它的select属性,该属性也就是你需要懒加载调用的statment id。 当然需要懒加载的statement 返回值当然是resultmap

<resultMap id="blogAuthorResumtMap" type="Blog">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="category" property="category"/>
<result column="author_id" property="author_id"/>
<!--使用assocition支持延迟加载功能,配置延迟加载关联关系-->
<association property="author" javaType="Author" select="selectAuthorById" column="author_id"/>
</resultMap> <!--要使用延迟记载的方法-->
<select id="selectBlogAuthor" resultMap="blogAuthorResumtMap">
SELECT id,title,category,author_id FROM t_blog
</select> <!--延迟加载查询博客对应的作者方法-->
<select id="selectAuthorById" parameterType="int" resultType="Author">
SELECT id,name from t_author where id=#{value}
</select>

OK,来看测试结果:

  @Test
public void getBlogAuthorByLazyloading(){
SqlSession sqlSession=null;
try{
sqlSession=sqlSessionFactory.openSession();
List<Blog> list = sqlSession.selectList("com.autohome.mapper.Author.selectBlogAuthor"); for (Blog blog:list) {
System.out.println("id:"+blog.getId()+",title:"+blog.getTitle()+",category:"+blog.getCategory());
System.out.println("author:"+blog.getAuthor().getName());
} }catch(Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}

  

从图一中看出,执行selectBlogAuthor返回List<Blog>对象时只执行了SQL SELECT id,title,category,author_id from t_blog,循环遍历时才去执行select id,name from t_author where id=?。

二、一级缓存

了解缓存前我们先看一张图片(图片来源于传智播客视频图片)。从图中可以了解一级缓存是sqlsession级别、二级缓存是mapper级别。在操作数据库时我们需要先构造sqlsession【默认实现是DefaultSqlSession.java】,在对象中有一个数据结构【hashmap】来存储缓存数据。不同的sqlsession区域是互不影响的。 如果同一个sqlsession之间,如果多次查询之间执行了commit,则缓存失效,mybatis避免脏读。

OK,在看mybatis一级缓存时,我总是觉的一级缓存有点鸡肋,两个查询如果得到一样的数据,你还会执行第二次么,果断引用第一次的返回值了。 可能还没了解到一级缓存的奥妙之处。一级缓存默认是开启的,不需要额外设置,直接使用。

public void testCache(){
SqlSession sqlSession=null;
try{
sqlSession=sqlSessionFactory.openSession(); Author author = sqlSession.selectOne("com.autohome.mapper.Author.selectAuthorById",1);
System.out.println("作者信息 id:"+author.getId()+",name:"+author.getName()); author = sqlSession.selectOne("com.autohome.mapper.Author.selectAuthorById",1);
System.out.println("作者信息2 id:"+author.getId()+",name:"+author.getName()); }catch(Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}

  

从DEBUG截图来看,当我们第一次调用方法时执行了SELECT id,name from t_author where id=? 此时缓存中还没有该数据,则执行数据库查询,当再次执行时直接从缓存中读取。

执行demo后我们来看下这个查询过程保存到缓存的源码,先看下DefaultSqlSession.java。我们调用的selectOne(),从代码中看它是直接调用selectList()然后判断返回值size大小。

@Override
public <T> T selectOne(String statement) {
return this.<T>selectOne(statement, null);
} @Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

  再跟踪到selectList方法,看到先构造MappedStatement对象,然后看到真正执行query()的是一个executor对象,在DefaultSqlSession.java中executor是成员变量,再翻到org.apache.ibatis.executor包中看到executor实际是一个接口。OK,那么我们debug时发现其引用是CachingExecutor。再打开CachingExecutor.java

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

 从CachingExecutor.java的两个query()可以看到先去构造CacheKey 再调用抽象类BaseExecutor.query(),这个也是最关键的一步。

//先创建CacheKey
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
} //再执行查询方法
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, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

  BaseExecutor.java

  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

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

  OK,看了一长串,终于是有点眉目了,我们看到finally中先删除当前key缓存,然后再调用localCache.putObject把最新的结果集存入HashMap中。

 

三、二级缓存

了解二级缓存之前先来看副图(图片来自传智播客视频,非本人编写),那么从图中我们可以看出,mybatis二级缓存是mapper级别,也就是说不同的sqlmapper共享不同的内存区域,不同的sqlsession共享同一个内存区域,用mapper的namespace区别内存区域。

开启mybatis二级缓存: 1、设置mybatis.xml,也就是说mybatis默认二级缓存是关闭的。

2、设置mapper。在mapper.xml内添加标签:<cache/>

3、pojo实现接口Serializable。实现该接口后也就说明二级缓存不仅可以存入内存中,还可以存入磁盘。

OK,看一个二级缓存demo:

@Test
public void testCache2(){
SqlSession sqlSession=null;
SqlSession sqlSession2=null;
try{
sqlSession=sqlSessionFactory.openSession();
sqlSession2=sqlSessionFactory.openSession(); Author author = sqlSession.selectOne("com.autohome.mapper.Author.selectAuthorById",1);
System.out.println("作者信息 id:"+author.getId()+",name:"+author.getName());
sqlSession.close(); Author author2 = sqlSession2.selectOne("com.autohome.mapper.Author.selectAuthorById",1);
System.out.println("作者信息2 id:"+author2.getId()+",name:"+author2.getName());
sqlSession2.close();
}catch(Exception e){
e.printStackTrace();
}finally { }
}

运行demo可以看出二级缓存不同的地方在于Cache Hit Ratio,发出sql查询时先看是否命中缓存,第一次则是0.0 ,再次查询时则直接读取缓存数据,命中率是0.5。当然数据结构还是HashMap。

如果数据实时性要求比较高,可以设置select 语句的 

 

如果数据的查询实时性要求比较高,则设置select语句的useCache="false",则每次都直接执行sql。

 <select id="selectBlogAuthor" resultMap="blogAuthorResumtMap" useCache="false">
SELECT id,title,category,author_id FROM t_blog
</select>

  

参考

http://www.cnblogs.com/DoubleEggs/p/6243223.html

http://blog.csdn.net/isea533/article/details/44566257

MyBatis从入门到放弃六:延迟加载、一级缓存、二级缓存的更多相关文章

  1. MyBatis基础入门《十六》缓存

    MyBatis基础入门<十六>缓存 >> 一级缓存 >> 二级缓存 >> MyBatis的全局cache配置 >> 在Mapper XML文 ...

  2. Mybatis一级、二级缓存

      Mybatis一级.二级缓存   一级缓存 首先做一个测试,创建一个mapper配置文件和mapper接口,我这里用了最简单的查询来演示. <mapper namespace="c ...

  3. 170214、mybatis一级和二级缓存

    mybatis一级缓存是指在内存中开辟一块区域,用来保存用户对数据库的操作信息(sql)和数据库返回的数据,如果下一次用户再执行相同的请求, 那么直接从内存中读数数据而不是从数据库读取. 其中数据的生 ...

  4. MyBatis(七):MyBatis缓存详解(一级缓存/二级缓存)

    一级缓存 ​ MyBatis一级缓存上SqlSession缓存,即在统一SqlSession中,在不执行增删改操作提交事务的前提下,对同一条数据进行多次查询时,第一次查询从数据库中查询,完成后会存入缓 ...

  5. Hibernate 再接触 一级缓存 二级缓存 查询缓存

    缓存 就是把本来应该放在硬盘里的东西放在内存里  将来存内存里读 一级缓存: session缓存 二级缓存: sessionFactory级别的   (适合经常访问,数据量有限,改动不大) 很多的se ...

  6. hibernate的获取session的两方法比较,和通过id获取对象的比较,一级缓存二级缓存

    opensession与currentsession的联系与区别 在同一个线程中opensession的session是不一样的,而currentsession获取的session是一样的,这就保证了 ...

  7. MyBatis从入门到放弃七:二级缓存原理分析

    前言 说起mybatis的一级缓存和二级缓存我特意问了几个身边的朋友他们平时会不会用,结果没有一个人平时业务场景中用. 好吧,那我暂且用来学习源码吧.一级缓存我个人认为也确实有些鸡肋,mybatis默 ...

  8. MyBatis从入门到精通(六):MyBatis动态Sql之if标签的用法

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解如何使用if标签生成动 ...

  9. mybatis一级缓存二级缓存

    一级缓存 Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言.所以在参数和SQL完全一样的情况下,我们使用同一个SqlSess ...

随机推荐

  1. 数据结构(C语言版)顺序栈相关算法的代码实现

    这两天完成了栈的顺序存储结构的相关算法,包括初始化.压栈.出栈.取栈顶元素.判断栈是否为空.返回栈长度.栈的遍历.清栈.销毁栈.这次的实现过程有两点收获,总结如下: 一.清楚遍历栈的概念 栈的遍历指的 ...

  2. Spring AOP的注解实现

    适用场景: 记录接口方法的执行情况,记录相关状态到日志中. 注解类:LogMark.java package com.lichmama.spring.demo.annotation; import j ...

  3. 剖析Elasticsearch集群系列第一篇 Elasticsearch的存储模型和读写操作

    剖析Elasticsearch集群系列涵盖了当今最流行的分布式搜索引擎Elasticsearch的底层架构和原型实例. 本文是这个系列的第一篇,在本文中,我们将讨论的Elasticsearch的底层存 ...

  4. HelloGitHub.com 网站开源了

    简介 随着 HelloGitHub 月刊持续更新了一年多,内容变的越来越多.因为内容数据没有结构化,如果还是使用之前的编辑文本的方式编辑月刊内容,会对后面的继续发刊和维护带来了很多问题和多余的工作,例 ...

  5. nyoj_120: 校园网络

    题目链接 要加边使一个图成为一个强连通分量,只需加max(出度为0的点数,入度为0的点数)条边(可以不使用tarjan算法).题目数据有点弱,网上一些人所谓 结果 = 出度为0的点的数量+入度为0的点 ...

  6. luogu P1494 岳麓山上打水 [iddfs]

    题目描述 今天天气好晴朗,处处好风光,好风光!蝴蝶儿忙啊,蜜蜂也忙,信息组的同学们更加忙.最近,由于XX原因,大家不得不到岳麓山去提水.55555555~,好累啊. 信息组有一个容量为q升的大缸,由于 ...

  7. Jenkins2 实现持续交付初次演练(MultiJob,Pipeline,Blue Ocean)

    背景 项目需要用到自动部署,但可获取外网的节点机器只有一台,那只能同过主节点机器进行构建完成然后分发至对应服务器进行启动更新. 目前已尝试过三种方式: 1.Pipeline-Trigger param ...

  8. 《HelloGitHub》第 16 期

    <HelloGitHub>第 16 期 兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这是一个面向编程新手.热爱编程. ...

  9. 有关struts中DispatchAction的用法小结

       今天刚刚看了DispatchAction觉得这个东西有点意思,所以就写点东西,通过它的名字我想应该可以明白它的作用了,用于分发的Action,主要的好处是把一些功能类似的Action放到一个Ac ...

  10. iOS App内存优化之 解决UIImagePickerController的图片对象占用RAM过高问题

    这个坑会在特定的情况下特别明显: 类似朋友圈的添加多张本地选择\拍照 的图片 并在界面上做一个预览功能 由于没有特别的相机\相册需求,则直接使用系统自带的UIImagePickerController ...