Mybatis查询延迟加载详解及实例

Mybatis查询延迟加载详解及实例

1.1     启用延迟加载

Mybatis的延迟加载是针对嵌套查询而言的,是指在进行查询的时候先只查询最外层的SQL,对于内层SQL将在需要使用的时候才查询出来。Mybatis的延迟加载默认是关闭的,即默认是一次就将所有的嵌套SQL一并查了将对象所有的信息都查询出来。开启延迟加载有两种方式。

第一种是在对应的<collection>或<association>标签上指定fetchType属性值为“lazy”。如下示例中我们在查询id为selectByPrimaryKey的查询时会返回BaseResultMap,在BaseResultMap中,我们指定了属性“nodes”是一个集合类型的,而且是需要通过id为selectNodes的查询进行查询的,我们指定了该查询的fetchType为lazy,即延迟加载。

  <resultMap id="BaseResultMap" type="com.elim.learn.mybatis.model.SysWfProcess">

   <id column="id" jdbcType="INTEGER" property="id" />

   <result column="template_id" jdbcType="INTEGER" property="templateId" />

   <result column="creator" jdbcType="INTEGER" property="creator" />

   <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />

   <collection property="nodes" column="id"

    ofType="com.elim.learn.mybatis.model.SysWfNode" select="selectNodes" fetchType="lazy"/>

  </resultMap>

  <resultMap id="SysWfNodeResult" type="com.elim.learn.mybatis.model.SysWfNode">

   <id column="id" jdbcType="INTEGER" property="nodeId" />

   <result column="process_id" jdbcType="INTEGER" property="processId" />

   <result column="node_code" jdbcType="VARCHAR" property="nodeCode" />

   <result column="node_name" jdbcType="VARCHAR" property="nodeName" />

  </resultMap>

  <select id="selectByPrimaryKey" parameterType="java.lang.Integer"

   resultMap="BaseResultMap">

   select

   <include refid="Base_Column_List" />

   from sys_wf_process

   where id = #{id,jdbcType=INTEGER}

  </select>

  <select id="selectNodes"

   resultMap="SysWfNodeResult">

   select id, process_id, node_code, node_name from sys_wf_node

   where process_id=#{id}

  </select>

第二种是开启全局的延迟加载。通过在Mybatis的配置文件的<settings>标签下加上如下配置可开启全局的延迟加载。开启了全局的延迟加载后我们就无需再在各个嵌套的子查询上配置延迟加载了,如果有某一个嵌套的子查询是不需要延迟加载的,可以设置其fetchType=”eager”。设置在嵌套查询上的fetchType可以覆盖全局的延迟加载设置。

   <setting name="lazyLoadingEnabled" value="true"/>

1.2     分析

Mybatis的查询结果是由ResultSetHandler接口的handleResultSets()方法处理的。ResultSetHandler接口只有一个实现,DefaultResultSetHandler。有兴趣的朋友可以去看一下它的源码,看一下它是如何处理结果集的。对于本文的主题,延迟加载相关的一个核心的方法就是如下这个创建返回结果对象的方法。

 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {

  final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();

  final List<Object> constructorArgs = new ArrayList<Object>();

  final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);

  if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {

   final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();

   for (ResultMapping propertyMapping : propertyMappings) {

    // issue gcode #109 && issue #149

    if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {

     return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);

    }

   }

  }

  return resultObject;

 }

在上面方法中我们可以看到Mybatis先是根据正常情况创建一个返回类型对应的对象。当我们的ResultMap是包含子查询的时候,其会在我们正常返回类型对象的基础上创建对应的代理对象。对,你没有看错,就是我们的直接结果是代理对象,而不是子查询对应的属性是代理对象。默认是基于JavassistProxyFactory类创建的代理对象。可以通过Mybatis的全局配置proxyFactory来更改,可选值是CGLIB和JAVASSIST,默认是后者。需要使用CGLIB代理时注意加入CGLIB的包。

   <setting name="proxyFactory" value="CGLIB"/>

回过头来看我们之前的那个延迟加载的配置,我们的一个查询返回的是SysWfProcess类型的对象,其有一个SysWfNode集合类型的nodes属性,nodes属性是通过一个子查询查出来的,而且是延迟加载。这个时候我们来进行以下测试。

 @Test

  public void testLazyLoad1() {

   SysWfProcessMapper mapper = this.session.getMapper(SysWfProcessMapper.class);

   SysWfProcess process = mapper.selectByPrimaryKey(1);

   System.out.println(process.getClass());

  }

这个时候你会发现,上面的测试代码的输出结果是一个代理类,而不是我们自己的com.elim.learn.mybatis.model.SysWfProcess类型。另外如果你启用了日志输出,并且是打印的DEBUG日志,你会看到Mybatis是发了两条SQL进行查询的。

2016-12-23 15:43:21,131 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: select id, template_id, creator, create_time from sys_wf_process where id = ?

