文章目录
- 一、目标:ResultMap映射参数
 - 二、设计:ResultMap映射参数
 - 三、实现:ResultMap映射参数
 - 3.1 工程结构
 - 3.2 ResultMap映射参数类图
 - 3.3 添加类型处理器
 - 3.3.1 日期类型处理器
 - 3.3.2 类型处理器注册机
 
- 3.4 存放映射对象
 - 3.4.1 结果标志
 - 3.4.2 结果映射Map
 - 3.4.3 封装结果映射
 - 3.4.4 解析结果映射
 
- 3.5 解析映射配置
 - 3.5.1 构建器基类
 - 3.5.2 映射构建器助手
 - 3.5.3 解析映射配置
 
- 3.6 使用映射对象
 
- 四、测试:ResultMap映射参数
 - 4.1 测试环境配置
 - 4.1.1 在mybatis数据库添加activity数据表
 - 4.1.2 提供DAO接口和实体类
 - 4.1.3 配置数据源和配置Mapper
 - 4.1.4 活动接口配置文件
 
- 4.2 单元测试
 
- 五、总结:ResultMap映射参数
 
一、目标:ResultMap映射参数
💡 如何将数据库表中的下划线的字段名称,映射成Java代码中的驼峰字段?
- 通常在数据库表字段的命名中,所定义的规范是希望使用小写的英文字母和下划线的方式组合使用。 
  
- 例如:雇员表中的雇员姓名,则使用 
employee_name表字段描述。 
 - 例如:雇员表中的雇员姓名,则使用 
 - 但这样的字段定义与 Java 代码开发中的 PO 数据库对象中的字段,是不能一一匹配的。因为 Java 代码中会使用驼峰的方式进行命名。 
  
- 同样是雇员姓名在 Java 代码中则是 
employeeName的方式进行表示。 
 - 同样是雇员姓名在 Java 代码中则是 
 - 在使用 Mybatis 框架的时候,如果遇到这样的字段,则需要通过把数据库表中的下划线的字段名称,映射成 Java 代码中的驼峰字段,这样才能在执行查询操作的时候,正确的把数据库中的结果映射到 Java 代码的返回对象上。
 注意:在 Mybatis 中也可以使用例如employee_name as employeeName的方式进行处理,但在整个编程中并不是太优雅。- 因为所有的查询都要做 
as映射,那么使用一个统一的字段映射更加合理。 
- 因为所有的查询都要做 
 
二、设计:ResultMap映射参数
💡 用一个标准的结构适配非映射类的对象属性。
- 之前处理解析 Mapper XML 中的 
select语句下配置的resultType时,其实就已经添加了ResultMap、ResultMapping的映射结构。 - 不过之前对于返回类型的处理都直接是对象类型,没有使用映射参数。而这也就代表着,在处理查询结果集后,SQL 对应的查询字段与 Java 代码中类的属性字段是一一对应的,所以不要使用映射,直接按照匹配的属性名称设置值即可。
 - 之所以采用这样通用的结果类型包装结构,是为了做统一的方式处理,也相当于是在定义标准,用一个标准的结构适配非映射类的对象属性。
 - 可以借助开发好的 ResultMap 封装参数结构,完善对字段映射的处理。
 

- 完善 ResultMapping 结果映射中 Builder 构建者的对映射属性的保存操作,并使用到解析 Mapper XML 中对 resultMap 元素的处理。
 - 映射参数的解析过程:主要以循环解析 resultMap 的标签集合,摘取核心的 
property、column字段构建出 ResultMapping 结果映射类。- 每一条配置都会创建出一个 ResultMapping 类。
 - 最后这个配置信息会被写入到 Configuration 配置项的 
Map<String, ResultMap> resultMaps的结果映射中。 
 - 最后在程序执行获取到 Mapper 一直调用到 DefaultSqlSession 查询结果封装时,再从配置项中把相关的 ResultMap 读取出来,进行设置属性值。
 
