手写Mybatis:第10章-使用策略模式,调用参数处理器

文章目录

  • 一、目标:参数处理器
  • 二、设计:参数处理器
  • 三、实现:参数处理器
    • 3.1 工程结构
    • 3.2 参数处理器关系图
    • 3.3 入参数校准
    • 3.4 参数策略处理器
      • 3.4.1 JDBC枚举类型修改
      • 3.4.2 类型处理器接口
      • 3.4.3 模板模式:类型处理器抽象基类
      • 3.4.4 类型处理器具体的子类实现
      • 3.4.5 类型处理器注册机
    • 3.5 参数构建
      • 3.5.1 参数映射类的修改
      • 3.5.2 SQL源码构建器
    • 3.6 参数使用
      • 3.6.1 参数处理器接口
      • 3.6.2 默认参数处理器
    • 3.7 参数处理器的使用
      • 3.7.1 脚本语言驱动
      • 3.7.2 XML语言驱动器
      • 3.7.3 映射器语句类修改,添加脚本语言驱动
      • 3.7.4 修改配置类
      • 3.7.5 语句处理器抽象基类
      • 3.7.6 预处理语句处理器
      • 3.7.7 默认sqlSession实现类添加日志描述
  • 四、测试:参数处理器
    • 4.1 修改 DAO 接口
    • 4.2 修改 User 实体类
    • 4.3 配置Mapper文件
    • 4.4 单元测试
      • 4.4.1 提取初始化SqlSession
      • 4.4.2 基本类型参数
      • 4.4.3 对象类型参数
  • 五、总结:参数处理器

一、目标:参数处理器

💡 结合参数的提取,对执行的 SQL 进行参数的自动化设置,而不是像我们之前那样把参数写成固定的。

请添加图片描述

  • 在流程上,通过 DefaultSqlSession#selectOne 方法调用执行器,并通过预处理语句处理器 PreparedStatementHandler 执行参数设置和结果查询。
  • 这个流程中我们处理的参数信息,也就是每个 SQL 执行时,那些 ?号 需要被替换的地方,目前是通过硬编码的方式进行处理的。
  • 使用策略模式,处理硬编码为自动化类型设置。

二、设计:参数处理器

💡 在 SQL 拆解出参数类型之后,怎么根据不同的参数进行不同的类型设置

  • 在自动化解析 XMLSQL 拆分出所有的参数类型后,则应该根据不同的参数进行不同的类型设置
    • Long 调用 ps.setLongString 调用 ps.setString
  • 这里使用 策略模式,封装进去类型处理器(也就是实现 TypeHandler 接口的过程)。

在这里插入图片描述

  • 其实关于参数的处理,因为有很多的类型(Long\String\Object\...),所以这里最重要的体现则是 策略模式 的使用。
    • 包括:构建参数时根据类型,选择对应的策略类型处理器,填充到参数映射集合中。
  • 另一方面:参数的使用,也就是在执行 DefaultSqlSession#selectOne 的链路中。
    • 包括:参数的设置,按照参数不同类型,获取出对应的处理器,以及入参。
    • 注意:由于入参值可能是一个对象中的属性,所以我们使用反射工具类 MetaObject 进行值的获取,避免由于动态的对象,没法硬编码获取属性值。

三、实现:参数处理器

3.1 工程结构

mybatis-step-09
|-src|-main|	|-java|		|-com.lino.mybatis|			|-binding|			|	|-MapperMethod.java|			|	|-MapperProxy.java|			|	|-MapperProxyFactory.java|			|	|-MapperRegistry.java|			|-builder|			|	|-xml|			|	|	|-XMLConfigBuilder.java|			|	|	|-XMLMapperBuilder.java|			|	|	|-XMLStatementBuilder.java|			|	|-BaseBuilder.java|			|	|-ParameterExpression.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|			|	|-resultset|			|	|	|-DefaultResultSetHandler.java|			|	|	|-ResultSetHandler.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|			|	|-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|			|	|-ResultHandler.java|			|	|-SqlSession.java|			|	|-SqlSessionFactory.java|			|	|-SqlSessionFactoryBuilder.java|			|	|-TransactionIsolationLevel.java|			|-transaction|			|	|-jdbc|			|	|	|-JdbcTransaction.java|			|	|	|-JdbcTransactionFactory.java|			|	|-Transaction.java|			|	|-TransactionFactory.java|			|-type|			|	|-BaseTypeHandler.java|			|	|-IntegerTypeHandler.java|			|	|-JdbcType.java|			|	|-LongTypeHandler.java|			|	|-StringTypeHandler.java|			|	|-TypeAliasRegistry.java|			|	|-TypeHandler.java|			|	|-TypeHandlerRegistry.java|-test|-java|	|-com.lino.mybatis.test|	|-dao|	|	|-IUserDao.java|	|-po|	|	|-User.java|	|-ApiTest.java|-resources|-mapper|	|-User_Mapper.xml|-mybatis-config-datasource.xml

3.2 参数处理器关系图

