IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 8.ParameterHandler -> 正文阅读

[大数据]8.ParameterHandler

1.JDBC参数处理

使用JDBC操作数据库时,以PreparedStatement为例:

PreparedStatement statement = connection.prepareStatement(sql);
statement.setInt(0, 0);
statement.setByte(1, (byte) 0);
statement.setBoolean(2, false);
statement.setBigDecimal(3, new BigDecimal(25000.98));
statement.setString(4, "员工,经理");

预编译SQL,对预编译SQL设置参数。对于不同的数据库字段类型,调用的setXXX方法也不一样。

因此mybatis把这些方法封装成不同的TypeHandler,我们可以指定javaType与jdbcType来选择调用。如:

LongTypeHandler、StringTypeHandler、BooleanTypeHandler等等。

决定TypeHandler的使用因素有:

  • mapper.xml文件中指定的参数类型parameterType属性,只有静态SQL有用。对于if、choose、when等是不使用的,或者${userId}

    <select id="getUserByList" parameterType="list" resultMap="resultMap">
    
  • mapper.xml文件的参数

    #{userId, jdbcType=BIGINT}
    
  • 查询参数

    UserPO simpleGetUser(@Param("userId") Long userId, @Param("userName") String userName);
    

2.解析xml文件中的参数设置

2.1解析parameterType

XMLStatementBuilder解析parameterType部分代码

public void parseStatementNode() {
  // ...
  // 参数类型
  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType); 
  // 解析sql文件时会用到parameterTypeClass
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);  
  // ...
}

XMLLanguageDriver解析为sql的代码

@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
  XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  return builder.parseScriptNode();
}

XMLScriptBuilder解析sql动态标签的代码

发现只有当mapperStatement语句不是动态SQL时,才会用到parameterType

public SqlSource parseScriptNode() {
  // 解析动态标签后 组成的sql节点片段
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  if (isDynamic) {
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}

2.2解析#{}

解析#{},主要分为两种

  • 静态SQL(RawSqlSource)
  • 动态SQL(DynamicSqlSource)

2.2.1RawSqlSource

public class RawSqlSource implements SqlSource {

  private final SqlSource sqlSource;

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    // 如果没有指定parameterType属性就使用Object
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    // 解析SQL中的参数,需要使用parameterType
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }

}

2.2.2DynamicSqlSource

/**
 * 动态SQL源码
 */
public class DynamicSqlSource implements SqlSource {

  private final Configuration configuration;
  private final SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    // 根据实际传入的参数来确定parameterType与静态SQL不同的地方
    // mapper接口,多个参数会被转为Map  
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }

}

2.2.3SqlSourceBuilder

SqlSourceBuilder.parse 解析xml文件中的#{},使用 ? 替换

//替换#{}中间的部分,如何替换,逻辑在ParameterMappingTokenHandler
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;
  // SQL是否缩小空格
  if (configuration.isShrinkWhitespacesInSql()) {
    sql = parser.parse(removeExtraWhitespaces(originalSql));
  } else {
    // 把SQL中#{XXX} 替换为 ? SQL预编译是使用
    sql = parser.parse(originalSql);
  }
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

2.2.4ParameterMappingTokenHandler

ParameterMappingTokenHandler参数映射解析处理器,重要部分。