三、实现:ResultMap映射参数
3.1 工程结构
mybatis-step-13
|-src
	|-main
	|	|-java
	|		|-com.lino.mybatis
    |			|-annotations
    |			|	|-Delete.java
	|			|	|-Insert.java
	|			|	|-Select.java
    |			|	|-Update.java
    |			|-binding
    |			|	|-MapperMethod.java
	|			|	|-MapperProxy.java
	|			|	|-MapperProxyFactory.java
    |			|	|-MapperRegistry.java
    |			|-builder
    |			|	|-annotations
    |			|	|	|-MapperAnnotationBuilder.java
    |			|	|-xml
    |			|	|	|-XMLConfigBuilder.java
    |			|	|	|-XMLMapperBuilder.java
    |			|	|	|-XMLStatementBuilder.java
    |			|	|-BaseBuilder.java
    |			|	|-MapperBuilderAssistant.java
    |			|	|-ParameterExpression.java
    |			|	|-ResultMapResolver.java
    |			|	|-SqlSourceBuilder.java
    |			|	|-StaticSqlSource.java
	|			|-datasource
	|			|	|-druid
	|			|	|	|-DruidDataSourceFacroty.java
	|			|	|-pooled
	|			|	|	|-PooledConnection.java
	|			|	|	|-PooledDataSource.java
	|			|	|	|-PooledDataSourceFacroty.java
	|			|	|	|-PoolState.java
	|			|	|-unpooled
	|			|	|	|-UnpooledDataSource.java
	|			|	|	|-UnpooledDataSourceFacroty.java
	|			|	|-DataSourceFactory.java
	|			|-executor
	|			|	|-parameter
	|			|	|	|-ParameterHandler.java
	|			|	|-result
	|			|	|	|-DefaultResultContext.java
	|			|	|	|-DefaultResultHandler.java
	|			|	|-resultset
	|			|	|	|-DefaultResultSetHandler.java
	|			|	|	|-ResultSetHandler.java
	|			|	|	|-ResultSetWrapper.java
	|			|	|-statement
	|			|	|	|-BaseStatementHandler.java
	|			|	|	|-PreparedStatementHandler.java
	|			|	|	|-SimpleStatementHandler.java
	|			|	|	|-StatementHandler.java
	|			|	|-BaseExecutor.java
	|			|	|-Executor.java
	|			|	|-SimpleExecutor.java
    |			|-io
    |			|	|-Resources.java
    |			|-mapping
    |			|	|-BoundSql.java
    |			|	|-Environment.java
    |			|	|-MappedStatement.java
    |			|	|-ParameterMapping.java
    |			|	|-ResultFlag.java
    |			|	|-ResultMap.java
    |			|	|-ResultMapping.java
    |			|	|-SqlCommandType.java
    |			|	|-SqlSource.java
    |			|-parsing
    |			|	|-GenericTokenParser.java
    |			|	|-TokenHandler.java
	|			|-reflection
	|			|	|-factory
	|			|	|	|-DefaultObjectFactory.java
	|			|	|	|-ObjectFactory.java
	|			|	|-invoker
	|			|	|	|-GetFieldInvoker.java
	|			|	|	|-Invoker.java
	|			|	|	|-MethodInvoker.java
	|			|	|	|-SetFieldInvoker.java
	|			|	|-property
	|			|	|	|-PropertyNamer.java
	|			|	|	|-PropertyTokenizer.java
	|			|	|-wrapper
	|			|	|	|-BaseWrapper.java
	|			|	|	|-BeanWrapper.java
	|			|	|	|-CollectionWrapper.java
	|			|	|	|-DefaultObjectWrapperFactory.java
	|			|	|	|-MapWrapper.java
	|			|	|	|-ObjectWrapper.java
	|			|	|	|-ObjectWrapperFactory.java
	|			|	|-MetaClass.java
	|			|	|-MetaObject.java
	|			|	|-Reflector.java
	|			|	|-SystemMetaObject.java
	|			|-scripting
	|			|	|-defaults
	|			|	|	|-DefaultParameterHandler.java
	|			|	|	|-RawSqlSource.java
	|			|	|-xmltags
	|			|	|	|-DynamicContext.java
	|			|	|	|-MixedSqlNode.java
	|			|	|	|-SqlNode.java
	|			|	|	|-StaticTextSqlNode.java
	|			|	|	|-XMLLanguageDriver.java
	|			|	|	|-XMLScriptBuilder.java
	|			|	|-LanguageDriver.java
	|			|	|-LanguageDriverRegistry.java
    |			|-session
    |			|	|-defaults
    |			|	|	|-DefaultSqlSession.java
    |			|	|	|-DefaultSqlSessionFactory.java
    |			|	|-Configuration.java
    |			|	|-ResultContext.java
    |			|	|-ResultHandler.java
    |			|	|-RowBounds.java
    |			|	|-SqlSession.java
    |			|	|-SqlSessionFactory.java
    |			|	|-SqlSessionFactoryBuilder.java
    |			|	|-TransactionIsolationLevel.java
    |			|-transaction
    |			|	|-jdbc
    |			|	|	|-JdbcTransaction.java
    |			|	|	|-JdbcTransactionFactory.java
    |			|	|-Transaction.java
    |			|	|-TransactionFactory.java
    |			|-type
    |			|	|-BaseTypeHandler.java
    |			|	|-DateTypeHandler.java
    |			|	|-IntegerTypeHandler.java
    |			|	|-JdbcType.java
    |			|	|-LongTypeHandler.java
    |			|	|-StringTypeHandler.java
    |			|	|-TypeAliasRegistry.java
    |			|	|-TypeHandler.java
    |			|	|-TypeHandlerRegistry.java
	|-test
		|-java
		|	|-com.lino.mybatis.test
		|	|-dao
		|	|	|-IActivityDao.java
		|	|-po
		|	|	|-Activity.java
		|	|-ApiTest.java
        |-resources
			|-mapper
			|	|-Activity_Mapper.xml
        	|-mybatis-config-datasource.xml
 