2016-12-23 15:43:21,156 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: 1(Integer)

2016-12-23 15:43:21,269 DEBUG [main] (BaseJdbcLogger.java:145) - <==   Total: 1

class com.elim.learn.mybatis.model.SysWfProcess_$$_jvstc25_0

2016-12-23 15:43:21,271 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: select id, process_id, node_code, node_name from sys_wf_node where process_id=?

2016-12-23 15:43:21,272 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: 1(Integer)

2016-12-23 15:43:21,274 DEBUG [main] (BaseJdbcLogger.java:145) - <==   Total: 2

但是如果我们把最后一个System.out.println()去掉,也就是说我们只是从数据库中查询出SysWfProcess对象,而不使用它的时候,通过查看日志输出你会发现Mybatis又只会发送一条SQL,即只是查询出SysWfProcess的信息。这是为什么呢?

1.3     aggressiveLazyLoading

这是因为当我们启用了延迟加载时,我们的查询结果返回的是一个代理对象,当我们访问该代理对象的方法时,都会触发加载所有的延迟加载的对象信息。这也就可以很好的解释上面的场景。但是如果是这样的设计,貌似Mybatis的延迟加载作用不大。但事实并非如此,这只是Mybatis的一个默认策略,我们可以通过Mybatis的全局配置aggressiveLazyLoading来改变它,默认是true,表示延迟加载时将在第一次访问代理对象的方法时就将全部的延迟加载对象加载出来。当设置为false时则会在我们第一次访问延迟加载的对象的时候才会从数据库加载对应的数据。注意在延迟对象未从数据库加载出来前,我们对应延迟对象的属性将是null,因为你没有对它赋值。

  <setting name="aggressiveLazyLoading" value="fasle"/>

1.4     lazyLoadTriggerMethods

那如果我们设置了aggressiveLazyLoading=”false”,但又希望在调用某些方法之前把所有的延迟对象都从数据库加载出来,怎么办呢?这个时候我们可以通过lazyLoadTriggerMethods参数来指定需要加载延迟对象的方法调用。默认是equals、clone、hashCode和toString,也就是说我们在调用代理对象的这些方法之前就会把延迟加载对象从数据库加载出来。

   <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />

Mybatis延迟加载生成的代理对象的代理过程,可以参考ProxyFactory的创建代理对象的过程,以下是基于Javassist创建的代理对象的代理过程,基于CGLIB的代理也是类似的。从下面的代码我们可以看到Mybatis的代理对象需要从数据库加载延迟对象时是在目标方法被调用以前发生的,这就可以保证我们的目标方法被调用时延迟加载的对象已经从数据库中加载出来了。

@Override

  public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {

   final String methodName = method.getName();

   try {

    synchronized (lazyLoader) {

     if (WRITE_REPLACE_METHOD.equals(methodName)) {

      Object original = null;

      if (constructorArgTypes.isEmpty()) {

       original = objectFactory.create(type);

      } else {

       original = objectFactory.create(type, constructorArgTypes, constructorArgs);

      }

      PropertyCopier.copyBeanProperties(type, enhanced, original);

      if (lazyLoader.size() > 0) {

       return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);

      } else {

       return original;

      }

     } else {

      if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {

       if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {

        lazyLoader.loadAll();

       } else if (PropertyNamer.isProperty(methodName)) {

        final String property = PropertyNamer.methodToProperty(methodName);

        if (lazyLoader.hasLoader(property)) {

         lazyLoader.load(property);

        }

       }

      }

     }

    }

    return methodProxy.invoke(enhanced, args);

   } catch (Throwable t) {

    throw ExceptionUtil.unwrapThrowable(t);

   }

  }

 }

