MyException - 我的异常网
当前位置:我的异常网» Java相关 » MyBatis从入门到抛却六:延迟加载、一级缓存、二级

MyBatis从入门到抛却六:延迟加载、一级缓存、二级缓存

www.MyException.Cn  网友分享于:2013-09-12  浏览:0次
MyBatis从入门到放弃六:延迟加载、一级缓存、二级缓存

前言

       使用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

2楼幕三少
mark
1楼幕三少
.net go java ?

文章评论

十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
中美印日四国程序员比较
中美印日四国程序员比较
那些争议最大的编程观点
那些争议最大的编程观点
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
 程序员的样子
程序员的样子
一个程序员的时间管理
一个程序员的时间管理
总结2014中国互联网十大段子
总结2014中国互联网十大段子
10个调试和排错的小建议
10个调试和排错的小建议
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
鲜为人知的编程真相
鲜为人知的编程真相
我的丈夫是个程序员
我的丈夫是个程序员
程序员和编码员之间的区别
程序员和编码员之间的区别
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
代码女神横空出世
代码女神横空出世
程序员应该关注的一些事儿
程序员应该关注的一些事儿
程序员必看的十大电影
程序员必看的十大电影
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
如何成为一名黑客
如何成为一名黑客
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
Java程序员必看电影
Java程序员必看电影
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
编程语言是女人
编程语言是女人
漫画:程序员的工作
漫画:程序员的工作
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
为什么程序员都是夜猫子
为什么程序员都是夜猫子
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
老程序员的下场
老程序员的下场
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
程序员都该阅读的书
程序员都该阅读的书
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有