3.2 ResultMap映射参数类图

- 以 XMLMapperBuilder 解析为入口,扩展 
resultMapElements方法,解析resultMap映射参数。- 解析过程涉及到 MapperBuilderAssistant 映射器构建助手类的使用,所以需要在 XMLMapperBuilder 构建函数中进行初始化。
 - 参数的解析细节主要在 MapperBuilderAssistant 映射构建器助手中完成。 
    
- 包括解析 
javaTypeClass、typeHandlerInstance,以及封装 XML 配置的基本字段映射信息。 
 - 包括解析 
 
 - 一个 DAO 方法的执行,需要从 Mapper 映射器获取开始,并逐步拿到映射器代理、映射器方法和 DefaultSqlSession 的执行。
 - 最终在执行后封装返回结果时,就可以按照我们已经提供好的结果映射参数进行处理。 
  
- 这部分操作也就是 
DefaultResultSetHandler#applyPropertyMappings的处理过程。 
 - 这部分操作也就是 
 
3.3 添加类型处理器
3.3.1 日期类型处理器
DateTypeHandler.java
package com.lino.mybatis.type;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
/**
 * @description: 日期类型处理器
 * @author: lingjian
 * @createDate: 2022/11/11 14:01
 */
public class DateTypeHandler extends BaseTypeHandler<Date> {
    @Override
    protected void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
        ps.setTimestamp(i, new Timestamp((parameter).getTime()));
    }
    @Override
    protected Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp sqlTimestamp = rs.getTimestamp(columnName);
        if (sqlTimestamp != null) {
            return new Date(sqlTimestamp.getTime());
        }
        return null;
    }
}
 
3.3.2 类型处理器注册机
TypeHandlerRegistry.java
package com.lino.mybatis.type;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
/**
 * @description: 类型处理器注册机
 */
public final class TypeHandlerRegistry {
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<>(16);
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLER_MAP = new HashMap<>(16);
    public TypeHandlerRegistry() {
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());
        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        register(Date.class, new DateTypeHandler());
    }
    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        register(javaType, null, typeHandler);
    }
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (null != javaType) {
            Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.computeIfAbsent(javaType, k -> new HashMap<>(16));
            map.put(jdbcType, handler);
        }
        ALL_TYPE_HANDLER_MAP.put(handler.getClass(), handler);
    }
    @SuppressWarnings("unchecked")
    public TypeHandler<?> getTypeHandler(Class<?> type, JdbcType jdbcType) {
        return getTypeHandler((Type) type, jdbcType);
    }
    public boolean hasTypeHandler(Class<?> javaType) {
        return hasTypeHandler(javaType, null);
    }
    public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {
        return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;
    }
    private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
        TypeHandler<?> handler = null;
        if (jdbcHandlerMap != null) {
            handler = jdbcHandlerMap.get(jdbcType);
            if (handler == null) {
                handler = jdbcHandlerMap.get(null);
            }
        }
        // type driver generics here
        return (TypeHandler<T>) handler;
    }
    public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) {
        return ALL_TYPE_HANDLER_MAP.get(handlerType);
    }
}
 
3.4 存放映射对象
3.4.1 结果标志
ResultFlag.java
package com.lino.mybatis.mapping;
/**
 * @description: 结果标志
 */
public enum ResultFlag {
    /**
     * 结果标志
     */
    ID, CONSTRUCTOR
}
 
3.4.2 结果映射Map
ResultMapping.java
package com.lino.mybatis.mapping;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.JdbcType;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.ArrayList;
import java.util.List;
/**
 * @description: 结果映射Map
 */