在这里插入图片描述

  • 策略模式,核心处理主要分为三块:类型处理、参数设置、参数使用。
    • 定义 TypeHandler 类型处理器策略接口,实现不同的处理策略,包括:Long、String、Integer 等。
    • 类型策略处理器实现完成后,需要注册到处理器注册机中,后续其他模块参数的设置的使用都是从 Configuration 中获取到 TypeHandlerRegistry 进行使用。
    • 有了策略处理器之后,在进行操作解析 SQL 时,就可以按照不同的类型把对应的策略处理器设置到 BoundSql#parameterMappings 参数里。

3.3 入参数校准

💡 针对参数的传递?

在这里插入图片描述

  • 这里的参数传递后,需要获取第0个参数,而且是硬编码固定。
    • 这是为什么呢?这个第0个参数是哪来的,我们接口调用的方法,参数不是一个吗?就像 User queryUserInfoById(Long id).

在这里插入图片描述

  • 其实这个参数来自映射器代理类 MapperProxy#invoke 中,因为 invoke 反射调用的方法,入参中是 Object[] args,所以这个参数被传递到后续的参数设置中。而我们的 DAO 测试类是一个已知的固定参数,所以后面硬编码获取了第0个参数。
    • JDK 反射调用方法操作固定方法入参。
  • 现在我们需要根据方法的信息,给方法做签名操作,以便于转换入参信息为方法的信息。比如:数组转换为对应的对象。

MapperMethod.java

package com.lino.mybatis.binding;import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import com.mysql.fabric.xmlrpc.base.Param;
import javassist.bytecode.SignatureAttribute;
import java.lang.reflect.Method;
import java.util.*;/*** @description: 映射器方法*/
public class MapperMethod {private final SqlCommand command;private final MethodSignature method;public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {this.command = new SqlCommand(configuration, mapperInterface, method);this.method = new MethodSignature(configuration, method);}public Object execute(SqlSession sqlSession, Object[] args) {Object result = null;switch (command.getType()) {case INSERT:break;case DELETE:break;case UPDATE:break;case SELECT:Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);break;default:throw new RuntimeException("Unknown execution method for: " + command.getName());}return result;}/*** SQL 指令*/public static class SqlCommand {private final String name;private final SqlCommandType type;public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {String statementName = mapperInterface.getName() + "." + method.getName();MappedStatement ms = configuration.getMappedStatement(statementName);this.name = ms.getId();this.type = ms.getSqlCommandType();}public String getName() {return name;}public SqlCommandType getType() {return type;}}/*** 方法签名*/public static class MethodSignature {private final SortedMap<Integer, String> params;public MethodSignature(Configuration configuration, Method method) {this.params = Collections.unmodifiableSortedMap(getParams(method));}public Object convertArgsToSqlCommandParam(Object[] args) {final int paramCount = params.size();if (args == null || paramCount == 0) {// 如果没参数return null;} else if (paramCount == 1) {return args[params.keySet().iterator().next().intValue()];} else {// 否则,返回一个ParamMap, 修改参数名,参数名就是其位置final Map<String, Object> param = new ParamMap<>();int i = 0;for (Map.Entry<Integer, String> entry : params.entrySet()) {// 1.先加一个#{0},#{1},#{2}...参数param.put(entry.getValue(), args[entry.getKey().intValue()]);// issue #71, add param names as param1, param2...but ensure backward compatibilityfinal String genericParamName = "param" + (i + 1);if (!param.containsKey(genericParamName)) {/*2.再加一个#{param1},#{param2}...参数你可以传递多个参数给一个映射器方法。默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等如果你想改变参数的名称(只在多参数情况下),那么你可以在参数上使用@Param("paramName")注解*/param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}}private SortedMap<Integer, String> getParams(Method method) {// 用一个TreeMap,这样就保证还是按参数的先后顺序final SortedMap<Integer, String> params = new TreeMap<>();final Class<?>[] argTypes = method.getParameterTypes();for (int i = 0; i < argTypes.length; i++) {String paramName = String.valueOf(params.size());// 不做 Param 的实现,这部分不处理。params.put(i, paramName);}return params;}/*** 参数map,静态内部类,更严格的get方法,如果没有相应的key,报错*/public static class ParamMap<V> extends HashMap<String, V> {private static final long serialVersionUID = -2212268410512043556L;@Overridepublic V get(Object key) {if (!super.containsKey(key)) {throw new RuntimeException("Parameter '" + key + "' not found. Available parameters are " + keySet());}return super.get(key);}}}
}
  • 在映射器方法中 MapperMethod#execute 将原来的直接将参数 args 传递给 SqlSession#selectOne 方法,调整为转换后再传递对象。
  • 这里的转换操作就是来自于 Method#getParameterTypes 对参数的获取和处理,于 args 进行对比。
    • 如果是单个参数,则直接返回参数 Tree 树结构下的对应节点值。
    • 非单个类型,则需要进行循环处理,这样转换后的参数才能被直接使用。

3.4 参数策略处理器