本文是介绍的都是基于<collection>这种关联,其实<association>关联的对象的延迟加载也是一样的,它们的默认策略也是一样的。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • Mybatis中的延迟加载案例解析

    一.延迟加载 resultMap可以实现高级映射(使用association.collection实现一对一及一对多映射),association.collection具备延迟加载功能. 延迟加载:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快. 在mybatis核心配置文件中配置: lazyLoadingEnabled.aggressiveLazyLoading 设置项 描述 允许值 默认值 lazyLoadingEnabled 全局性设置

  • mybatis教程之延迟加载详解

    延迟加载 1 使用延迟加载意义 在进行数据查询时,为了提高数据库查询性能,尽量使用单表查询,因为单表查询比多表关联查询速度要快. 如果查询单表就可以满足需求,一开始先查询单表,当需要关联信息时,再关联查询,当需要关联信息再查询这个叫延迟加载. mybatis中resultMap提供延迟加载功能,通过resultMap配置延迟加载. 2 配置mybatis支持延迟加载 在 SqlMapConfig.xml中配置全局参数: <!-- 全局配置参数 --> <settings> <

  • Mybatis查询延迟加载详解及实例

    Mybatis查询延迟加载详解及实例 1.1     启用延迟加载 Mybatis的延迟加载是针对嵌套查询而言的,是指在进行查询的时候先只查询最外层的SQL,对于内层SQL将在需要使用的时候才查询出来.Mybatis的延迟加载默认是关闭的,即默认是一次就将所有的嵌套SQL一并查了将对象所有的信息都查询出来.开启延迟加载有两种方式. 第一种是在对应的<collection>或<association>标签上指定fetchType属性值为"lazy".如下示例中我们

  • MyBatis获取数据库自生成的主键Id详解及实例代码

    MyBatis获取数据库自生成的主键Id详解及实例代码 在使用MySQL数据库时我们一般使用数据库的自增主键自动产生主键.如果在插入主表时,我们需要同时插入从表的数据,这时我们通常需要知道主表插入时自动产生的主键Id值. 下面介绍使用MyBatis进行插入时,如何同时获取数据库自生成的主键: 1.XML配置文件 <insert id="insert" parameterType="Person" useGeneratedKeys="true"

  • spring、mybatis 配置方式详解(常用两种方式)

    在之前的文章中总结了三种方式,但是有两种是注解sql的,这种方式比较混乱所以大家不怎么使用,下面总结一下常用的两种总结方式: 一. 动态代理实现 不用写dao的实现类 这种方式比较简单,不用实现dao层,只需要定义接口就可以了,这里只是为了记录配置文件所以程序写的很简单: 1.整体结构图: 2.三个配置文件以及一个映射文件 (1).程序入口以及前端控制器配置 web.xml <?xml version="1.0" encoding="UTF-8"?> &

  • Mybatis-Plus和Mybatis的区别详解

    原文:https://blog.csdn.net/qq_34508530/article/details/88943858 . 区别一 如果Mybatis Plus是扳手,那Mybatis Generator就是生产扳手的工厂. 通俗来讲-- MyBatis:一种操作数据库的框架,提供一种Mapper类,支持让你用java代码进行增删改查的数据库操作,省去了每次都要手写sql语句的麻烦.但是!有一个前提,你得先在xml中写好sql语句,是不是很麻烦?于是有下面的↓ Mybatis Generat

  • IDEA插件EasyCode及MyBatis最优配置步骤详解

    具体安装步骤,不再赘述,仅附上个人工作.学习中的对 EasyCode 的详细配置. 插件链接地址:https://gitee.com/makejava/EasyCode Type Mapper varchar(\(\d+\))? java.lang.String char(\(\d+\))? java.lang.String text java.lang.String decimal(\(\d+\))? java.lang.Double decimal(\(\d+,\d+\))? java.la

  • Angularjs的$http异步删除数据详解及实例

    Angularjs的$http异步删除数据详解及实例 有人会说删除这东西有什么可讲的,写个删除的service,controller调用一下不就完了. 嗯...看起来是这样,但是具体实现起来真的有这么简单吗?首先有以下几个坑 怎么确定数据是否删除成功? 怎么同步视图的数据库的内容? 1.思路 1.实现方式一 删除数据库中对应的内容,然后将$scope中的对应的内容splice 2.实现方式二 删除数据库中对应的内容,然后再reload一下数据(也就是再调用一次查询方法,这种消耗可想而知,并且还要

  • SpringMvc+Mybatis+Pagehelper分页详解

    最近公司需要做一个告警页面的功能,需要分页,查了很多资料发现PageHelper比较合适 故写一篇从零开始的PageHelper使用的教程,也记录下忙活一天的东西 1.首先需要在项目中添加PageHelper的依赖,这里我用的Maven添加 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>

  • spring Boot与Mybatis整合优化详解

    SpringBoot官方文档http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ 关于spring-boot与mybatis整合优化方面的介绍,就是Mybatis-Spring-boot-starter的介绍: 1.取消spring-mybatis.xml配置 ①自动检测已存在的Datasource 之前,需要在spring-mybatis.xml中配置datasource的Bean,现在只需要在applicat

  • Python 操作MySQL详解及实例

    Python 操作MySQL详解及实例 使用Python进行MySQL的库主要有三个,Python-MySQL(更熟悉的名字可能是MySQLdb),PyMySQL和SQLAlchemy. Python-MySQL资格最老,核心由C语言打造,接口精炼,性能最棒,缺点是环境依赖较多,安装复杂,近两年已停止更新,只支持Python2,不支持Python3. PyMySQL为替代Python-MySQL而生,纯python打造,接口与Python-MySQL兼容,安装方便,支持Python3. SQLA

  • Python操作MongoDB详解及实例

    Python操作MongoDB详解及实例 由于需要在页面展示MongoDB库里的数据,所以考虑使用python操作MongoDB,PyMongo模块是Python对MongoDB操作的接口包,所以首页安装pymongo. 1.安装命令 pip install pymongo 2.查询命令: import pymongo # 创建连接 client = pymongo.MongoClient(host="10.0.2.38", port=27017) # 连接probeb库 db = c

随机推荐