MyBatis——级联映射与懒加载

MyBatis其中一个比较强大的功能是支持查询结果级联映射。使用MyBatis级联映射,我们可以很轻松地实现一对多、一对一或者多对多关联查询,甚至可以利用MyBatis提供的级联映射实现懒加载。所谓的懒加载,就是当我们在一个实体对象中关联其他实体对象时,如果不需要获取被关联的实体对象,则不需要为被关联的实体执行额外的查询操作,仅当调用当前实体的Getter方法获取被关联实体对象时,才会执行一次额外的查询操作。通过这种方式在一定程度上能够减轻数据库的压力。本章我们就来学习一下MyBatis的级联映射和懒加载机制以及它的实现原理。

MyBatis懒加载机制

我们知道MyBatis的级联映射可以通过两种方式实现,其中一种方式是为实体的属性关联一个外部的查询Mapper,这种情况下,MyBatis实际上为实体的属性执行一次额外的查询操作;另一种方式是通过JOIN查询来实现,这种方式需要为实体关联的其他实体对应的属性配置映射关系,通过JOIN查询方式只需要一次查询即可。

在一些情况下,我们需要按需加载,即当我们查询用户信息时,如果不需要获取用户订单信息,则不需要执时订单查询对应的Mapper,仅当调用Getter方法获取订单数据时,才执行一次额外的查询操作。这种方式能够在一定程度上能够减少数据库IO次数,提升系统性能。

MyBatis中提供了懒加载机制,能够帮助我们实现这种需求,接下来我们就来了解一下MyBatis懒加载机制的使用方式。MyBatis主配置文件中提供了lazyLoadingEnabled和aggressiveLazyLoading参数用来控制是否开启懒加载机制。lazyLoadingEnabled参数值为true时表示开启懒加载,否则表示不开启懒加载。aggressiveLazyLoading参数用于控制ResultMap默认的加载行为,参数值为false表示ResultMap默认的加载行为为懒加载,否则为积极加载。

除此之外,<collection>和<association>标签还提供了一个fetchType属性,用于控制级联查询的加载行为,fetchType属性值为lazy时表示该级联查询采用懒加载方式,当fetchType属性值为eager时表示该级联查询采用积极加载方式。

我们可以使用如下配置开启懒加载:

    <settings>
        <!-- 打开延迟加载的开关 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载改为懒加载即按需加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- equals,clone,hashCode,toString等方法不触发懒加载 -->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>

我们可以使用如下测试用例测试MyBatis的懒加载机制:

执行测试用例时,控制台输出内容如下。从控制台日志输出内容可以看出,开启懒加载机制后,当我们调用OrderMapper的getOrderByNo()方法查询订单信息时,Order实体user属性关联的外部Mapper并没有被执行,当我们调用Order对象的getUser()方法获取订单对应的用户信息时,才会执行Order实体user属性关联的Mapper查询用户信息。

MyBatis级联映射实现原理

由于MyBatis中的ResultMap是实现级联映射和懒加载机制的基础,因此在介绍MyBatis级联映射源码之前,我们首先需要对MyBatis中的ResultMap有较为详细的了解。

MyBatis是一个半自动化的ORM框架,可以将数据库中的记录转换为Java实体对象,但是Java实体属性通常采用驼峰命名法,而数据库字段习惯采用下画线分割命名法,因此需要用户指定Java实体属性与数据库表字段之间的映射关系。

MyBatis的Mapper配置中提供了一个<resultMap>标签,用于建立数据库字段与Java实体属性之间的映射关系。下面是一个简单的ResultMap配置:

  <resultMap id="joinedAuthor" type="org.apache.ibatis.domain.blog.Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
    <result property="favouriteSection" column="author_favourite_section"/>
  </resultMap>