  • Mybatis 的源码包中,type 包下所提供的就是一套参数的处理策略集合。
  • 它通过定义类型处理器接口、由抽象模板实现并定义标准流程,定义提取抽象方法交予给子类实现,这些之类就是各个类型处理器的具体实现。

3.4.1 JDBC枚举类型修改

JdbcType.java

package com.lino.mybatis.type;import java.sql.Types;
import java.util.HashMap;
import java.util.Map;/*** @description: JDBC枚举类型* @author: lingjian* @createDate: 2022/11/5 17:10*/
public enum JdbcType {// JDBC枚举类型INTEGER(Types.INTEGER),FLOAT(Types.FLOAT),DOUBLE(Types.DOUBLE),DECIMAL(Types.DECIMAL),VARCHAR(Types.VARCHAR),CHAR(Types.CHAR),TIMESTAMP(Types.TIMESTAMP);public final int TYPE_CODE;private static Map<Integer, JdbcType> codeLookup = new HashMap<>();static {for (JdbcType type : JdbcType.values()) {codeLookup.put(type.TYPE_CODE, type);}}JdbcType(int code) {this.TYPE_CODE = code;}public static JdbcType forCode(int code) {return codeLookup.get(code);}
}
  • 添加 CHAR(Typcs.CHAR) 字符类型

3.4.2 类型处理器接口

TypeHandler.java

package com.lino.mybatis.type;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: 类型处理器*/
public interface TypeHandler<T> {/*** 设置参数** @param ps        预处理语言* @param i         次数* @param parameter 参数对象* @param jdbcType  JDBC类型* @throws SQLException SQL异常*/void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
  • 首先定义一个类型处理器的接口。
  • 这里设置参数也是一样,所有不同类型的参数,都可以被提取出来这些标准的参数字段和异常,后续的子类按照这个标准实现即可。

3.4.3 模板模式:类型处理器抽象基类

BaseTypeHandler.java

package com.lino.mybatis.type;import com.lino.mybatis.session.Configuration;
import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: 类型处理器的基类*/
public abstract class BaseTypeHandler<T> implements TypeHandler<T> {protected Configuration configuration;public void setConfiguration(Configuration configuration) {this.configuration = configuration;}@Overridepublic void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {// 定义抽象方法,由子类实现不同类型的属性设置setNonNullParameter(ps, i, parameter, jdbcType);}/*** 属性设置:抽象方法,由子类实现** @param ps        预处理语言* @param i         次数* @param parameter 参数对象* @param jdbcType  JDBC类型* @throws SQLException SQL异常*/protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
  • 通过抽象基类的流程模板定义,便于一些参数的判断和处理。

3.4.4 类型处理器具体的子类实现

IntegerTypeHandler.java

package com.lino.mybatis.type;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: Integer类型处理器*/
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException {ps.setInt(i, parameter);}
}

LongTypeHandler.java

package com.lino.mybatis.type;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: Long类型处理器*/
public class LongTypeHandler extends BaseTypeHandler<Long> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {ps.setLong(i, parameter);}
}

StringTypeHandler.java

package com.lino.mybatis.type;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: String类型处理器*/
public class StringTypeHandler extends BaseTypeHandler<String> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, parameter);}
}
  • 这里的接口实现暂时实现了3个,分别是 IntegerTypeHandler、LongTypeHandler、StringTypeHandler
  • Mybatis 中源码还有30+个其他类型,可以参照源码阅读。

3.4.5 类型处理器注册机

TypeHandlerRegistry.java

package com.lino.mybatis.type;import java.lang.reflect.Type;
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());}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;}
}
  • 这里在构造函数中,新增加了 LongTypeHandler、IntegerTypeHandler、StringTypeHandler 三种类型的注册机。
  • 同时注意到,无论是对象类型,还是基本类型,都是一个类型处理器。只不过在注册的时候多注册了一个。
    • 这种操作方式和我们平常的业务开发中,也是一样的。一种是多注册,另外一种是判断处理

3.5 参数构建

  • 需要对 SqlSourceBuilder 源码构建器中,创建参数映射 ParameterMapping 需要添加参数处理器的内容。
    • 因为只有这样才能方便的从参数映射中获取到对应类型的处理器进行使用。
  • 需要完善 ParameterMappping 添加 TypeHandler 属性信息。
    • 以及在 ParameterMappingTokenHandler#buildParameterMapping 处理参数映射时,构建出参数的映射。

3.5.1 参数映射类的修改

ParameterMapping.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;/*** @description: 参数映射 #{property,javaType=int,jdbcType=NUMERIC}*/
public class ParameterMapping {private Configuration configuration;/*** property*/private String property;/*** javaType = int*/private Class<?> javaType = Object.class;/*** javaType = NUMERIC*/private JdbcType jdbcType;/*** 类型处理器*/private TypeHandler<?> typeHandler;private ParameterMapping() {}public static class Builder {private ParameterMapping parameterMapping = new ParameterMapping();public Builder(Configuration configuration, String property, Class<?> javaType) {parameterMapping.configuration = configuration;parameterMapping.property = property;parameterMapping.javaType = javaType;}public Builder javaType(Class<?> javaType) {parameterMapping.javaType = javaType;return this;}public Builder jdbcType(JdbcType jdbcType) {parameterMapping.jdbcType = jdbcType;return this;}public ParameterMapping build() {if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {Configuration configuration = parameterMapping.configuration;TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);}return parameterMapping;}}public Configuration getConfiguration() {return configuration;}public String getProperty() {return property;}public Class<?> getJavaType() {return javaType;}public JdbcType getJdbcType() {return jdbcType;}public TypeHandler<?> getTypeHandler() {return typeHandler;}
}
  • build 添加类型处理器的处理判断

