04. Mybatis的resultMap基本应用
resultMap 元素是 MyBatis 中最重要最强大的元素。它就是让你远离 90%的需要从结果集中取出数据的 JDBC 代码的那个东西,而且在一些情形下允许你做一些 JDBC 不支持的事情。事实上,编写相似于对复杂语句联合映射这些等同的代码,也许可以跨过上千行的代码。
ResultMap 的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。
你已经看到简单映射语句的示例了,但没有明确的 resultMap。比如:
这样一个语句简单作用于所有列被自动映射到 HashMap 的键上,这由resultType 属性指定。这在很多情况下是有用的,但是 HashMap 不能很好描述一个领域模型。那样你的应用程序将会使用 JavaBeans 或 POJOs来作为领域模型。MyBatis 对两者都支持。看看下面这个 JavaBean:
package com.someapp.model;public class User { private int id; private String username; private String hashedPassword; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getHashedPassword() { return hashedPassword; } public void setHashedPassword(String hashedPassword) { this.hashedPassword = hashedPassword; }}
基于 JavaBean 的规范,上面这个类有 3 个属性:id,username 和hashedPassword。这些在 select 语句中会精确匹配到列名。这样的一个 JavaBean 可以被映射到结果集,就像映射到 HashMap 一样简单。
要记住类型别名是你的伙伴。使用它们你可以不用输入类的全路径。比如:
这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,基于属性名来映射列到JavaBean 的属性上。如果列名没有精确匹配,你可以在列名上使用 select 字句的别名(一个标准的 SQL 特性)来匹配标签。比如:
那么如何试用外部的 resultMap:
引用它的语句使用 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:
ReulstMap只是字段与属性之间的映射关系的集合,那么,真正的字段与属性之间的映射关系,ResultMapping(org.apache.ibatis.mapping.ResultMapping)属性映射来描述。
ResultMap数据结构如下:
package org.apache.ibatis.mapping;import java.util.ArrayList;import java.util.Collections;import java.util.HashSet;import java.util.List;import java.util.Locale;import java.util.Set;import org.apache.ibatis.session.Configuration;/** * 结果映射,保存着表与对象之间的映射关系 * */public class ResultMap { // 对应的id属性 private String id; // 对应的type属性 private Class> type; // 对应除元素外的所有属性映射关系 private List resultMappings; // 对应所有属性映射中带有ID标志的映射关系,包括元素和的子元素 private List idResultMappings; // 对应所有属性映射中带有Constructor标志的映射关系,包括所有子元素 private List constructorResultMappings; // 对应所有属性映射中不带有Constructor标志的映射关系 private List propertyResultMappings; // 对应所有属性映射中的column属性的集合 private Set mappedColumns; // 鉴别器,对应元素 private Discriminator discriminator; // 是否含有嵌套的结果映射, // 如果某个属性映射存在resultMap属性,且不存在resultSet属性,则为true private boolean hasNestedResultMaps; // 是否含有嵌套查询, // 如果某个属性映射存在select属性,则为true private boolean hasNestedQueries; // 是否开启自动映射 private Boolean autoMapping;}
而且在ResultMap类内部有一个静态的构造类,如下:
//静态内部类,建造者模式 public static class Builder { private ResultMapping resultMapping = new ResultMapping(); public Builder(Configuration configuration, String property, String column, TypeHandler> typeHandler) { this(configuration, property); resultMapping.column = column; resultMapping.typeHandler = typeHandler; } public Builder(Configuration configuration, String property, String column, Class> javaType) { this(configuration, property); resultMapping.column = column; resultMapping.javaType = javaType; } public Builder(Configuration configuration, String property) { resultMapping.configuration = configuration; resultMapping.property = property; resultMapping.flags = new ArrayList(); resultMapping.composites = new ArrayList(); resultMapping.lazy = configuration.isLazyLoadingEnabled(); } public Builder javaType(Class> javaType) { resultMapping.javaType = javaType; return this; } public Builder jdbcType(JdbcType jdbcType) { resultMapping.jdbcType = jdbcType; return this; } public Builder nestedResultMapId(String nestedResultMapId) { resultMapping.nestedResultMapId = nestedResultMapId; return this; } public Builder nestedQueryId(String nestedQueryId) { resultMapping.nestedQueryId = nestedQueryId; return this; } public Builder resultSet(String resultSet) { resultMapping.resultSet = resultSet; return this; } public Builder foreignColumn(String foreignColumn) { resultMapping.foreignColumn = foreignColumn; return this; } public Builder notNullColumns(Set notNullColumns) { resultMapping.notNullColumns = notNullColumns; return this; } public Builder columnPrefix(String columnPrefix) { resultMapping.columnPrefix = columnPrefix; return this; } public Builder flags(List flags) { resultMapping.flags = flags; return this; } public Builder typeHandler(TypeHandler> typeHandler) { resultMapping.typeHandler = typeHandler; return this; } public Builder composites(List composites) { resultMapping.composites = composites; return this; } public Builder lazy(boolean lazy) { resultMapping.lazy = lazy; return this; } public ResultMapping build() { // lock down collections resultMapping.flags = Collections.unmodifiableList(resultMapping.flags); resultMapping.composites = Collections.unmodifiableList(resultMapping.composites); resolveTypeHandler(); validate(); return resultMapping; } //一些验证逻辑,验证result map有没有写错 private void validate() { // Issue #697: cannot define both nestedQueryId and nestedResultMapId if (resultMapping.nestedQueryId != null && resultMapping.nestedResultMapId != null) { throw new IllegalStateException("Cannot define both nestedQueryId and nestedResultMapId in property " + resultMapping.property); } // Issue #5: there should be no mappings without typehandler if (resultMapping.nestedQueryId == null && resultMapping.nestedResultMapId == null && resultMapping.typeHandler == null) { throw new IllegalStateException("No typehandler found for property " + resultMapping.property); } // Issue #4 and GH #39: column is optional only in nested resultmaps but not in the rest if (resultMapping.nestedResultMapId == null && resultMapping.column == null && resultMapping.composites.isEmpty()) { throw new IllegalStateException("Mapping is missing column attribute for property " + resultMapping.property); } if (resultMapping.getResultSet() != null) { int numColums = 0; if (resultMapping.column != null) { numColums = resultMapping.column.split(",").length; } int numForeignColumns = 0; if (resultMapping.foreignColumn != null) { numForeignColumns = resultMapping.foreignColumn.split(",").length; } if (numColums != numForeignColumns) { throw new IllegalStateException("There should be the same number of columns and foreignColumns in property " + resultMapping.property); } } } private void resolveTypeHandler() { if (resultMapping.typeHandler == null && resultMapping.javaType != null) { Configuration configuration = resultMapping.configuration; TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType); } } public Builder column(String column) { resultMapping.column = column; return this; } }
ResultMapping数据结构如下:
package org.apache.ibatis.mapping;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.Set;import org.apache.ibatis.session.Configuration;import org.apache.ibatis.type.JdbcType;import org.apache.ibatis.type.TypeHandler;import org.apache.ibatis.type.TypeHandlerRegistry;/** * 字段映射 一个ResultMapping实例对应ResultSet中一个字段到javaBean中一个属性的映射关系 * * sql元素中,除了子元素以外的其他元素都会生成此类型实例, * 其中包括: * * * 内部包含Builder类,负责数据的整合和校验,不负责传入参数的解析 * @author Administrator * */public class ResultMapping { // 核心配置对象 private Configuration configuration; // 属性名,对应元素的property属性 private String property; // 字段名,对应元素的column属性 private String column; // 属性的java类型,对应元素的javaType属性 private Class> javaType; // 字段的jdbc类型,对应元素的jdbcType属性 private JdbcType jdbcType; // 类型处理器,对应元素的typeHandler属性 private TypeHandler> typeHandler; // 对应元素的resultMap属性应用名称空间后的结果 private String nestedResultMapId; // 对应元素的select属性应用名称空间后的结果 private String nestedQueryId; // 对应元素的notNullColumn属性拆分后的结果 private Set notNullColumns; // 对应元素的columnPrefix属性 private String columnPrefix; // 处理后的标志,标志共两个:id和constructor private List flags; // 对应元素的column属性拆分后生成的结果,composites.size()>0会时column为null private List composites; // 对应元素的resultSet属性 private String resultSet; // 对应元素的foreignColumn属性 private String foreignColumn; // 是否延迟加载,对应元素的fetchType属性值,lazy则为true否则为false // 也直接从配置对象中读取 private boolean lazy;}