如上面的配置所示,每个ResultMap需要有一个全局唯一的Id,由<resultMap>标签的id属性指定。除此之外,ResultMap还需要通过type属性指定与哪一个Java实体进行映射。在<resultMap>标签中,需要使用<id>或<result>标签配置具体的某个表字段与Java实体属性之间的映射关系。数据库主键通常使用<id>标签建立映射关系,普通数据库字段则使用<reuslt>标签。

除了属性映射外,ResultMap还支持使用构造器映射,构造器映射需要使用<constructor>标签。下面是构造器映射案例,配置如下:

    <resultMap id="selectImmutableAuthor" type="org.apache.ibatis.domain.blog.ImmutableAuthor">
        <constructor>
            <idArg column="id" javaType="_int" />
            <arg column="username" javaType="string" />
            <arg column="password" javaType="string" />
            <arg column="email" javaType="string" />
            <arg column="bio" javaType="string" />
        </constructor>
        <result property="favouriteSection" column="favourite_section" javaType="org.apache.ibatis.domain.blog.Section" />
    </resultMap>

如上面的配置所示,使用构造器映射的前提是建立映射的Java实体需要提供对应的构造方法。<idArg>标签用于配置数据库主键的映射,<arg>标签用于配置普通数据库字段的映射。

最后我们来总结一下,<resultMap>标签中可以使用下面几种子标签。

  • <constructor>:该标签用于建立构造器映射。该标签有两个子标签,<idArg>标签用于配置主键映射,标记出主键,可以提高整体性能;<arg>标签用于配置普通字段的映射。
  • <id>:用于配置数据库主键映射,标记出数据库主键,有助于提高整体性能。
  • <result>:用于配置数据库字段与Java实体属性之间的映射关系。
  • <association>:用于配置一对一关联映射,可以关联一个外部的查询Mapper或者配置一个嵌套的ResultMap。
  • <collection>:用于配置一对多关联映射,可以关联一个外部的查询Mapper或者配置一个嵌套的ResultMap。
  • <discriminator>:用于配置根据字段值使用不同的ResultMap。该标签有一个子标签,<case>标签用于枚举字段值对应的ResultMap,类似于Java中的switch语法。

ResultMap解析过程

MyBatis在启动时,所有配置信息都会被转换为Java对象,通过<resultMap>标签配置的结果集映射信息也不例外。MyBatis通过ResultMap类描述<resultMap>标签的配置信息,ResultMap类的所有属性如下:

public class ResultMap {
    /**
   * 配置对象
   */
  private Configuration configuration;

  /**
   * ID
   */
  private String id;

  /**
   * 类型
   */
  private Class<?> type;

  /**
   * 结果映射列表
   */
  private List<ResultMapping> resultMappings;

  /**
   * ID结果映射列表
   */
  private List<ResultMapping> idResultMappings;

  /**
   * 构造函数结果映射列表
   */
  private List<ResultMapping> constructorResultMappings;

  /**
   * 属性结果映射列表
   */
  private List<ResultMapping> propertyResultMappings;

  /**
   * 映射的列集合
   */
  private Set<String> mappedColumns;

  /**
   * 映射的属性集合
   */
  private Set<String> mappedProperties;

  /**
   * 判断字段是否为鉴别器
   */
  private Discriminator discriminator;

  /**
   * 是否有嵌套结果映射
   */
  private boolean hasNestedResultMaps;

  /**
   * 是否有嵌套查询
   */
  private boolean hasNestedQueries;

  /**
   * 是否自动映射
   */
  private Boolean autoMapping;

这些属性的含义如下。

