MyBatis 源码学习13——ResultMap

一、ResultMap:

ResultMap:
保存Java实体属性与数据库表字段之间的映射关系,是实现级联映射和懒加载机制的基础。

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

MyBatis的Mapper配置中提供了一个<resultMap>标签,用于建立数据库字段与Java实体属性之间的映射关系。
在这里插入图片描述

<resultMap>子标签:

<constructor>:该标签用于建立构造器映射。该标签有两个子标签,<idArg>标签用于配置主键映射,标记出主键,可以提高整体性能;<arg>标签用于配置普通字段的映射。

<id>:用于配置数据库主键映射,标记出数据库主键,有助于提高整体性能。

<result>:用于配置数据库字段与Java实体属性之间的映射关系。

<association>:用于配置一对一关联映射,可以关联一个外部的查询Mapper或者配置一个嵌套的ResultMap。

<collection>:用于配置一对多关联映射,可以关联一个外部的查询Mapper或者配置一个嵌套的ResultMap。

<discriminator>:用于配置根据字段值使用不同的ResultMap。该标签有一个子标签,<case>标签用于枚举字段值对应的ResultMap,类似于Java中的switch语法。

ResultMap解析过程:
MyBatis在启动时,所有配置信息都会被转换为Java对象,标签信息会转为ResultMap对象。

ResultMap类
在这里插入图片描述

• 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对象的过程

MyBatis中的Mapper配置信息解析是通过XMLMapperBuilder类完成的,该类提供了一个parse()方法,用于解析Mapper中的所有配置信息:
在这里插入图片描述

在XMLMapperBuilder的parse()方法中,调用XMLMapperBuilder类的configurationElement()方法,然后configurationElement()方法又调用resultMapElements()方法对所有<resultMap>标签进行解析。
在这里插入图片描述

resultMapElements()方法最终会调用重载的resultMapElement()方法对每个<resultMap>标签进行解析:
在这里插入图片描述

1.首先获取<resultMap>标签的所有属性信息,
2.然后对<id>等子标签进行解析,通过buildResultMappingFromContext(),创建字段的ResultMapping对象
3.接着创建一个ResultMapResolver对象,
4.调用ResultMapResolver对象的resolve()方法返回一个ResultMap对象。
在这里插入图片描述

ResultMapResolver对象的resolve()方法调用了MapperBuilderAssistant的addResultMap()方法:
在这里插入图片描述

1.首先判断该ResultMap是否继承了其他ResultMap。如果是,则获取父ResultMap对象,然后去除父ResultMap中的构造器映射信息,将父ResultMap中配置的映射信息添加到当前ResultMap对象。

2.然后通过建造者模式在ResultMap.Builder类中创建一个ResultMap对象,然后为ResultMap对象的所有属性赋值。

3.把ResultMap对象添加到Configuration对象的属性resultMaps中,key为ResultMap对象的id,value为ResultMap对象。

二、映射实现原理:

StatementHandler组件与数据库完成交互后,会使用ResultSetHandler组件对结果集进行处理。
PreparedStatementHandler类的query()方法:
在这里插入图片描述

1.调用PreparedStatement对象的execute()方法完成与数据库交互,

2.调用ResultSetHandler对象的handleResultSets()方法对结果集进行处理。

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

DefaultResultSetHandler类中handleResultSets()方法
在这里插入图片描述

1.为了简化对JDBC中ResultSet对象的操作,将ResultSet对象包装成ResultSetWrapper对象,

2.然后获取MappedStatement对象对应的ResultMap对象,接着调用重载的handleResultSet()方法,handleResultSet()方法中做了一些逻辑判断,但最终都会调用DefaultResultSetHandler类的handleRowValues()方法。
在这里插入图片描述

在DefaultResultSetHandler类的handleRowValues()方法中处理结果集时,对嵌套的ResultMap和非嵌套ResultMap做了不同处理:
在这里插入图片描述

方法中判断ResultMap中是否有嵌套的ResultMap,
当使用或标签通过JOIN查询方式进行级联映射时,hasNestedResultMaps()方法的返回值为true,调用handleRowValuesForNestedResultMap()方法;
当使用和标签关联一个外部的查询Mapper时,ResultMap对象的hasNestedResultMaps属性值为false,调用handleRowValuesForSimpleResultMap()方法。

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

在getRowValue()方法中:
在这里插入图片描述

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

2.调用applyAutomaticMappings()方法处理自动映射,对未通过<result>标签配置映射的数据库字段进行与Java实体属性的映射处理。
方法中首先获取未指定映射的所有数据库字段和对应的Java属性,然后获取对应的字段值,通过反射机制为Java实体对应的属性值赋值。
在这里插入图片描述

3.调用applyPropertyMappings()方法处理<result>标签配置的映射信息。对所有<result>标签配置的映射信息进行遍历,然后找到数据库字段对应的值,为Java实体属性赋值。applyPropertyMappings()方法的实现代码如下:
在这里插入图片描述

4.调用DefaultResultSetHandler类的applyNestedResultMappings()方法处理嵌套的结果集映射。applyNestedResultMappings()方法实现如下:
在这里插入图片描述

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

没有嵌套的ResultMap,handleRowValuesForSimpleResultMap()方法:
在这里插入图片描述

1.首先调用skipRows()方法跳过RowBounds对象指定偏移的行,

2.然后遍历结果集中所有的行,对标签配置的鉴别器进行处理,获取实际映射的ResultMap对象,

3.接着调用getRowValue()方法处理一行记录,将数据库行记录转换为Java实体对象。getRowValue()方法实现如下:

在getRowValue():
1.创建ResultLoaderMap对象,该对象用于存放懒加载的属性及对应的ResultLoader对象,MyBatis中的ResultLoader用于执行一个查询Mapper,然后将执行结果赋值给某个实体对象的属性。

2.调用createResultObject()方法创建ResultMap对应的Java实体对象,我们需要重点关注该方法的实现,代码如下:

createResultObject()方法中:
在这里插入图片描述

1.首先调用重载的createResultObject()方法使用ObjectFactory对象创建Java实体对象,

2.然后判断ResultMap中是否有嵌套的查询,如果有嵌套的查询并且开启了懒加载机制,则通过MyBatis中的ProxyFactory创建实体对象的代理对象。
ProxyFactory接口有两种不同的实现,分别为CglibProxyFactory和JavassistProxyFactory。
也就是说,MyBatis同时支持使用Cglib和Javassist创建代理对象,具体使用哪种策略创建代理对象,可以在MyBatis主配置文件中通过proxyFactory属性指定。

3.调用applyAutomaticMappings()方法处理自动映射,

4.调用applyPropertyMappings()方法处理标签配置的映射字段,该方法中除了为Java实体属性设置值外,还将指定了懒加载的属性添加到ResultLoaderMap对象中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值