mybatis查询延迟加载

elim168 elim168     2022-12-05     572

关键词:

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 columnPrefixthrows SQLException

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

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

    final Object resultObject = createResultObject(rswresultMapconstructorArgTypesconstructorArgscolumnPrefix);

    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(resultObjectlazyLoaderconfigurationobjectFactoryconstructorArgTypesconstructorArgs);

       

     

   

    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[] argsthrows 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(typeconstructorArgTypesconstructorArgs);

           

            PropertyCopier.copyBeanProperties(typeenhancedoriginal);

            if (lazyLoader.size() > 0)

              return new JavassistSerialStateHolder(original,lazyLoader.getProperties(), objectFactoryconstructorArgTypes,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(enhancedargs);

       catch (Throwable t)

        throw ExceptionUtil.unwrapThrowable(t);

     

   

 

 

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

 

 

(注:本文是基于Mybatis3.3.1所写,写于2016年12月23日星期五)

mybatis学习11mybatis中的延迟加载

1.什么是延迟加载  举个例子:如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。所以延迟加载即先从单表查询... 查看详情

mybatis探究之延迟加载和缓存

mybatis探究之延迟加载和缓存一、什么是延迟加载1.延迟加载的概念在mybatis进行多表查询时,并非所有的查询都需要立即进行。例如在查询带有账户信息的用户信息时,我们们并不需要总是在加载用户信息时就一定要加载他的账... 查看详情

mybatis入门基础----延迟加载

...延迟加载二、使用association实现延迟加载三、延迟加载在mybatis核心配置文件sqlMapConfig.xml中的配置回到顶部一、什么是延迟加载  resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collectio... 查看详情

延迟加载

   MyBatis中的延迟加载,也称为懒加载,是指在进行关联查询时,按照设置延迟加载规则推迟对关联对象的select查询。延迟加载可以有效的减少数据库压力。 注意:MyBatis的延迟加载只是对关联对象的查询有延迟设... 查看详情

mybatis延迟加载缓存逆向工程

一、Mybatis中的延迟加载   1、延迟加载背景:Mybatis中Mapper配置文件中的resultMap可以实现高级映射(使用association、collection实现一对一及一对多(多对多)映射),同样的association、collection具备延迟加载功能。所谓延迟加载,... 查看详情

11.延迟加载

延迟加载  MyBatis中的延迟加载,也称为懒加载,是指在进行关联查询时,按照设置延迟规则推迟对关联对象的 select查询。延迟加载可以有效的减少数据库压力。 需要注意的是,MyBatis的延迟加载只是对关联对象的查询有... 查看详情

mybatis学习总结——延迟加载(代码片段)

...户信息按需延迟加载的resultMap定义--><resultMaptype="com.mybatis.entity.Orders"id="ord 查看详情

mybatis-注解

mybatis延迟加载延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张... 查看详情

mybatis学习——分步查询与延迟加载

声明:面试是遇到延迟加载问题,在网页搜索到此篇文章,感觉很有帮助,留此学习之用!一、分步查询分步查询通常应用于关联表查询,如:电商平台,查询订单信息时需要查询部分的用户信息;OA系统查询个人信息时需要查... 查看详情

mybatis_延迟加载

延迟加载所谓的mybatis延迟加载,指的是当进行多个表之间的关联查询时,先从单表中进行查询,按照一定的设计规则,需要时再对该表所关联的表单继续进行查询好比如在某个购物网站上填写收货地址时,先加载省份,等用户... 查看详情

mybatis专题-延迟加载(代码片段)

....搭建一对多测试环境3.使用collection实现的延迟加载4.开启Mybatis的延迟加载策略1.什么是延迟加载?前面我们讲解Mybatis中的一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。在实际使用中的大... 查看详情

mybatis测试resultmap,分步查询以及延迟加载

什么是resultMap? resultMap即自定义结果集映射规则,之前我们使用的resultType是MyBatis为我们提供的默认映射规则,使用方法如下:<mappernamespace="com.zgz.MyBatis.dao.DeptMapper"><selectid="getDeptById"resultType="com.zgz.MyBatis.bean. 查看详情

mybatis-延迟加载

   立即加载:只要一调用就立即发起加载。举例:一个用户有100个账户,查询账户时有必要把用户信息也显示出来。  延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时... 查看详情

mybatis的延迟加载

首先我们先思考一个问题,假设:在一对一,或者一多中,我们有一个用户,他有100个账户。  问题1:在查询用户的时候,要不要把关联的账户查出来?  问题2:在查询账户的时候,要不要把关联的用户查出来?  解答... 查看详情

mybatis一对多查询及延迟加载

...查询的结果集存储在User对象的上哪个属性。ofType="com.demo.mybatis.po.Order":指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。MyBatis根据关联对象查询的select的语句的执行时机,分... 查看详情

mybatis框架-第三篇

目录第一章:Mybatis延迟加载策略1.1-什么是延迟加载?1.2-需求1.3-解决方案1.4-代码实现第二章:Mybatis缓存2.1-Mybatis一级缓存2.2-Mybatis二级缓存第一章:Mybatis延迟加载策略1.1-什么是延迟加载?就是在需要用到数据时才进行加载,不... 查看详情

mybatis文件映射之利用延迟加载解决collection分布查询

Employee.javapublicclassEmployee{privateIntegerid;privateStringlastName;privateStringgender;privateStringemail;Departmentdept;}Department.javapublicclassDepartment{privateIntegerid;privateStringdeptNa 查看详情

mybatis延迟加载和缓存机制(一级二级缓存)

...息。所以这就是突出了懒这个特点。真是懒啊。    Mybatis中resultMap可以实 查看详情