3.5.2 SQL源码构建器

SqlSourceBuilder.java

package com.lino.mybatis.builder;import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.parsing.GenericTokenParser;
import com.lino.mybatis.parsing.TokenHandler;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** @description: SQL源码构建器*/
public class SqlSourceBuilder extends BaseBuilder {private static Logger logger = LoggerFactory.getLogger(SqlSourceBuilder.class);private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";public SqlSourceBuilder(Configuration configuration) {super(configuration);}public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);String sql = parser.parse(originalSql);// 返回静态SQLreturn new StaticSqlSource(configuration, sql, handler.getParameterMappings());}private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {private List<ParameterMapping> parameterMappings = new ArrayList<>();private Class<?> parameterType;private MetaObject metaParameters;public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {super(configuration);this.parameterType = parameterType;this.metaParameters = configuration.newMetaObject(additionalParameters);}public List<ParameterMapping> getParameterMappings() {return parameterMappings;}@Overridepublic String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));return "?";}/*** 构建参数映射** @param content 参数* @return 参数映射*/private ParameterMapping buildParameterMapping(String content) {// 先解析参数映射,就是转化成一个 HashMap | #{favouriteSection,jdbcType=VARCHAR}Map<String, String> propertiesMap = new ParameterExpression(content);String property = propertiesMap.get("property");Class<?> propertyType;if (typeHandlerRegistry.hasTypeHandler(parameterType)) {propertyType = parameterType;} else if (property != null) {MetaClass metaClass = MetaClass.forClass(parameterType);if (metaClass.hasGetter(property)) {propertyType = metaClass.getGetterType(property);} else {propertyType = Object.class;}} else {propertyType = Object.class;}logger.info("构建参数映射 property:{} propertyType:{}", property, propertyType);ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);return builder.build();}}
}
  • 这部分就是对参数的细化处理,构建出参数的映射关系。
    • 首先是 if 判断对应的参数类型是否在 TypeHandlerRegistry 注册器中。
    • 如果不在则拆解对象,按属性进行获取 propertyType 的操作。
  • 这一块也用到了 MetaClass 反射工具类的使用。

3.6 参数使用

  • 参数构建完成后,就可以在 DefaultSqlSession#selectOne 调用时设置参数使用了。
    • 链路:Executor#query -> SimpleExecutor#doQuery -> StatementHandler#parameterize -> PreparedStatementHandler#parameterize -> ParameterHandler#setParameters
    • ParameterHandler#setParameters 就可以看到了根据参数的不同处理器循环设置参数。

3.6.1 参数处理器接口

ParameterHandler.java

package com.lino.mybatis.executor.parameter;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: 参数处理器*/
public interface ParameterHandler {/*** 获取参数** @return 参数*/Object getParameterObject();/*** 设置参数** @param ps 预处理语句对象* @throws SQLException sql异常*/void setParameters(PreparedStatement ps) throws SQLException;
}

3.6.2 默认参数处理器

DefaultParameterHandler.java