对于#{}的解析及使用占位符替换的逻辑都在这里

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;
    }

    @Override
    public String handleToken(String content) {
      // content 内容为#{userId, jdbcType=BIGINT} 中的 userId, jdbcType=BIGINT
      parameterMappings.add(buildParameterMapping(content));
      // 如何替换很简单,永远是一个问号,但是参数的信息要记录在parameterMappings里面供后续使用
      return "?";
    }

    // 构建参数映射
    // #{userName,jdbcType=VARCHAR,javaType=string,typeHandler=org.apache.ibatis.type.StringTypeHandler} 为例
    private ParameterMapping buildParameterMapping(String content) {
      // 先解析参数映射,就是转化成一个hashmap
      Map<String, String> propertiesMap = parseParameterMapping(content);
      String property = propertiesMap.get("property");
      Class<?> propertyType;
      // 这里分支比较多,需要逐个理解
      if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
        propertyType = metaParameters.getGetterType(property);
      } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
        propertyType = parameterType;
      } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
        propertyType = java.sql.ResultSet.class;
      } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
        propertyType = Object.class;
      } else {
        // 为javaBean对象,通过getter方法获取属性类型
        MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        if (metaClass.hasGetter(property)) {
          propertyType = metaClass.getGetterType(property);
        } else {
          propertyType = Object.class;
        }
      }
      ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
      Class<?> javaType = propertyType;
      String typeHandlerAlias = null;
      for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
          // #{}指定了javaType则以此为准,而不是以parameterType或者ParameterObject字段的属性类型为准
          javaType = resolveClass(value);
          builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
          builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
          builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {
          builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {
          builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {
          // #{}指定了typeHandler
          typeHandlerAlias = value;
        } else if ("jdbcTypeName".equals(name)) {
          builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {
          // Do Nothing
        } else if ("expression".equals(name)) {
          throw new BuilderException("Expression based parameters are not supported yet");
        } else {
          throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + PARAMETER_PROPERTIES);
        }
      }
      if (typeHandlerAlias != null) {
        // #{}指定了typeHandler,根据TypeHandler获取,如果为空则又以javaType获取
        builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
      }
      // 最终构建ParameterMapping,如果TypeHandler还为空,则以javaType与jdbcType再去获取TypeHandler
      return builder.build();
    }
    
    // builder.build()时会用到,如果javaType为Object时,为UnknownTypeHandler
    private void resolveTypeHandler() {
      // 没有指定typeHandler,则根据javaType,jdbcType 去typeHandlerRegistry 里面获取
      if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {
        Configuration configuration = parameterMapping.configuration;
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);
      }
    }

    private Map<String, String> parseParameterMapping(String content) {
      try {
        return new ParameterExpression(content);
      } catch (BuilderException ex) {
        throw ex;
      } catch (Exception ex) {
        throw new BuilderException("Parsing error was found in mapping #{" + content + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
      }
    }
  }

2.3参数解析总结

解析构造parameterMappings,在setParameter时使用

  • 如果是静态SQL,会使用xml配置的属性parameterType。如果没有指定则会被设置为Object.class

  • 如果是动态SQL,会使用ParameterObject参数来获取字段类型

  • properType的顺序为:

    • parameterType有相应的hasTypeHandler(Class<?> javaType, JdbcType jdbcType)
    • 如果没有通过反射获取ParameterObject的字段类型
    • #{}中指定了javaType则以此为准,而不是以parameterType或者ParameterObject字段的属性类型为准
  • 解析#{}中指定了jdbcType

  • 解析#{}中指定的TypeHandler

  • 如果#{}中指定的TypeHandler没有在注册其中找到,则根据javaType,jdbcType(可能为空)去typeHandlerRegistry 里面获取。

  • javaType为Object,jdbcType为空时则为UnknownTypeHandler

image-20210909225406097

3.ParamNameResolver

参数装换即将JAVA 方法中的普通参数,封装转换成Map,以便map中的key和sql的参数引用相对应。

UserPO simpleGetUser(@Param("userId") Long userId, @Param("userName") String userName);
<select id="simpleGetUser" resultMap="resultMap">
  select
  <include refid="base_column"/>
  from tb_user
    where user_id = #{userId, jdbcType=BIGINT}
  <if test="userName != null and userName != ''">
    and USER_NAME = #{userName, jdbcType=VARCHAR}
  </if>
</select>
  • 单个参数的情况下且没有设置@param注解会直接转换,勿略SQL中的引用名称。
  • 多个参数情况:优先采用@Param中设置的名称,如果没有则用参数序号代替 即"param1、parm2…"
  • 如果javac编译时设置了 -parameters 编译参数,也可以直接获取源码中的变量名称作为key

以上所有转换逻辑均在ParamNameResolver中实现

3.1ParamNameResolver

根据Mapper接口初始化接口方法中的@Param 参数。

  • 设置@Param,则key为注解值
  • 没有设置@Param,开启useActualParamName,则为(“arg0” , “arg1” , … “argn”)
  • 没有设置@Param,没有开启useActualParamName,则为(“0”, “1”, …“n”)
public ParamNameResolver(Configuration config, Method method) {
  this.useActualParamName = config.isUseActualParamName();
  final Class<?>[] paramTypes = method.getParameterTypes();
  // paramAnnotations(涉及方法参数使用注解[][] 第一个表示参数的个数,第二个表示每个参数列表里对应的注解)
  final Annotation[][] paramAnnotations = method.getParameterAnnotations();
  final SortedMap<Integer, String> map = new TreeMap<>();
  int paramCount = paramAnnotations.length;
  // get names from @Param annotations
  for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
    // 去除特殊的参数(RowBounds、ResultHandler)
    if (isSpecialParameter(paramTypes[paramIndex])) {
      // skip special parameters
      continue;
    }
    String name = null;
    // 获取@Param注解参数的名字及参数位置
    for (Annotation annotation : paramAnnotations[paramIndex]) {
      if (annotation instanceof Param) {
        hasParamAnnotation = true;
        name = ((Param) annotation).value();
        break;
      }
    }
    if (name == null) {
      // @Param was not specified.
      // useActualParamName为true,返回arg0-argn
      if (useActualParamName) {
        name = getActualParamName(method, paramIndex);
      }
      if (name == null) {
        // use the parameter index as the name ("0", "1", ...)
        // gcode issue #71
        name = String.valueOf(map.size());
      }
    }
    map.put(paramIndex, name);
  }
  names = Collections.unmodifiableSortedMap(map);
}

