手写Mybatis:第14章-解析和使用ResultMap映射参数配置
文章目录
- 一、目标: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> {@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 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 herereturn (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.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);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 + 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 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);// 绑定映射器到namespaceconfiguration.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.配置namespaceString namespace = element.attributeValue("namespace");if ("".equals(namespace)) {throw new RuntimeException("Mapper's 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(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);}// 构建 ResultMappingresultMappings.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 元素列表*/@SafeVarargsprivate 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_timeFROM activityWHERE 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;@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 - 构建参数映射 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个字段,下划线对应非下划线的方式,进行匹配处理,最终返回统一的封装对象结果。
相关文章:

手写Mybatis:第14章-解析和使用ResultMap映射参数配置
文章目录 一、目标:ResultMap映射参数二、设计:ResultMap映射参数三、实现:ResultMap映射参数3.1 工程结构3.2 ResultMap映射参数类图3.3 添加类型处理器3.3.1 日期类型处理器3.3.2 类型处理器注册机 3.4 存放映射对象3.4.1 结果标志3.4.2 结…...

GE VME-7807RC-410001350-93007807-410001 K数字输入模块
通道数目: VME-7807RC-410001350-93007807-410001K 数字输入模块通常具有多个数字输入通道,可以同时监测多个数字信号。 输入类型: 这种模块通常用于监测数字信号,例如开关状态(ON/OFF)或计数器脉冲。 采…...

C++插入加密,替代加密
void 插入加密() {//缘由https://bbs.csdn.net/topics/396047473int n 1, j 0;char aa[60]{}, aaa[] "abcde";cin >> aa;while (j < 60 && (aa[j] - \0))cout << aa[j] << aaa[j % 5]; } void 插入加密() {//缘由https://bbs.csdn.n…...

Web前端开发概述
Web(World Wide Web,全球广域网)是指一种基于互联网的信息系统,通过超文本链接将全球各地的文档、图像、视频等资源相互关联起来,并通过Web浏览器进行交互浏览和访问。Web的发展使得人们可以方便地获取和共享各种类型的…...

Web自动化 —— Selenium元素定位与防踩坑
1. 基本元素定位一 from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By # selenium Service("../../chromedriver.exe") # driver webdriver.Chrome(serviceService) # driver.…...

【数据结构】树和二叉树的概念及结构(一)
目录 一,树的概念及结构 1,树的定义 2,树结点的分类及关系 3,树的表示 二,二叉树的概念及结构 1,二叉树的定义 2,特殊的二叉树 3,二叉树的性质 4,二叉树的存储结构 1&…...

第三章 USB应用笔记之USB鼠标(以STM32 hal库为例)
第三章 USB应用笔记之USB鼠标(以STM32 hal库为例) 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 第三章 USB应用笔记之USB鼠标(以STM32 hal库为例)前言一、STM32 U…...

微服务01-基本介绍+注册中心EureKa
基本介绍 服务集群:一个请求由多个服务完成,服务接口暴露,以便于相互调用; 注册中心:每个服务的状态,需要进行维护,我们可以在注册中心进行监控维护服务; 配置中心:这些…...
【ES6】JavaScript中的异步编程:async和await
在JavaScript中,异步编程是一种处理长时间运行的操作的方法,这些操作包括读取文件、网络请求或处理大数据等。在传统的回调函数中,代码按照顺序执行,一旦遇到长时间运行的操作,就需要回调函数来处理结果。这使得代码变…...

51单片机热水器温度控制系统仿真设计( proteus仿真+程序+原理图+报告+讲解视频)
51单片机热水器温度控制系统仿真设计 1.主要功能:2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单 &&下载链接 51单片机热水器温度控制系统仿真设计( proteus仿真程序原理图报告讲解视频) 仿真图proteus7.8及以上 程序编译器&#x…...
Spring Boot 配置文件加密
方式一:Spring Cloud Config 一、建立config server 1. build.gradle 文件中添加: plugins {id javaid org.springframework.boot version 2.7.0id io.spring.dependency-management version 1.0.11.RELEASE }ext {set(springCloudVersion, "202…...

【树形权限】树形列表权限互斥选择、el-tree设置禁用等等
需求:按照权限管理配置的数据权限树展开;点击查看按钮后进入其他指定机构选择弹窗为一树形结构 本文章对项目中出现得关键点进行总结。 一、实现如上树形列表 在 element 官方表格示例中,实现树形表格列表数据渲染,非常简单。只…...

ubuntu 22.04安装cuda、cudnn、conda、pytorch
1、cuda 视频连接 https://www.bilibili.com/video/BV1bW4y197Mo/?spm_id_from333.999.0.0&vd_source3b42b36e44d271f58e90f86679d77db7cuda 11.8 https://developer.nvidia.com/cuda-toolkit-archive点击进入 https://developer.nvidia.com/cuda-11-8-0-download-arc…...

2023 最新前端面试题 (HTML 篇)
1. src 和 href 的区别 src 用于替换当前元素(引入),href 用于在当前文档和引用资源之间确立联系(引用) (1)src(source) 指向外部资源的位置,指向的内容将会嵌…...

华为云银河麒麟V10安装libmcrypt
本次安装是在华为云上执行。cpu是鲲鹏,操作系统是银河麒麟V10. 先下载安装包: wget http://downloads.sourceforge.net/mcrypt/libmcrypt-2.5.8.tar.gz 解包,进入目录中。 执行如下命令: ./configure make make install 执…...

智慧导览|智能导游系统|AR景区导览系统|景区电子导览
随着文旅市场的加快复苏,以及元宇宙、VR、AR、虚拟数字人等新兴技术的快速发展,文旅行业也正在加快数字化转型的步伐,向智慧景区建设迈进。为满足不同年龄段游客的游览需要,提升旅游服务体验,越来越多的旅游景区、博物…...
【Docker】Docker基本使用介绍
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。 一、安装Docker 首先,你需要从官方网站上下载Docker的安装包,并按…...

Linux命令200例:man用于显示和阅读关于Linux内置命令的使用说明
🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌。CSDN专家博主,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师࿰…...

idea 无法识别maven的解决
问题描述 从git拉取代码或者修改文件夹以后,整个项目所有依赖爆红无法通过修改或者重新加载maven解决版本为idea 2021 问题定位 maven的版本太高,而idea的般本太低,导致识别的时候稳定性差 解决 使用idea原生的maven版本 选择已捆绑的m…...
String底层函数的实现方式
一、常见的String封装函数 1. strcpy函数的实现 char *strcpy(char *dest, const char *src) {char *tmp dest;while ((*dest *src) ! \0)/* nothing */;return tmp; } 2. strncpy函数的实现 char *strncpy(char *dest, const char *src, size_t count) {char *tmp dest…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...