网页模板网站模板,做网站还是做游戏,什么广告推广最有效果,微信网站设计运营文章目录 一、目标#xff1a;ResultMap映射参数二、设计#xff1a;ResultMap映射参数三、实现#xff1a;ResultMap映射参数3.1 工程结构3.2 ResultMap映射参数类图3.3 添加类型处理器3.3.1 日期类型处理器3.3.2 类型处理器注册机 3.4 存放映射对象3.4.1 结果标志3.4.2 结… 文章目录 一、目标ResultMap映射参数二、设计ResultMap映射参数三、实现ResultMap映射参数3.1 工程结构3.2 ResultMap映射参数类图3.3 添加类型处理器3.3.1 日期类型处理器3.3.2 类型处理器注册机 3.4 存放映射对象3.4.1 结果标志3.4.2 结果映射Map3.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 配置数据源和配置Mapper4.1.4 活动接口配置文件 4.2 单元测试 五、总结ResultMap映射参数 一、目标ResultMap映射参数 如何将数据库表中的下划线的字段名称映射成Java代码中的驼峰字段? 通常在数据库表字段的命名中所定义的规范是希望使用小写的英文字母和下划线的方式组合使用。 例如雇员表中的雇员姓名则使用 employee_name 表字段描述。 但这样的字段定义与 Java 代码开发中的 PO 数据库对象中的字段是不能一一匹配的。因为 Java 代码中会使用驼峰的方式进行命名。 同样是雇员姓名在 Java 代码中则是 employeeName 的方式进行表示。 在使用 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 配置项的 MapString, 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.xml3.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 BaseTypeHandlerDate {Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {ps.setTimestamp(i, new Timestamp((parameter).getTime()));}Overrideprotected 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 MapJdbcType, TypeHandler? JDBC_TYPE_HANDLER_MAP new EnumMap(JdbcType.class);private final MapType, MapJdbcType, TypeHandler? TYPE_HANDLER_MAP new HashMap(16);private final MapClass?, 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) {MapJdbcType, 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 TypeHandlerT getTypeHandler(Type type, JdbcType jdbcType) {MapJdbcType, 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 herereturn (TypeHandlerT) 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 ListResultFlag 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(ListResultFlag 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 ListResultFlag getFlags() {return flags;}
}3.4.3 封装结果映射
ResultMap 映射对象的封装主要包括了对象的构建和结果的存放。存放的地点就是 Configuration 配置项中所提供的结果映射 MapString, 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 ListResultMapping resultMappings;private SetString mappedColumns;public ResultMap() {}public static class Builder {private ResultMap resultMap new ResultMap();public Builder(Configuration configuration, String id, Class? type, ListResultMapping 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 ListResultMapping getResultMappings() {return resultMappings;}public SetString getMappedColumns() {return mappedColumns;}public ListResultMapping 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 ListResultMapping resultMappings;public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class? type, ListResultMapping 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, ListResultFlag 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.queryUserInfoByIdid applyCurrentNamespace(id, false);MappedStatement.Builder statementBuilder new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);// 结果映射 给 MappedStatement#resultMapssetStatementResultMap(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 返回结果所以这里返回的是 nullresultMap applyCurrentNamespace(resultMap, true);ListResultMap resultMaps new ArrayList();if (resultMap ! null) {String[] resultMapNames resultMap.split(,);for (String resultMapName : resultMapNames) {resultMaps.add(configuration.getResultMap(resultMapName.trim()));}}/** 通常使用 resultType 即可满足大部分场景* select idqueryUserInfoById resultTypecom.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, ListResultMapping resultMappings) {// 补全ID全路径如com.lino.mybatis.test.dao.IActivityDao activityMapid 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 columnactivity_id propertyactivityId / 的 column、property 字段。 添加 ResultMap 方法 addResultMap。 从 ResultMapResolver 结果映射器调用添加 ResultMap。最终就是把这个配置保存到 Configuration 配置项中。
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);// 绑定映射器到namespaceconfiguration.addMapper(Resources.classForName(builderAssistant.getCurrentNamespace()));}}/*** 配置mapper元素* mapper namespaceorg.mybatis.example.BlogMapper* select idselectBlog parameterTypeint resultTypeBlog* select * from Blog where id #{id}* /select* /mapper** param element 元素*/private void configurationElement(Element element) {// 1.配置namespaceString namespace element.attributeValue(namespace);if (.equals(namespace)) {throw new RuntimeException(Mappers namespace cannot be empty);}builderAssistant.setCurrentNamespace(namespace);// 2.解析resultMapresultMapElement(element.elements(resultMap));// 3.配置select|insert|update|deletebuildStatementFromContext(element.elements(select), element.elements(insert), element.elements(update), element.elements(delete));}/*** 解析resultMap** param list 结果映射列表*/private void resultMapElement(ListElement list) {for (Element element : list) {try {resultMapElement(element, Collections.emptyList());} catch (Exception ignore) {}}}/*** resultMap idactivityMap typecn.bugstack.mybatis.test.po.Activity* id columnid propertyid/* result columnactivity_id propertyactivityId/* result columnactivity_name propertyactivityName/* result columnactivity_desc propertyactivityDesc/* result columncreate_time propertycreateTime/* result columnupdate_time propertyupdateTime/* /resultMap*/private ResultMap resultMapElement(Element resultMapNode, ListResultMapping additionalResultMappings) throws Exception {String id resultMapNode.attributeValue(id);String type resultMapNode.attributeValue(type);Class? typeClass resolveClass(type);ListResultMapping resultMappings new ArrayList();resultMappings.addAll(additionalResultMappings);ListElement resultChildren resultMapNode.elements();for (Element resultChild : resultChildren) {ListResultFlag flags new ArrayList();if (id.equals(resultChild.getName())) {flags.add(ResultFlag.ID);}// 构建 ResultMappingresultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));}// 创建结果映射解析器ResultMapResolver resultMapResolver new ResultMapResolver(builderAssistant, id, typeClass, resultMappings);return resultMapResolver.resolve();}/*** id columnid propertyid/* result columnactivity_id propertyactivityId/*/private ResultMapping buildResultMappingFromContext(Element context, Class? resultType, ListResultFlag 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 元素列表*/SafeVarargsprivate final void buildStatementFromContext(ListElement... lists) {for (ListElement list : lists) {for (Element element : list) {final XMLStatementBuilder statementBuilder new XMLStatementBuilder(configuration, builderAssistant, element);statementBuilder.parseStatementNode();}}}
}在 XMLMapperBuilder#configurationElement 配置元素解析的方法中新增加了关于 resultMap 元素的解析。 由于可能在一个 Mapper XML 中有多组这样的映射参数配置所以这里获取的是一个 elements 集合元素。 解析的核心过程包括 读取 resultMap 标签中如 resultMap idactivityMap typecom.lino.mybatis.test.po.Activity 的 id、type 信息。之后循环解析标签内的每条配置元素如 result columnactivity_id propertyactivityId/ 中的 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 ListString mappedColumnNames rsw.getMappedColumnNames(resultMap, columnPrefix);boolean foundValues false;final ListResultMapping 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 映射的字段。在后续循环处理 ListResultMapping 时进行比对判断是否包含当前字段。如果包含则获取到对应类型的 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 version1.0 encodingUTF-8?
!DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//ENhttp://mybatis.org/dtd/mybatis-3-config.dtd
configurationenvironments defaultdevelopmentenvironment iddevelopmenttransactionManager typeJDBC/dataSource typePOOLEDproperty namedriver valuecom.mysql.jdbc.Driver/property nameurlvaluejdbc:mysql://127.0.0.1:3306/mybatis?useUnicodetrueamp;characterEncodingutf8/property nameusername valueroot/property namepassword value123456//dataSource/environment/environmentsmappers!--XML配置--mapper resourcemapper/Activity_Mapper.xml//mappers
/configuration4.1.4 活动接口配置文件 Activity_Mapper.xml ?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.lino.mybatis.test.dao.IActivityDaoresultMap idactivityMap typecom.lino.mybatis.test.po.Activityid columnid propertyid/result columnactivity_id propertyactivityId/result columnactivity_name propertyactivityName/result columnactivity_desc propertyactivityDesc/result columncreate_time propertycreateTime/result columnupdate_time propertyupdateTime//resultMapselect idqueryActivityById parameterTypejava.lang.Long resultMapactivityMapSELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activityWHERE activity_id #{activityId}/select
/mapper4.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;Beforepublic void init() throws IOException {// 1.从SqlSessionFactory中获取SqlSessionSqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(mybatis-config-datasource.xml));sqlSession sqlSessionFactory.openSession();}Testpublic 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 - 构建参数映射 propertyactivityId propertyTypeclass java.lang.Long
16:37:14.794 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statementcom.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter100001
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设置对应的参数信息 value100001
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个字段下划线对应非下划线的方式进行匹配处理最终返回统一的封装对象结果。