package com.lino.mybatis.scripting.defaults;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.reflection.MetaObject;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;/*** @description: 默认参数处理器*/
public class DefaultParameterHandler implements ParameterHandler {private Logger logger = LoggerFactory.getLogger(DefaultParameterHandler.class);private final TypeHandlerRegistry typeHandlerRegistry;private final MappedStatement mappedStatement;private final Object parameterObject;private BoundSql boundSql;private Configuration configuration;public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {this.mappedStatement = mappedStatement;this.configuration = mappedStatement.getConfiguration();this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();this.parameterObject = parameterObject;this.boundSql = boundSql;}@Overridepublic Object getParameterObject() {return parameterObject;}@Overridepublic void setParameters(PreparedStatement ps) throws SQLException {List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (null != parameterMappings) {for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);String propertyName = parameterMapping.getProperty();Object value;if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {// 通过 MetaObject.getValue 反射取得值设置进去MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}JdbcType jdbcType = parameterMapping.getJdbcType();// 设置参数logger.info("根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:{}", JSON.toJSONString(value));TypeHandler typeHandler = parameterMapping.getTypeHandler();typeHandler.setParameter(ps, i + 1, value, jdbcType);}}}
}
  • 每一个循环的参数设置,都是从 BoundSql 中获取 ParameterMapping 集合进行循环操作,而这个集合参数就是我们前面 ParameterMappingTokenHandler#buildParameterMapping 构建参数映射时处理的。
  • 设置参数时根据参数的 parameterObject 入参信息,判断是否基本类型,如果不是则从对象中进行拆解获取(也就是一个对象A中包括对象B),处理完成后就可以准确拿到对应的入参值。
    • 入参值在 MapperMethod 中已经处理了一遍 方法签名
  • 基本信息获取完成后,则根据参数类型获取到对应的 TypeHandler 类型处理器,也就是找到 IntegerTypeHandler、LongTypeHandler、StringTypeHandler 等,确定找到以后,则可以进行对应的参数设置。
    • typeHandler.setParameter(ps, i + 1, value, jdbcType) 通过这样的方式把我们之前硬编码的操作进行解耦。

3.7 参数处理器的使用

3.7.1 脚本语言驱动

LanguageDriver.java

package com.lino.mybatis.scripting;import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;/*** @description: 脚本语言驱动*/
public interface LanguageDriver {/*** 创建SQL源** @param configuration 配置项* @param script        元素* @param parameterType 参数类型* @return SqlSource SQL源码*/SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType);/*** 创建参数处理器** @param mappedStatement 映射器语句类* @param parameterObject 参数对象* @param boundSql        sql语句* @return ParameterHandler 参数处理器*/ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
}
  • 添加创建参数处理器接口

3.7.2 XML语言驱动器

XMLLanguageDriver.java

package com.lino.mybatis.scripting.xmltags;import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.defaults.DefaultParameterHandler;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;/*** @description: XML 语言驱动器*/
public class XMLLanguageDriver implements LanguageDriver {@Overridepublic SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType) {// 用XML脚本构建器解析XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();}@Overridepublic ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);}
}

3.7.3 映射器语句类修改,添加脚本语言驱动

MappedStatement.java

package com.lino.mybatis.mapping;import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;/*** @description: 映射器语句类*/
public class MappedStatement {private Configuration configuration;private String id;private SqlCommandType sqlCommandType;private SqlSource sqlSource;Class<?> resultType;private LanguageDriver lang;public MappedStatement() {}public static class Builder {private MappedStatement mappedStatement = new MappedStatement();public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class<?> resultType) {mappedStatement.configuration = configuration;mappedStatement.id = id;mappedStatement.sqlCommandType = sqlCommandType;mappedStatement.sqlSource = sqlSource;mappedStatement.resultType = resultType;mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();}public MappedStatement build() {assert mappedStatement.configuration != null;assert mappedStatement.id != null;return mappedStatement;}}public Configuration getConfiguration() {return configuration;}public String getId() {return id;}public SqlCommandType getSqlCommandType() {return sqlCommandType;}public SqlSource getSqlSource() {return sqlSource;}public Class<?> getResultType() {return resultType;}public LanguageDriver getLang() {return lang;}
}

3.7.4 修改配置类

Configuration.java

package com.lino.mybatis.session;import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** @description: 配置项*/
public class Configuration {/*** 环境*/protected Environment environment;/*** 映射注册机*/protected MapperRegistry mapperRegistry = new MapperRegistry(this);/*** 映射的语句,存在Map里*/protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);/*** 类型别名注册机*/protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();/*** 脚本语言注册器*/protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();/*** 类型处理器注册机*/protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();/*** 对象工厂*/protected ObjectFactory objectFactory = new DefaultObjectFactory();/*** 对象包装工厂*/protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();/*** 准备资源列表*/protected final Set<String> loadedResources = new HashSet<>();/*** 数据库ID*/protected String databaseId;public Configuration() {typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);}public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);}public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}public boolean hasMapper(Class<?> type) {return mapperRegistry.hasMapper(type);}public void addMappedStatement(MappedStatement ms) {mappedStatements.put(ms.getId(), ms);}public MappedStatement getMappedStatement(String id) {return mappedStatements.get(id);}public TypeAliasRegistry getTypeAliasRegistry() {return typeAliasRegistry;}public Environment getEnvironment() {return environment;}public void setEnvironment(Environment environment) {this.environment = environment;}public String getDatabaseId() {return databaseId;}/*** 生产执行器** @param transaction 事务* @return 执行器*/public Executor newExecutor(Transaction transaction) {return new SimpleExecutor(this, transaction);}/*** 创建语句处理器** @param executor        执行器* @param mappedStatement 映射器语句类* @param parameter       参数* @param resultHandler   结果处理器* @param boundSql        SQL语句* @return StatementHandler 语句处理器*/public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, ResultHandler resultHandler, BoundSql boundSql) {return new PreparedStatementHandler(executor, mappedStatement, parameter, resultHandler, boundSql);}/*** 创建结果集处理器** @param executor        执行器* @param mappedStatement 映射器语句类* @param boundSql        SQL语句* @return ResultSetHandler 结果集处理器*/public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, BoundSql boundSql) {return new DefaultResultSetHandler(executor, mappedStatement, boundSql);}/*** 创建元对象** @param object 原对象* @return 元对象*/public MetaObject newMetaObject(Object object) {return MetaObject.forObject(object, objectFactory, objectWrapperFactory);}/*** 创建类型处理器注册机** @return TypeHandlerRegistry 类型处理器注册机*/public TypeHandlerRegistry getTypeHandlerRegistry() {return typeHandlerRegistry;}/*** 是否包含资源** @param resource 资源* @return 是否*/public boolean isResourceLoaded(String resource) {return loadedResources.contains(resource);}/*** 添加资源** @param resource 资源*/public void addLoadedResource(String resource) {loadedResources.add(resource);}/*** 获取脚本语言注册机** @return languageRegistry 脚本语言注册机*/public LanguageDriverRegistry getLanguageRegistry() {return languageRegistry;}/*** 获取参数处理器** @param mappedStatement 映射器语言类型* @param parameterObject 参数对象* @param boundSql        SQL语句* @return ParameterHandler参数处理器*/public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {// 创建参数处理器ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);return parameterHandler;}/*** 获取默认脚本语言驱动** @return 脚本语言驱动*/public LanguageDriver getDefaultScriptingLanguageInstance() {return languageRegistry.getDefaultDriver();}
}
  • 添加获取默认脚本语言驱动 getDefaultScriptingLanguageInstance
  • 添加获取参数处理器 newParameterHandler