  • Id:通过<resultMap>标签的id属性和Mapper命名空间组成的全局唯一的Id。
  • Type:通过<resultMap>标签的type属性指定与数据库表建立映射的Java实体。
  • resultMappings:通过<result>标签配置的所有数据库字段与Java实体属性之间的映射信息。
  • idResultMappings:通过<id>标签配置的数据库主键与Java实体属性的映射信息。需要注意的是,<id>标签与<result>标签没有本质的区别。
  • constructorResultMappings:通过<constructor>标签配置的构造器映射信息。
  • propertyResultMappings:通过<result>标签配置的数据库字段与Java实体属性的映射信息。
  • mappedColumns:该属性存放所有映射的数据库字段。当使用columnPrefix属性配置了前缀时,MyBatis会对mappedColumns属性进行遍历,为所有数据库字段追加columnPrefix属性配置的前缀。
  • mappedProperties:该属性存放所有映射的Java实体属性信息。
  • discriminator:该属性为在<resultMap>标签中通过<discriminator>标签配置的鉴别器信息。
  • hasNestedResultMaps:该属性用于标识是否有嵌套的ResultMap,当使用<association>或<collection>标签以JOIN查询方式配置一对一或一对多级联映射时,<association>或<collection>标签相当于一个嵌套的ResultMap,因此hasNestedResultMaps属性值为true。
  • hasNestedQueries:该属性用于标识是否有嵌套的查询,当使用<association>或<collection>标签关联一个外部的查询Mapper建立一对一或一对多级联映射时,hasNestedQueries属性值为true。
  • autoMapping:autoMapping属性为true,表示开启自动映射,即使未使用<result>或<id>标签配置映射字段,MyBatis也会自动对这些字段进行映射。

弄清楚ResultMap类各属性的作用后,我们来了解一下<resultMap>标签解析生成ResultMap对象的过程。MyBatis中的Mapper配置信息解析都是通过XMLMapperBuilder类完成的,该类提供了一个parse()方法,用于解析Mapper中的所有配置信息,代码如下:

  /**
   * 解析方法。
   */
  public void parse() {
      /**
       * 如果资源还没有加载过,则执行以下操作:
       * 1. 获取配置元素,传入 mapper 的根节点
       * 2. 将资源添加到配置的已加载资源列表中
       * 3. 绑定命名空间对应的映射器
       */
      if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          bindMapperForNamespace();
      }

      /**
       * 解析待处理的结果映射
       */
      parsePendingResultMaps();

      /**
       * 解析待处理的缓存引用
       */
      parsePendingCacheRefs();

      /**
       * 解析待处理的语句
       */
      parsePendingStatements();
  }

如上面的代码所示,在XMLMapperBuilder的parse()方法中,调用XMLMapperBuilder类的configurationElement()进行处理,该方法定义如下:

  /**
   * 处理配置元素
   * 
   * @param context XML节点
   */
  private void configurationElement(XNode context) {
    try {
      // 获取命名空间
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper的命名空间不能为空");
      }
      // 设置当前命名空间
      builderAssistant.setCurrentNamespace(namespace);
      // 处理缓存引用元素
      cacheRefElement(context.evalNode("cache-ref"));
      // 处理缓存元素
      cacheElement(context.evalNode("cache"));
      // 处理参数映射元素
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 处理结果映射元素
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 处理SQL元素
      sqlElement(context.evalNodes("/mapper/sql"));
      // 根据上下文构建语句
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("解析Mapper XML时发生错误。XML的位置是'" + resource + "',原因:" + e, e);
    }
  }

在XMLMapperBuilder类的configurationElement()方法中,调用resultMapElements()方法对所有<resultMap>标签进行解析。resultMapElements()方法最终会调用重载的resultMapElement()方法对每个<resultMap>标签进行解析,该方法代码如下:

  /**
   * 根据给定的结果映射节点生成结果映射元素
   *
   * @param resultMapNode        结果映射节点
   * @param additionalResultMappings  额外的结果映射
   * @param enclosingType        包含的类型
   * @return                      结果映射元素
   */
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

如上面的代码所示,在XMLMapperBuilder类的resultMapElement()方法中,首先获取<resultMap>标签的所有属性信息,然后对<id>、<constructor>、<discriminator>子标签进行解析,接着创建一个ResultMapResolver对象,调用ResultMapResolver对象的resolve()方法返回一个ResultMap对象。ResultMapResolver对象的resolve()方法代码如下:

  /**
   * 解析结果映射
   * 
   * @return 解析结果
   */
  public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
  }