public class ResultMapping {
    private Configuration configuration;
    private String property;
    private String column;
    private Class<?> javaType;
    private TypeHandler<?> typeHandler;
    private List<ResultFlag> flags;
    public ResultMapping() {
    }
    public static class Builder {
        private ResultMapping resultMapping = new ResultMapping();
        public Builder(Configuration configuration, String property, String column, Class<?> javaType) {
            resultMapping.configuration = configuration;
            resultMapping.property = property;
            resultMapping.column = column;
            resultMapping.javaType = javaType;
            resultMapping.flags = new ArrayList<>();
        }
        public Builder typeHandler(TypeHandler<?> typeHandler) {
            resultMapping.typeHandler = typeHandler;
            return this;
        }
        public Builder flags(List<ResultFlag> flags) {
            resultMapping.flags = flags;
            return this;
        }
        public ResultMapping build() {
            resolveTypeHandler();
            return resultMapping;
        }
        private void resolveTypeHandler() {
            if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
                Configuration configuration = resultMapping.configuration;
                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
                resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, null);
            }
        }
    }
    public Configuration getConfiguration() {
        return configuration;
    }
    public String getProperty() {
        return property;
    }
    public String getColumn() {
        return column;
    }
    public Class<?> getJavaType() {
        return javaType;
    }
    public TypeHandler<?> getTypeHandler() {
        return typeHandler;
    }
    public List<ResultFlag> getFlags() {
        return flags;
    }
}
 
3.4.3 封装结果映射
- ResultMap 映射对象的封装主要包括了对象的构建和结果的存放。
 - 存放的地点就是 Configuration 配置项中所提供的结果映射 
Map<String, ResultMap> resultMaps。 - 这样的配置方式也是为了后续可以通过 
resultMaps Key获取到对应的 ResultMap 进行使用。 
ResultMap.java
package com.lino.mybatis.mapping;
import com.lino.mybatis.session.Configuration;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
 * @description: 结果映射
 */
public class ResultMap {
    private String id;
    private Class<?> type;
    private List<ResultMapping> resultMappings;
    private Set<String> mappedColumns;
    public ResultMap() {
    }
    public static class Builder {
        private ResultMap resultMap = new ResultMap();
        public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
            resultMap.id = id;
            resultMap.type = type;
            resultMap.resultMappings = resultMappings;
        }
        public ResultMap build() {
            resultMap.mappedColumns = new HashSet<>();
            // 添加 mappedColums 字段
            for (ResultMapping resultMapping : resultMap.resultMappings) {
                final String column = resultMapping.getColumn();
                if (column != null) {
                    resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
                }
            }
            return resultMap;
        }
    }
    public String getId() {
        return id;
    }
    public Class<?> getType() {
        return type;
    }
    public List<ResultMapping> getResultMappings() {
        return resultMappings;
    }
    public Set<String> getMappedColumns() {
        return mappedColumns;
    }
    public List<ResultMapping> getPropertyResultMappings() {
        return resultMappings;
    }
}
 
- ResultMap 中 Builder 建造者负责完成字段的处理,通过把字段统一转换为大写存放到 
mappedColumns映射字段中。并返回resultMap对象。 - 其余的信息都可以通过构造函数进行传递。
 
3.4.4 解析结果映射
ResultMapResolver.java
package com.lino.mybatis.builder;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import java.util.List;
/**
 * @description: 结果映射解析器
 */
public class ResultMapResolver {
    private final MapperBuilderAssistant assistant;
    private String id;
    private Class<?> type;
    private List<ResultMapping> resultMappings;
    public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, List<ResultMapping> resultMappings) {
        this.assistant = assistant;
        this.id = id;
        this.type = type;
        this.resultMappings = resultMappings;
    }
    public ResultMap resolve() {
        return assistant.addResultMap(this.id, this.type, this.resultMappings);
    }
}
 
- 新增 ResultMapResolver 结果映射器,它的作用就是对解析结果内容的一个封装处理。
 - 最终调用的还是 MapperBuilderAssistant 映射构建器助手,所提供 ResultMap 封装和保存操作。
 
3.5 解析映射配置
- 整个配置解析都以围绕 Mybatis 框架中使用 
resultMap映射为主。 - 而 
resultMap的参数映射配置也是用于解决数据库表中的字段与 Java 代码中的对象字段不一致的情况。 

3.5.1 构建器基类
BaseBuilder.java
package com.lino.mybatis.builder;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
/**
 * @description: 构建器的基类,建造者模式
 */