3.7.5 语句处理器抽象基类

BaseStatementHandler.java

package com.lino.mybatis.executor.statement;import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;/*** @description: 语句处理器抽象基类*/
public abstract class BaseStatementHandler implements StatementHandler {protected final Configuration configuration;protected final Executor executor;protected final MappedStatement mappedStatement;protected final Object parameterObject;protected final ResultSetHandler resultSetHandler;protected final ParameterHandler parameterHandler;protected BoundSql boundSql;public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultHandler, BoundSql boundSql) {this.configuration = mappedStatement.getConfiguration();this.executor = executor;this.mappedStatement = mappedStatement;this.parameterObject = parameterObject;this.boundSql = boundSql;this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, boundSql);}@Overridepublic Statement prepare(Connection connection) {Statement statement = null;try {// 实例化 Statementstatement = instantiateStatement(connection);// 参数设置,可以被抽取,提供配置statement.setQueryTimeout(350);statement.setFetchSize(10000);return statement;} catch (Exception e) {throw new RuntimeException("Error prepare statement. Cause: " + e, e);}}protected abstract Statement instantiateStatement(Connection connection) throws SQLException;}
  • 添加 ParameterHandler 参数处理器

3.7.6 预处理语句处理器

PreparedStatementHandler.java

package com.lino.mybatis.executor.statement;import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** @description: 预处理语句处理器(PREPARED)*/
public class PreparedStatementHandler extends BaseStatementHandler {public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultSetHandler, BoundSql boundSql) {super(executor, mappedStatement, parameterObject, resultSetHandler, boundSql);}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();return connection.prepareStatement(sql);}@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.handleResultSets(ps);}
}

3.7.7 默认sqlSession实现类添加日志描述

DefaultSqlSession.java

package com.lino.mybatis.session.defaults;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;/*** @description: 默认sqlSession实现类*/
public class DefaultSqlSession implements SqlSession {private Logger logger = LoggerFactory.getLogger(DefaultSqlSession.class);...@Overridepublic <T> T selectOne(String statement, Object parameter) {logger.info("执行查询 statement:{} parameter:{}", statement, JSON.toJSONString(parameter));MappedStatement ms = configuration.getMappedStatement(statement);List<T> list = executor.query(ms, parameter, Executor.NO_RESULT_HANDLER, ms.getSqlSource().getBoundSql(parameter));return list.get(0);}...
}

四、测试:参数处理器

4.1 修改 DAO 接口

IUserDao.java

package com.lino.mybatis.test.dao;import com.lino.mybatis.test.po.User;/*** @Description: 用户持久层*/
public interface IUserDao {/*** 根据ID查询用户信息** @param uId ID* @return User 用户*/User queryUserInfoById(Long uId);/*** 根据用户对象查询用户信息* @param user 用户* @return User 用户*/User queryUserInfo(User user);
}

4.2 修改 User 实体类

User.java

package com.lino.mybatis.test.po;import java.util.Date;/*** @description: 用户实例类*/
public class User {private Long id;/*** 用户ID*/private String userId;/*** 头像*/private String userHead;/*** 用户名称*/private String userName;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;public User() {}public User(Long id, String userId) {this.id = id;this.userId = userId;}// getter/setter
}

4.3 配置Mapper文件

User_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.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.lino.mybatis.test.po.User">SELECT id, userId, userName, userHeadFROM userWHERE id = #{id}</select><select id="queryUserInfo" parameterType="com.lino.mybatis.test.po.User" resultType="com.lino.mybatis.test.po.User">SELECT id, userId, userName, userHeadFROM userwhere id = #{id} and userId = #{userId}</select>
</mapper>

4.4 单元测试

4.4.1 提取初始化SqlSession

ApiTest.java

private SqlSession sqlSession;@Before
public void init() throws IOException {// 1.从SqlSessionFactory中获取SqlSessionSqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));sqlSession = sqlSessionFactory.openSession();
}
  • 因为接下来需要验证两种不同入参的单元测试,所以提取 从SqlSessionFactory中获取SqlSession

4.4.2 基本类型参数

