栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Mybatis级联映射与懒加载

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Mybatis级联映射与懒加载

级联映射与懒加载
  • 概述
  • 一对多关联映射
  • 一对一关联映射
  • Discriminator详解
  • 级联映射实现原理(重要!!!)
    • ResultMap概述
    • ResultMap解析过程
    • 级联映射实现原理
  • 懒加载机制
  • 懒加载实现原理
    • 总结

概述

所谓的懒加载,就是当在一个实体对象中 关联 其他实体对象时,如果不需要获取被关联的实体对象,则不需要为 被关联的实体 执行 额外的查询操作,仅当 调用 当前实体的Getter方法 获取 被关联实体对象时,才会执行一次额外的查询操作
通过这种方式在一定程度上能够减轻数据库的压力

一对多关联映射

MyBatis的Mapper配置中提供了一个标签,用于建立实体间一对多的关系

标签需要嵌套在标签中使用,可以使用标签为实体A的属性b 关联一个 外部的查询Mapper,使用ofType属性 指定 属性b中 存放的类型(实体B),使用select属性 指定 通过执行相关sql 来为实体A的属性b 填充(动词)值

然后只需要在定义Mapper SQL配置时,通过resultMap属性指定结果集映射即可

除了可以通过标签 关联一个 外部定义的Mapper 来完成 一对多关联查询外,MyBatis还支持通过JOIN子句实现一对多查询

这种情况下,标签相当于一个嵌套的ResultMap,通过ofType属性指定 实体A的属性b中存放的类型为实体B,然后通过标签 配置 实体B中的每个属性配置 与 数据库字段之间的映射

一对一关联映射

MyBatis一对一关联映射的配置方式与一对多映射类似,不同的是在定义ResultMap时需要使用标签

在配置ResultMap结果集映射时,通过标签为 实体A的属性b 关联一个 外部SQL Mapper配置,当MyBatis进行结果集映射时,会以b表的字段内容 作为 参数执行一次额外的查询操作,然后使用查询结果 为 实体A的属性b填充值

MyBatis同样支持通过JOIN查询实现一对一级联查询
这种情况下,标签相当于一个嵌套的ResultMap。使用JOIN语句同时查询表a和表b,只需要使用标签 将表b相关的字段 映射到 实体B对应的属性即可

Discriminator详解

该单词的意思是“鉴别器”
从单词含义上并不能看出Discriminator的作用
实际上,MyBatis中的Discriminator类似于Java中的switch语法,能够根据数据库记录中某个字段的值映射到不同的ResultMap

使用标签对表A的字段a进行映射
当字段a值为1时,为实体A的属性a关联一个外部的查询Mapper
当字段a值为2时,则不做映射处理

级联映射实现原理(重要!!!) ResultMap概述

MyBatis的Mapper配置中提供了一个标签,用于建立数据库字段与Java实体属性之间的映射关系
每个ResultMap需要有一个全局唯一的Id,由标签的id属性指定
除此之外,ResultMap还需要通过type属性 指定 与哪一个Java实体进行映射

标签中,需要使用标签 配置 具体的某个表字段 与 Java实体属性之间的映射关系
数据库主键通常使用标签建立映射关系,普通数据库字段则使用标签

除了属性映射外,ResultMap还支持使用构造器映射,构造器映射需要使用标签
使用构造器映射的前提是 建立映射的Java实体需要提供对应的构造方法。标签 用于配置 数据库主键的映射,标签 用于配置 普通数据库字段的映射

总结:
标签中可以使用下面几种子标签

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

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

这些属性的含义如下

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

解析过程如下:
MyBatis中的Mapper配置信息解析都是通过XMLMapperBuilder类完成的,该类提供了一个parse()方法,用于解析Mapper中的所有配置信息

在XMLMapperBuilder的parse()方法中,调用XMLMapperBuilder类的configurationElement()进行处理

在XMLMapperBuilder类的configurationElement()方法中,调用resultMapElements()方法对所有标签进行解析

resultMapElements()方法最终会调用重载的resultMapElement()方法对每个标签进行解析

在XMLMapperBuilder类的resultMapElement()方法中,首先获取标签的所有属性信息,然后对子标签进行解析,接着 创建一个 ResultMapResolver对象,调用ResultMapResolver对象的resolve()方法 返回一个 ResultMap对象

ResultMapResolver对象的resolve()方法的逻辑非常简单,调用MapperBuilderAssistant对象的addResultMap()方法 创建ResultMap对象,并把ResultMap对象 添加到 Configuration对象中

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

级联映射实现原理

StatementHandler组件 与 数据库 完成交互后,会使用 ResultSetHandler组件 对 结果集 进行处理

在PreparedStatementHandler类的query()方法中,调用PreparedStatement对象的execute()方法完成与数据库交互之后,会调用ResultSetHandler对象的handleResultSets()方法对结果集进行处理

ResultSetHandler接口只有一个默认的实现,即DefaultResultSetHandler类

在DefaultResultSetHandler类的handleResultSets()方法中,为了简化对JDBC中ResultSet对象的操作,将ResultSet对象 包装成 ResultSetWrapper对象,然后 获取MappedStatement对象 对应的 ResultMap对象,接着 调用 重载的handleResultSet()方法进行处理

在handleResultSet()方法中做了一些逻辑判断,最终都会调用DefaultResultSetHandler类的handleRowValues()方法进行处理