public class BaseBuilder {
    protected final Configuration configuration;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final TypeHandlerRegistry typeHandlerRegistry;
    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }
    public Configuration getConfiguration() {
        return configuration;
    }
    protected Class<?> resolveAlias(String alias) {
        return typeAliasRegistry.resolveAlias(alias);
    }
    /**
     * 根据别名解析 Class 类型别名注册/事务管理器别名
     *
     * @param alias 别名
     * @return 对象类型
     */
    protected Class<?> resolveClass(String alias) {
        if (alias == null) {
            return null;
        }
        try {
            return resolveAlias(alias);
        } catch (Exception e) {
            throw new RuntimeException("Error resolving class. Cause: " + e, e);
        }
    }
    protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, Class<? extends TypeHandler<?>> typeHandlerType) {
        if (typeHandlerType == null) {
            return null;
        }
        return typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
    }
}
 
3.5.2 映射构建器助手
MapperBuilderAssistant.java
package com.lino.mybatis.builder;
import com.lino.mybatis.mapping.*;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeHandler;
import java.util.ArrayList;
import java.util.List;
/**
 * @description: 映射构建器助手,建造者
 */
public class MapperBuilderAssistant extends BaseBuilder {
    private String currentNamespace;
    private String resource;
    public MapperBuilderAssistant(Configuration configuration, String resource) {
        super(configuration);
        this.resource = resource;
    }
    public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, List<ResultFlag> flags) {
        Class<?> javaTypeClass = resolveResultJavaType(resultType, property, null);
        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, null);
        ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
        builder.typeHandler(typeHandlerInstance);
        builder.flags(flags);
        return builder.build();
    }
    private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
        if (javaType == null && property != null) {
            try {
                MetaClass metaResultType = MetaClass.forClass(resultType);
                javaType = metaResultType.getSetterType(property);
            } catch (Exception ignore) {
            }
        }
        if (javaType == null) {
            javaType = Object.class;
        }
        return javaType;
    }
    public String getCurrentNamespace() {
        return currentNamespace;
    }
    public void setCurrentNamespace(String currentNamespace) {
        this.currentNamespace = currentNamespace;
    }
    public String applyCurrentNamespace(String base, boolean isReference) {
        if (base == null) {
            return null;
        }
        if (isReference) {
            if (base.contains(".")) {
                return base;
            }
        } else {
            if (base.startsWith(currentNamespace + ".")) {
                return base;
            }
            if (base.contains(".")) {
                throw new RuntimeException("Dots are not allowed in element names, please remove it from " + base);
            }
        }
        return currentNamespace + "." + base;
    }
    /**
     * 添加映射器语句
     */
    public MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,
                                              Class<?> parameterType, String resultMap, Class<?> resultType,
                                              LanguageDriver lang) {
        // 给id加上namespace前缀:com.lino.mybatis.test.dao.IUserDao.queryUserInfoById
        id = applyCurrentNamespace(id, false);
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);
        // 结果映射, 给 MappedStatement#resultMaps
        setStatementResultMap(resultMap, resultType, statementBuilder);
        MappedStatement statement = statementBuilder.build();
        // 映射语句信息,建造完存放到配置项中
        configuration.addMappedStatement(statement);
        return statement;
    }
    private void setStatementResultMap(String resultMap, Class<?> resultType, MappedStatement.Builder statementBuilder) {
        // 因为暂时还没有在 Mapper XML 中配置 Map 返回结果,所以这里返回的是 null
        resultMap = applyCurrentNamespace(resultMap, true);
        List<ResultMap> resultMaps = new ArrayList<>();
        if (resultMap != null) {
            String[] resultMapNames = resultMap.split(",");
            for (String resultMapName : resultMapNames) {
                resultMaps.add(configuration.getResultMap(resultMapName.trim()));
            }
        }
        /*
         * 通常使用 resultType 即可满足大部分场景
         * <select id="queryUserInfoById" resultType="com.lino.mybatis.test.po.User">
         * 使用 resultType 的情况下,Mybatis 会自动创建一个 ResultMap,基于属性名称映射列到 JavaBean 的属性上。
         */
        else if (resultType != null) {
            ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
                    configuration, statementBuilder.id() + "-Inline", resultType, new ArrayList<>());
            resultMaps.add(inlineResultMapBuilder.build());
        }
        statementBuilder.resultMaps(resultMaps);
    }
    public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {
        // 补全ID全路径,如:com.lino.mybatis.test.dao.IActivityDao + activityMap
        id = applyCurrentNamespace(id, false);
        ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings);
        ResultMap resultMap = inlineResultMapBuilder.build();
        configuration.addResultMap(resultMap);
        return resultMap;
    }
}
 