ApiTest.java

@Test
public void test_queryUserInfoId() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证: 基本参数User user = userDao.queryUserInfoById(1L);logger.info("测试结果:{}", JSON.toJSONString(user));
}

测试结果

10:06:48.775 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:id propertyType:class java.lang.Long
10:06:48.934 [main] INFO  c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IUserDao.queryUserInfoById parameter:1
10:06:49.805 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1043208434.
10:06:49.815 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1
10:06:49.838 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}

在这里插入图片描述

  • 从测试过程中可以在 DefaultParameterHandler#setParameters 中打断点,验证方法参数以及获得到的类型处理器。
  • 这里测试可以通过,可以满足基本类型对象的入参信息。

4.4.3 对象类型参数

ApiTest.java

@Test
public void test_queryUserInfo() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证: 对象参数User user = userDao.queryUserInfo(new User(1L, "10001"));logger.info("测试结果:{}", JSON.toJSONString(user));
}

测试结果

10:10:05.878 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:id propertyType:class java.lang.Long
10:10:05.878 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:userId propertyType:class java.lang.String
10:10:05.947 [main] INFO  c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IUserDao.queryUserInfo parameter:{"id":1,"userId":"10001"}
10:10:06.574 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1107024580.
10:10:06.590 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1
10:10:06.590 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"10001"
10:10:06.590 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}

在这里插入图片描述

  • 从测试结果验证对象参数 User 中包含两个属性时,检查我们的代码处理过程,验证是否可以正确获取到两个类型处理器,分别设置参数的过程。
  • 从测试结果看,可以看到测试通过,并打印了相关参数的构建和使用。

五、总结:参数处理器

  • 已经把一个 ORM 框架的基本流程串联起来了,包含的分包结构:构建、绑定、映射、反射、执行、类型、事务、数据源等。
  • 参数类型的策略化设计,通过策略解耦,模板定义流程,让我们整个参数设置变得更加清晰。
  • 还有一些细节的功能点:MapperMethod 中添加方法签名、类型处理器创建和使用时,都使用了 MetaObject 反射器工具类进行处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/121256.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity Android 之 在Unity 中引入 OkHttp的操作注意(OKHttp4.xx- kotlin 的包)简单记录

Unity Android 之 在Unity 中引入 OkHttp的操作注意(OKHttp4.xx- kotlin 的包)简单记录 目录 Unity Android 之 在Unity 中引入 OkHttp的操作注意(OKHttp4.xx- kotlin 的包)简单记录 一、简单介绍 二、OKHttp 4.xx 的 SDK 封装 aar 给 Unity 的使用注意 三、附录 OKHttp 的…

德庄借助纷享销客CRM系统实现高效管理

德庄集团创于1999年&#xff0c;是一家集餐饮产业、食品产业、科技研发及文化研究为一体的现代化民营企业&#xff0c;下属9家子公司、2大现代化食品加工基地、1所研究所、1所培训学校、1个技术中心。拥有德庄、青一色、滟设、香漫谷、饭空等8大子品牌&#xff0c;呈现出良好的…

IDEA新建SpringBoot项目时启动编译报错:Error:java: 无效的源发行版: 17

文章目录 原因检查解决步骤修改jdk修改SpringBoot版本 原因 出现这种错误的原因可能是&#xff1a; 本机默认使用&#xff08;编译&#xff09;的jdk与该项目所使用的jdk版本不同。 jdk版本不适用于这个Idea&#xff0c;很典型的一个例子就是使用的Idea是2020的&#xff0c;而…

【前端】CSS-Flex弹性盒模型布局

目录 一、前言二、Flex布局是什么1、任何一个容器都可以指定为Flex布局2、行内元素也可以使用Flex布局3、Webkit内核的浏览器&#xff0c;必须加上-webkit前缀 三、基本概念四、flex常用的两种属性1、容器属性2、项目属性 五、容器属性1、flex-direction①、定义②、语句1&…

模糊测试面面观 | 模糊测试是如何发现异常情况的?

协议模糊测试是一种用于评估通信协议、文件格式和API实现系统安全性和稳定性的关键技术。在模糊测试过程中&#xff0c;监视器扮演着关键角色&#xff0c;它们能够捕获异常情况、错误响应、资源利用等&#xff0c;为测试人员提供有价值的信息&#xff0c;有助于发现潜在漏洞和问…

汽车3D HMI图形引擎选型指南【2023】

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 2002年&#xff0c;电影《少数派报告》让观众深入了解未来。 除了情节的核心道德困境之外&#xff0c;大多数人都对它的技术着迷。 我们看到了自动驾驶汽车、个性化广告和用户可以无缝交互的 3D 计算机界面。 令人惊讶的是…

数字人创作+SadTalker+GTX1080

https://github.com/OpenTalker/SadTalker 开源项目 SadTalker模型是一个使用图片与音频文件自动合成人物说话动画的开源模型&#xff0c;我们自己给模型一张图片以及一段音频文件&#xff0c;模型会根据音频文件把传递的图片进行人脸的相应动作&#xff0c;比如张嘴&#xf…