在DefaultResultSetHandler类的handleRowValues()方法中 判断 ResultMap中是否有嵌套的ResultMap,当使用或标签通过JOIN查询方式进行级联映射时,hasNestedResultMaps()方法的返回值为true

如果有嵌套的ResultMap,则调用handleRowValuesForNestedResultMap()方法进行处理,否则调用handleRowValuesForSimpleResultMap()方法

有嵌套ResultMap时的处理逻辑如下:
handleRowValuesForNestedResultMap()方法 对 结果集对象 进行遍历,处理每一行数据
首先调用resolveDiscriminatedResultMap()方法 处理 标签中 通过标签配置的 鉴别器信息,根据 字段值 获取 对应的ResultMap对象,然后调用DefaultResultSetHandler类的getRowValue()方法将结果集中的一行数据转换为Java实体对象

在getRowValue()方法中,主要做了以下几件事情:

  1. 调用createResultObject()方法 处理 通过标签 配置的 构造器映射,根据 配置信息 找到 对应的构造方法,然后通过MyBatis中的ObjectFactory创建ResultMap关联的实体对象

  2. 调用applyAutomaticMappings()方法处理自动映射,对 未通过标签配置映射的数据库字段 进行 与Java实体属性的映射处理

    在applyAutomaticMappings()方法中,首先获取 未指定映射 的 所有数据库字段 和 对应的Java属性,然后获取对应的字段值,通过反射机制为Java实体对应的属性值赋值

  3. 调用applyPropertyMappings()方法 处理 标签配置的 映射信息。该方法处理逻辑相对简单,对所有标签配置的映射信息进行遍历,然后找到数据库字段对应的值,为Java实体属性赋值

  4. 调用DefaultResultSetHandler类的applyNestedResultMappings()方法处理嵌套的结果集映射

    在applyNestedResultMappings()方法中,首先获取 嵌套ResultMap对象,然后根据 嵌套ResultMap的Id 从缓存中 获取嵌套ResultMap 对应的 Java实体对象,如果能获取到,则调用linkObjects()方法 将 嵌套Java实体 与 外部Java实体进行关联。如果缓存中没有,则调用getRowValue()方法 创建 嵌套ResultMap对应的Java实体对象 并进行 属性映射,然后调用linkObjects()方法与外部的Java实体对象进行关联

懒加载机制

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

MyBatis中提供了懒加载机制,能够帮助我们实现这种需求
MyBatis主配置文件中提供了lazyLoadingEnabled和aggressiveLazyLoading参数用来控制是否开启懒加载机制

  • lazyLoadingEnabled参数值为true时表示开启懒加载,否则表示不开启懒加
  • aggressiveLazyLoading参数用于控制ResultMap默认的加载行为,参数值为false表示ResultMap默认的加载行为为懒加载,否则为积极加载

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

懒加载实现原理

在DefaultResultSetHandler类的handleRowValues()方法中处理结果集时,对嵌套的ResultMap和非嵌套ResultMap做了不同处理

ResultMap对象的hasNestedResultMaps属性值为false,hasNestedQueries属性值为true

MyBatis框架在开启懒加载机制后,handleRowValues()方法 会调用 handleRowValuesForSimpleResultMap()方法 处理 ResultMap映射

在handleRowValuesForSimpleResultMap()方法中,首先调用skipRows()方法 跳过 RowBounds对象指定偏移的行,然后遍历结果集中所有的行,对标签配置的鉴别器进行处理,获取 实际映射的ResultMap对象,接着调用getRowValue()方法处理一行记录,将 数据库行记录 转换为 Java实体对象

在getRowValue()方法中主要做了下面几件事情:

  1. 创建ResultLoaderMap对象,该对象用于存放 懒加载的属性 及 对应的ResultLoader对象,MyBatis中的ResultLoader用于执行一个查询Mapper,然后将执行结果赋值给某个实体对象的属性
  2. 调用createResultObject()方法创建ResultMap对应的Java实体对象,createResultObject()方法中,首先调用重载的createResultObject()方法 使用ObjectFactory对象 创建 Java实体对象,然后判断ResultMap中是否有嵌套的查询,如果有嵌套的查询 并且 开启了 懒加载机制,则通过MyBatis中的ProxyFactory创建实体对象的代理对象。ProxyFactory接口有两种不同的实现,分别为CglibProxyFactory和JavassistProxyFactory。也就是说,MyBatis同时支持使用Cglib和Javassist创建代理对象,具体使用哪种策略创建代理对象,可以在MyBatis主配置文件中通过proxyFactory属性指定
  3. 调用applyAutomaticMappings()方法处理自动映射
  4. 调用applyPropertyMappings()方法处理标签配置的映射字段,该方法中除了为Java实体属性设置值外,还将指定了懒加载的属性添加到ResultLoaderMap对象中
总结

MyBatis中的懒加载实际上是通过动态代理来实现的
当通过MyBatis的配置开启懒加载后,执行第一次查询操作 实际上 返回的是 通过Cglig或者Javassist创建的代理对象
因此,调用代理对象的Getter方法 获取 懒加载属性时,会执行 动态代理 的 拦截方法
在拦截方法中,通过Getter方法名称 获取 Java实体属性名称,然后根据 属性名称 获取 对应的LoadPair对象
LoadPair对象中维护了Mapper的Id,有了Mapper的Id就可以获取对应的MappedStatement对象
接着执行一次额外的查询操作,使用查询结果为懒加载属性赋值

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/657802.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号