- 在 MapperBuilderAssistant 映射构建器助手中,新增加了2个方法。 
  
- 构建 Mapping 方法 
buildResultMapping。- 在最开始 Mapper XML 映射构建器解析 
buildResultMappingFromContext所调用的XMLMapperBuilder#buildResultMapping方法。 - 封装映射配置中 
<result column="activity_id" property="activityId" />的column、property字段。 
 - 在最开始 Mapper XML 映射构建器解析 
 - 添加 ResultMap 方法 
addResultMap。- 从 ResultMapResolver 结果映射器调用添加 ResultMap。
 - 最终就是把这个配置保存到 Configuration 配置项中。
 
 
 - 构建 Mapping 方法 
 
3.5.3 解析映射配置
- 基于这样对映射字段的解决方案,所以需要扩展 Mapper XML 映射构建器 
configurationElement方法的处理内容。 - 添加解析 
resultMap操作。这部分操作也就是在解析整个select、insert、update、delete部分。 
XMLMapperBuilder.java
package com.lino.mybatis.builder.xml;
import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.builder.ResultMapResolver;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.ResultFlag;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
 * @description: XML映射构建器
 */
public class XMLMapperBuilder extends BaseBuilder {
    private Element element;
    private String resource;
    /**
     * 映射器构建助手
     */
    private MapperBuilderAssistant builderAssistant;
    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource) throws DocumentException {
        this(new SAXReader().read(inputStream), configuration, resource);
    }
    public XMLMapperBuilder(Document document, Configuration configuration, String resource) {
        super(configuration);
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
        this.element = document.getRootElement();
        this.resource = resource;
    }
    /**
     * 解析
     *
     * @throws Exception 异常
     */
    public void parse() throws Exception {
        // 如果当前资源没有加载过再加载,防止重复加载
        if (!configuration.isResourceLoaded(resource)) {
            configurationElement(element);
            // 标记一下,已经加载过了
            configuration.addLoadedResource(resource);
            // 绑定映射器到namespace
            configuration.addMapper(Resources.classForName(builderAssistant.getCurrentNamespace()));
        }
    }
    /**
     * 配置mapper元素
     * <mapper namespace="org.mybatis.example.BlogMapper">
     * <select id="selectBlog" parameterType="int" resultType="Blog">
     * select * from Blog where id = #{id}
     * </select>
     * </mapper>
     *
     * @param element 元素
     */
    private void configurationElement(Element element) {
        // 1.配置namespace
        String namespace = element.attributeValue("namespace");
        if ("".equals(namespace)) {
            throw new RuntimeException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // 2.解析resultMap
        resultMapElement(element.elements("resultMap"));
        // 3.配置select|insert|update|delete
        buildStatementFromContext(element.elements("select"), element.elements("insert"), element.elements("update"), element.elements("delete"));
    }
    /**
     * 解析resultMap
     *
     * @param list 结果映射列表
     */
    private void resultMapElement(List<Element> list) {
        for (Element element : list) {
            try {
                resultMapElement(element, Collections.emptyList());
            } catch (Exception ignore) {
            }
        }
    }
    /**
     * <resultMap id="activityMap" type="cn.bugstack.mybatis.test.po.Activity">
     * <id column="id" property="id"/>
     * <result column="activity_id" property="activityId"/>
     * <result column="activity_name" property="activityName"/>
     * <result column="activity_desc" property="activityDesc"/>
     * <result column="create_time" property="createTime"/>
     * <result column="update_time" property="updateTime"/>
     * </resultMap>
     */
    private ResultMap resultMapElement(Element resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
        String id = resultMapNode.attributeValue("id");
        String type = resultMapNode.attributeValue("type");
        Class<?> typeClass = resolveClass(type);
        List<ResultMapping> resultMappings = new ArrayList<>();
        resultMappings.addAll(additionalResultMappings);
        List<Element> resultChildren = resultMapNode.elements();
        for (Element resultChild : resultChildren) {
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
                flags.add(ResultFlag.ID);
            }
            // 构建 ResultMapping
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
        // 创建结果映射解析器
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, resultMappings);
        return resultMapResolver.resolve();
    }
    /**
     * <id column="id" property="id"/>
     * <result column="activity_id" property="activityId"/>
     */
    private ResultMapping buildResultMappingFromContext(Element context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
        String property = context.attributeValue("property");
        String column = context.attributeValue("column");
        return builderAssistant.buildResultMapping(resultType, property, column, flags);
    }
    /**
     * 配置select|insert|update|delete
     *
     * @param lists 元素列表
     */
    @SafeVarargs
    private final void buildStatementFromContext(List<Element>... lists) {
        for (List<Element> list : lists) {
            for (Element element : list) {
                final XMLStatementBuilder statementBuilder = new XMLStatementBuilder(configuration, builderAssistant, element);
                statementBuilder.parseStatementNode();
            }
        }
    }
}
 