如上面的代码所示,ResultMapResolver对象的resolve()方法的逻辑非常简单,调用MapperBuilderAssistant对象的addResultMap()方法创建ResultMap对象,并把ResultMap对象添加到Configuration对象中。MapperBuilderAssistant的addResultMap()方法代码如下:

  /**
   * 添加一个结果映射到配置中
   *
   * @param id           结果映射的id
   * @param type          结果映射关联的类
   * @param extend        父结果映射的id
   * @param discriminator 分类歧视字段
   * @param resultMappings 结果映射的集合
   * @param autoMapping   是否自动映射
   * @return 结果映射对象
   */
  public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    id = applyCurrentNamespace(id, false);
    extend = applyCurrentNamespace(extend, true);

    if (extend != null) {
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("无法找到id为'" + extend + "'的父结果映射");
      }
      ResultMap resultMap = configuration.getResultMap(extend);
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      extendedResultMappings.removeAll(resultMappings);
      // 如果当前结果映射声明了构造函数,则移除父结果映射中所有的构造函数参数
      boolean declaresConstructor = false;
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      if (declaresConstructor) {
        extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
      }
      resultMappings.addAll(extendedResultMappings);
    }
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    configuration.addResultMap(resultMap);
    return resultMap;
  }

在MapperBuilderAssistant类的addResultMap()方法中,首先判断该ResultMap是否继承了其他ResultMap。如果是,则获取父ResultMap对象,然后去除父ResultMap中的构造器映射信息,将父ResultMap中配置的映射信息添加到当前ResultMap对象,最后通过建造者模式创建ResultMap对象。在ResultMap.Builder类中创建了一个ResultMap对象,然后为ResultMap对象的所有属性赋值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 智慧社区背景与挑战 随着城市化的快速发展,社区面临健康、安全、邻里关系和服务质量等多方面的挑战。华为技术有限公司提出智慧社区解决方案,旨在通过先进的数字化技术应对这些问题,提升城市社区的生活质量。 2. 技术推动智慧社区发展 技术进步,特别是数字化、无线化、移动化和物联化,为城市社区的智慧化提供了可能。这些技术的应用不仅提高了社区的运行效率,也增强了居民的便利性和安全性。 3. 智慧社区的核心价值 智慧社区承载了智慧城市的核心价值,通过全面信息化处理,实现对城市各个方面的数字网络化管理、服务与决策功能,从而提升社会服务效率,整合社会服务资源。 4. 多层次、全方位的智慧社区服务 智慧社区通过构建和谐、温情、平安和健康四大社区模块,满足社区居民的多层次需求。这些服务模块包括社区医疗、安全监控、情感沟通和健康监测等。 5. 智慧社区技术框架 智慧社区技术框架强调统一平台的建设,设立数据中心,构建基础网络,并通过分层建设,实现平台能力及应用的可持续成长和扩展。 6. 感知统一平台与服务方案 感知统一平台是智慧社区的关键组成部分,通过统一的RFID身份识别和信息管理,实现社区服务的智能化和便捷化。同时,提供社区内外监控、紧急救助服务和便民服务等。 7. 健康社区的构建 健康社区模块专注于为居民提供健康管理服务,通过整合医疗资源和居民接入,实现远程医疗、慢性病管理和紧急救助等功能,推动医疗模式从治疗向预防转变。 8. 平安社区的安全保障 平安社区通过闭路电视监控、防盗报警和紧急求助等技术,保障社区居民的人身和财产安全,实现社区环境的实时监控和智能分析。 9. 温情社区的情感沟通 温情社区着重于建立社区居民间的情感联系,通过组织社区活动、一键呼叫服务和互帮互助平台,增强邻里间的交流和互助。 10. 和谐社区的资源整合 和谐社区作为社会资源的整合协调者,通过统一接入和身份识别,实现社区信息和服务的便捷获取,提升居民生活质量,促进社区和谐。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值