3.2getNamedParams

把参数名称与参数值封装进Map,在设置参数时使用。

  • 只有一个参数,没有使用@Param注解
    • collection或者array数组转为map,key为[collection,list,array]
    • 非集合,直接使用参数本身
  • 多个参数,转为Map
    • 使用@Param注解,key为注解值
    • 没有使用@Param注解,key为(“arg0” , “arg1” , … “argn”)或者(“0”, “1”, …“n”),与ParamNameResolver初始化有关
    • 同时会有key为param1,param2,param3…
public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  if (args == null || paramCount == 0) {
    return null;
  } else if (!hasParamAnnotation && paramCount == 1) {
    // 没有使用@Param注解,且只有一个参数。
    Object value = args[names.firstKey()];
    // 把collection或者array数组转为map.key为[collection,list,array],不然直接返回参数本身
    return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
  } else {
    // 多个参数,转为map 此时的@Param注解 起到作用,key为注解的值。同时把param+参数序号 也存入map。value为具体的参数值
    // 单个参数使用@Param注解
    final Map<String, Object> param = new ParamMap<>();
    int i = 0;
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
      param.put(entry.getValue(), args[entry.getKey()]);
      // add generic param names (param1, param2, ...)
      final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
      // ensure not to overwrite parameter named with @Param
      if (!names.containsValue(genericParamName)) {
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
  }
}

4.DefaultParameterHandler

image-20210907234631512

构建DefaultParameterHandler需要MappedStatement、Object(参数)、BoundSql

在构建PreparedStatementHandler等jdbc处理器时,会在BaseStatementHandler构造器中创建ParameterHandler

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  // ...
  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  // ...
}

参数映射

映射是指Map中的key如何与SQL中绑定的参数相对应。以下这几种情况

  • 单个原始类型:直接映射,勿略SQL中引用名称
  • Map类型:基于Map key映射
  • Object:基于属性名称映射,支持嵌套对象属性访问

在Object类型中,支持通过“.”方式映射属中的属性。如:user.age

参数赋值

通过TypeHandler 为PrepareStatement设置值,通常情况下一般的数据类型MyBatis都有与之相对应的TypeHandler

public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  // SQL解析的参数映射关系  
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings != null) {
    // 循环设参数,sql中的占位符参数
    for (int i = 0; i < parameterMappings.size(); i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        // 若有额外的参数, 设为额外的参数
        if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          // 若参数为null,直接设null
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          // 若参数有相应的TypeHandler,直接设object
          // 适用于一个参数,不使用@Param注解
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        try {
          // 根据参数占位符的位置设置参数,在BaseTypeHandler中  
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } catch (TypeException | SQLException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        }
      }
    }
  }
}

ParameterMapping的TypeHandler为UnknownTypeHandler后,会根据parameter的参数类型与jdbcType再去查找具体的TypeHandler,之后进行设置参数

// BaseTypeHandler中的方法
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
  if (parameter == null) {
    if (jdbcType == null) {
      throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
    }
    try {
      // value值为null  
      ps.setNull(i, jdbcType.TYPE_CODE);
    } catch (SQLException e) {
      throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
            + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
            + "Cause: " + e, e);
    }
  } else {
    try {
      // 非null,抽象方法在具体的子类中 
      setNonNullParameter(ps, i, parameter, jdbcType);
    } catch (Exception e) {
      throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
            + "Try setting a different JdbcType for this parameter or a different configuration property. "
            + "Cause: " + e, e);
    }
  }
}
// UnknownTypeHandler中的方法
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
    throws SQLException {
  // 根据parameter的参数类型与jdbcType再去查找具体的TypeHandler  
  TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
  handler.setParameter(ps, i, parameter, jdbcType);
}

private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
    TypeHandler<?> handler;
    if (parameter == null) {
        handler = OBJECT_TYPE_HANDLER;
    } else {
        handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
        // check if handler is null (issue #270)
        if (handler == null || handler instanceof UnknownTypeHandler) {
            handler = OBJECT_TYPE_HANDLER;
        }
    }
    return handler;
}


setParameter(ps, i, parameter, jdbcType);
}

private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<?> handler;
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}


  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-09-22 14:44:36  更:2021-09-22 14:46:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:13:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码