- 在 
XMLMapperBuilder#configurationElement配置元素解析的方法中,新增加了关于resultMap元素的解析。- 由于可能在一个 Mapper XML 中有多组这样的映射参数配置,所以这里获取的是一个 
elements集合元素。 
 - 由于可能在一个 Mapper XML 中有多组这样的映射参数配置,所以这里获取的是一个 
 - 解析的核心过程包括: 
  
- 读取 
resultMap标签中,如<resultMap id="activityMap" type="com.lino.mybatis.test.po.Activity">的id、type信息。 - 之后循环解析标签内的每条配置元素,如 
<result column="activity_id" property="activityId"/>中的column、property信息。 - 同时会把 
id的配置专门用 ResultFlag 枚举类进行标记。 - 基础信息解析完成后,就开始调用结果映射器把解析的信息封装成 ResultMap 进行存放。
 
 - 读取 
 
3.6 使用映射对象
- 从 DefaultSqlSession 调用方法,执行 SQL 后,就是对结果的封装。 
  
- 主要体现在 
DefaultResultSetHandler#handlerResultSets结果收集器的操作中。 
 - 主要体现在 
 
DefaultResultSetHandler.java
package com.lino.mybatis.executor.resultset;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.result.DefaultResultContext;
import com.lino.mybatis.executor.result.DefaultResultHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
 * @description: 默认Map结果处理器
 * @author: lingjian
 * @createDate: 2022/11/8 13:59
 */
public class DefaultResultSetHandler implements ResultSetHandler {
    private static final Object NO_VALUE = new Object();
    ...
    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
        // 根据返回类型,实例化对象
        Object resultObject = createResultObject(rsw, resultMap, null);
        if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
            final MetaObject metaObject = configuration.newMetaObject(resultObject);
            // 自动映射:把每列的值都赋到对应的字段上
            applyAutomaticMappings(rsw, resultMap, metaObject, null);
            // Map映射:根据映射类型赋值到字段
            applyPropertyMappings(rsw, resultMap, metaObject, null);
        }
        return resultObject;
    }
    ...
    private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
        final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        boolean foundValues = false;
        final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
        for (ResultMapping propertyMapping : propertyMappings) {
            final String column = propertyMapping.getColumn();
            if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
                // 获取值
                final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
                Object value = typeHandler.getResult(rsw.getResultSet(), column);
                // 设置值
                final String property = propertyMapping.getProperty();
                if (value != NO_VALUE && property != null && value != null) {
                    // 通过反射工具类设置属性值
                    metaObject.setValue(property, value);
                    foundValues = true;
                }
            }
        }
        return foundValues;
    }
}
 
- 从 
DefaultResultSetHandler#handlerResultSets方法开始,调用handlerResultSet方法,创建结果处理器、封装数据和保存结果。 - 那么从封装数据阶段,则包括了创建对象和封装对象属性。
 

- 在 
DefaultResultSetHandler#getRowValue方法中,原有的是通过自动映射,把每列的值赋值到对应的字段上。 - 而现在因为有了属性映射,所以需要新添加 
applyPropertyMappings方法进行处理。- 在 
applyPropertyMappings首先获取mappedColumnNames映射的字段。 - 在后续循环处理 
List<ResultMapping>时,进行比对判断是否包含当前字段。 - 如果包含则获取到对应类型的 TypeHandler 类型处理器,执行 
TypeHandler#getResult获取结果。 - 并把这个结果通过 MetaObject 反射工具类把结果设置到对象对应的属性中。
 
 - 在 
 
四、测试:ResultMap映射参数
4.1 测试环境配置
4.1.1 在mybatis数据库添加activity数据表
CREATE TABLE `activity`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `activity_id` bigint(20) NOT NULL COMMENT '活动ID',
  `activity_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '活动名称',
  `activity_desc` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '活动描述',
  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `unique_activity_id`(`activity_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '活动配置' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of activity
-- ----------------------------
INSERT INTO `activity` VALUES (1, 100001, '活动名', '测试活动', '2021-08-08 20:14:50', '2021-08-08 20:14:50');
INSERT INTO `activity` VALUES (3, 100002, '活动名', '测试活动', '2021-10-05 15:49:21', '2021-10-05 15:49:21');
 