02-系统篇-内存分布

一.进程中的内存分布 进程是应用程序运行的单位&#xff0c;进程在内存中运行时&#xff0c;各个部分分布的情况如图。 1.栈内存(statck) 特点:后进先出 系统一般会对栈的大小进行限制&#xff0c;可以用命令进行查看&#xff0c;如下&#xff0c;700是进程号&#xff0c;栈被…

Python学习 -- Math模块和Random模块

math 模块提供了许多数学函数&#xff0c;用于执行各种数学运算。以下是一些常用的 math 函数以及相应的示例代码&#xff1a; math.sqrt(x): 计算平方根。 import math x 25 square_root math.sqrt(x) print(f"√{x} {square_root}") math.pow(x, y): 计算 x …

机器学习(吴恩达第一课)

课程链接 文章目录 第一周1、机器学习定义2、监督学习(Supervised learning)1、回归(Regression)2、分类(Classification) 3、无监督学习(Unsupervised learning)4、线性回归模型5、代价函数6、梯度下降(Gradient descent)1、学习率2、用于线性回归的梯度下降 第二周(多维特征…

W5500-EVB-PICO进行MQTT连接订阅发布教程(十二)

前言 上一章我们用开发板通过SNTP协议获取网络协议&#xff0c;本章我们介绍一下开发板通过配置MQTT连接到服务器上&#xff0c;并且订阅和发布消息。 什么是MQTT&#xff1f; MQTT是一种轻量级的消息传输协议&#xff0c;旨在物联网&#xff08;IoT&#xff09;应用中实现设备…

MATLAB中mod函数转化为C语言

背景 有项目算法使用matlab中mod函数进行运算&#xff0c;这里需要将转化为C语言&#xff0c;从而模拟算法运行&#xff0c;将算法移植到qt。 MATLAB中mod简单介绍 语法 b mod(a,m) 说明 b mod(a,m) 返回 a 除以 m 后的余数&#xff0c;其中 a 是被除数&#xff0c;m 是…

数字孪生与GIS:智慧城市的未来之路

数字孪生和地理信息系统&#xff08;GIS&#xff09;是两个在现代科技中崭露头角的概念&#xff0c;它们的融合为智慧城市项目带来了革命性的机会。本文将解释数字孪生为何需要融合GIS&#xff0c;并以智慧城市项目为例进行说明。 数字孪生是一种虚拟模型&#xff0c;它精确地…

行军遇到各种复杂地形怎么处理?

行军遇到各种复杂地形怎么处理&#xff1f; 【安志强趣讲《孙子兵法》第30讲】 【原文】 凡军好高而恶下&#xff0c;贵阳而贱阴&#xff0c;养生而处实&#xff0c;军无百疾&#xff0c;是谓必胜。 【注释】 阳&#xff0c;太阳能照到的地方。阴&#xff0c;太阳照不到的地方。…

【Python】从入门到上头— IO编程(8)

文章目录 一.IO编程是什么二.文件读写1.读取文件2.file-like Object二进制文件字符编码 3.写文件file对象的常用函数常见标识符 三.StringIO和BytesIO1.StringIO2.BytesIO 四.操作文件和目录五.序列化和反序列化1.pickle.dumps()2.pickle.loads()3.JSON 一.IO编程是什么 IO在计…

vue3 搭配ElementPlus做基础表单校验 自定义表单校验

<script setup> import { ref, reactive } from vue// 表单元素 const dom ref(null) // 校验规则 const rules {name: [{ required: true, message: 请输入活动名称, trigger: blur }],//校验手机号格式phone: [{ required: true, message: "请输入电话", t…

React 中的 ref 如何操作 dom节点,使输入框获取焦点

聚焦文字输入框 .focus() 获取焦点 当用户点击按钮时&#xff0c;handleClick 函数会被调用&#xff0c;从而将焦点聚焦到文本输入框上。 // 焦文字输入框 import { useRef } from "react";const FocusForm () > {const inputRef useRef<any>(null);func…

ITIL重大事件管理综合指南

什么是ITIL中的重大事件 重大事件是一个高影响力的紧急问题&#xff0c;通常影响整个组织或其主要部分。重大事件几乎总是导致组织的服务变得不可用&#xff0c;从而导致该组织的业务受到打击&#xff0c;并最终影响其财务状况。重大事件可以通过两种方式影响组织的服务&#…

Leetcode Top 100 Liked Questions(序号198~234)

​198. House Robber ​ 题意&#xff1a;你是一个强盗&#xff0c;你要去抢劫&#xff0c;每个房子都有特定金额的钱&#xff0c;但是不能拿相邻的房子的钱 我的思路 有点像动态规划&#xff0c;但是首先把每个结点空一格的后缀和得到&#xff0c;2n 之后从(in-1;i>0;i…

macos 不支持svn安装

macos 10.13可能不支持svn命令,所以要安装 xcode-select --install 弹窗在线安装失败的话只能手动下载安装 打开:Sign In - Apple 搜索Command Line Tools (macOS 10.13) 下载9.4.1版本直接安装后即可