4.1.2 提供DAO接口和实体类
Activity.java
package com.lino.mybatis.test.po;
import java.util.Date;
/**
 * @description: 活动类
 */
public class Activity {
    /**
     * 主键ID
     */
    private Long id;
    /**
     * 活动ID
     */
    private Long activityId;
    /**
     * 活动名称
     */
    private String activityName;
    /**
     * 活动描述
     */
    private String activityDesc;
    /**
     * 创建人
     */
    private String creator;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Long getActivityId() {
        return activityId;
    }
    public void setActivityId(Long activityId) {
        this.activityId = activityId;
    }
    public String getActivityName() {
        return activityName;
    }
    public void setActivityName(String activityName) {
        this.activityName = activityName;
    }
    public String getActivityDesc() {
        return activityDesc;
    }
    public void setActivityDesc(String activityDesc) {
        this.activityDesc = activityDesc;
    }
    public String getCreator() {
        return creator;
    }
    public void setCreator(String creator) {
        this.creator = creator;
    }
    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
    public Date getUpdateTime() {
        return updateTime;
    }
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}
 
IActivityDao.java
package com.lino.mybatis.test.dao;
import com.lino.mybatis.test.po.Activity;
/**
 * @description: 活动持久层
 */
public interface IActivityDao {
    /**
     * 根据活动ID查询活动
     *
     * @param activityId 活动ID
     * @return 活动对象
     */
    Activity queryActivityById(Long activityId);
}
 
4.1.3 配置数据源和配置Mapper
mybatis-config-datasource.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--XML配置-->
        <mapper resource="mapper/Activity_Mapper.xml"/>
    </mappers>
</configuration>
 
4.1.4 活动接口配置文件
Activity_Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.mybatis.test.dao.IActivityDao">
    <resultMap id="activityMap" type="com.lino.mybatis.test.po.Activity">
        <id column="id" property="id"/>
        <result column="activity_id" property="activityId"/>
        <result column="activity_name" property="activityName"/>
        <result column="activity_desc" property="activityDesc"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>
    <select id="queryActivityById" parameterType="java.lang.Long" resultMap="activityMap">
        SELECT activity_id, activity_name, activity_desc, create_time, update_time
        FROM activity
        WHERE activity_id = #{activityId}
    </select>
</mapper>
 
4.2 单元测试
ApiTest.java
package com.lino.mybatis.test;
import com.alibaba.fastjson.JSON;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.session.SqlSession;
import com.lino.mybatis.session.SqlSessionFactory;
import com.lino.mybatis.session.SqlSessionFactoryBuilder;
import com.lino.mybatis.test.dao.IActivityDao;
import com.lino.mybatis.test.po.Activity;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
 * @description: 单元测试
 */
public class ApiTest {
    private Logger logger = LoggerFactory.getLogger(ApiTest.class);
    private SqlSession sqlSession;
    @Before
    public void init() throws IOException {
        // 1.从SqlSessionFactory中获取SqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
        sqlSession = sqlSessionFactory.openSession();
    }
    @Test
    public void test_queryActivityById() {
        // 1.获取映射器对象
        IActivityDao dao = sqlSession.getMapper(IActivityDao.class);
        // 2.测试验证
        Activity result = dao.queryActivityById(100001L);
        logger.info("测试结果:{}", JSON.toJSONString(result));
    }
}
 
测试结果
16:37:14.750 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Long
16:37:14.794 [main] INFO  c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:100001
16:37:15.518 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1516500233.
16:37:15.526 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
16:37:15.553 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
 

- 从 Debug 调试截图中的字段映射匹配和值的填充,测试结果看是通过的。
 
五、总结:ResultMap映射参数
- 本章结合整个框架和 ResultMap 提前预留出来的参数解析框架,添加映射类参数的处理操作。
 - 在整个解析的过程中,一个 ResultMap 对应多个 ResultMapping 的关系,把每一条映射都处理成 ResultMapping 信息,都存放到配置项中。 
  
- 前面提到过 Configuration 伴随着整个 session 生命周期。
 
 - 所有的解析操作完成以后就是到了接触处理封装中。 
  
- 思考:怎么把 SQL 执行的结果和对象封装到一起,普通的对象默认按照对象字段即可封装,而带有下划线的属性字段,则需要根据映射的2个字段,下划线对应非下划线的方式,进行匹配处理,最终返回统一的封装对象结果。
 
 



















