Mybatis 参数处理器注入配置解析流程

参数处理器的作用

Mybatis作为一个ORM框架, 其最原始的本质就是JDBC,对于JDBC的使用步骤中有2步和参数处理器有关, 就是给预处理器PreparedStatement 设置参数以及通过结果集获取字段值。 这两个步骤在Mybatis中已经成为框架底层逻辑流程, 给用户留下扩展点的就是参数处理器。

参数处理器的顶级接口

/*T 为类型处理泛型*/
public interface TypeHandler<T> {

  /**
   * 
   * @param ps 预表达式
   * @param i  参数位置
   * @param parameter  实参对象
   * @param jdbcType 对于的JDBC类型
   * @throws SQLException
   */
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
   * @return the result
   * @throws SQLException
   *           the SQL exception
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

如代码所示, 对JDBC的使用有所了解, 为啥定义这几个方法还是比较容易知道了, 最后一个getResult 是和存储过程相关。

抽象类TypeReference

我们知道泛型类型如何获取, 是需要反射相关的代码来解析的, 所以Mybatis为了得到该参数处理器的泛型参数类型就搞了一个抽象类TypeReference, 用来解析实现类的泛型类型。

public abstract class TypeReference<T> {

  /*泛型类型的原始类型*/
  private final Type rawType;

  protected TypeReference() {
    /*构造方法解析泛型类型  注意这里的getClass 实际调用的是实现类的getClass, 方法具有多态性质*/
    rawType = getSuperclassTypeParameter(getClass());
  }

  Type getSuperclassTypeParameter(Class<?> clazz) {
    /*获取泛型父类*/
    Type genericSuperclass = clazz.getGenericSuperclass();
    /*如果父类是class对象*/
    if (genericSuperclass instanceof Class) {
      // try to climb up the hierarchy until meet something useful
      //如果泛型父类和TypeReference 相等继续往上爬
      if (TypeReference.class != genericSuperclass) {
        return getSuperclassTypeParameter(clazz.getSuperclass());
      }
      /*到这里说明父类不是泛型类型,缺少泛型类型参数,无法解析*/
      throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. "
        + "Remove the extension or add a type parameter to it.");
    }
    /*获取泛型类型的实际类型*/
    Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
    // TODO remove this when Reflector is fixed to return Types
    if (rawType instanceof ParameterizedType) {
      rawType = ((ParameterizedType) rawType).getRawType();
    }

    return rawType;
  }

  public final Type getRawType() { /*获取泛型类型*/
    return rawType;
  }

  @Override
  public String toString() {
    return rawType.toString();
  }

}

抽象实现类BaseTypeHandler

BaseTypeHandler 继承了TypeReference 实现了TypeHandler, 不仅是一个参数处理器,还能解析参数处理器的泛型类型。
BaseTypeHandler默认构造方法会掉用父类的无参构造方法,这也就调用了TypeReference的构造方法能够进行泛型类型参数解析。 作为一个抽象类,,其本身也实现父类的方法,并提供更加具体一点的抽象方法共给具体类去实现。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  /**
   * @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This field will remove future.
   */
  @Deprecated
  protected Configuration configuration;

  /**
   * Sets the configuration.
   *
   * @param c
   *          the new configuration
   * @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This property will remove future.
   */
  @Deprecated
  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }

  @Override
  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 {
        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 {
        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);
      }
    }
  }

  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    try {
      return getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
  }

  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(rs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);
    }
  }

  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(cs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + e, e);
    }
  }

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the nullable result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
   * @return the nullable result
   * @throws SQLException
   *           the SQL exception
   */
  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

是不是所有的参数处理器都要实现BaseTypeHandler

嗯, 最好是这样, 这样我们就可以很方便的获取泛型类实际类型,以及一些通用功能的实现, 但是参数处理器的接口本质还是TypeHandler, 其他类只是一些装饰而已。所以也可以直接继承TypeHandler。

参数处理器的配置(自定义参数处理器)

使用参数处理器我们需要指定这个参数处理器处理的Java类型,以及改Java类型对应JDBC类型。也就是配置参数处理器。对于配置我们一般都会使用XML或者注解指定。所以Mybatis也提供这样的功能。

实现自己的参数处理器:

/**
 * 用户参数处理器
 * @author puhaiguo
 * @date 2022-12-13 04:58
 * @version 1.0
 */
@MappedTypes({User.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class UserTypeHandler implements TypeHandler<User> {
  @Override
  public void setParameter(PreparedStatement ps, int i, User parameter, JdbcType jdbcType) throws SQLException {
    int n = 0;
  }

  @Override
  public User getResult(ResultSet rs, String columnName) throws SQLException {
    return null;
  }

  @Override
  public User getResult(ResultSet rs, int columnIndex) throws SQLException {
    return null;
  }

  @Override
  public User getResult(CallableStatement cs, int columnIndex) throws SQLException {
    return null;
  }
}

然后我们需要把这个类配置到Mybatis配置文件

因为我使用了注解标记了UserTypeHandler 对应的Java类型和JDBC类型, 所以我可以这么配置

<typeHandlers>
    <typeHandler handler="com.learn.mybatis.typehandler.UserTypeHandler"/>
  </typeHandlers>

如果没使用注解指明对应关系
需要这么配置:

<typeHandlers>
    <typeHandler handler="com.learn.mybatis.typehandler.UserTypeHandler" jdbcType="User" javaType="varchar"/>
  </typeHandlers>

参数处理器的解析过程

typeHandlerElement(root.evalNode("typeHandlers"));  //设置类型处理器
private void typeHandlerElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) { /*按照包的配置*/
          String typeHandlerPackage = child.getStringAttribute("name"); //获取包名
          typeHandlerRegistry.register(typeHandlerPackage);/*按照包名进行注册*/
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);/*别名对应的类型*/
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);/*别名对应的类型*/
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);/*别名对应的类型*/
          if (javaTypeClass != null) {/*如果java类型为空*/
            if (jdbcType == null) {/*如果jdbc类型为空*/
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

如上代码逻辑可以看出 支持直接配置类, 也可以指定包路径进行扫描。

因为我的配置类用的是注解,所以代码逻辑走typeHandlerRegistry.register(typeHandlerClass)

public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); //获取注解
    if (mappedTypes != null) { //如果注解不为空
      for (Class<?> javaTypeClass : mappedTypes.value()) { //获取注解的value值, 所有的java类型
        register(javaTypeClass, typeHandlerClass); //进行注册
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      register(getInstance(null, typeHandlerClass));
    }
  }

上面的代码也很好理解就是一些解析注解进行注册的过程。其中因为注解配置的JavaType 和 JDBCType都是数组类型,所以是支持多映射关系。

public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
  }

代码这里需要进行实例化参数处理器

public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {//java类型 -> 类型处理器
    if (javaTypeClass != null) {/*如果java类型不为空*/
      try {
        Constructor<?> c = typeHandlerClass.getConstructor(Class.class);//进行尝试找到当前java类型的构造函数
        return (TypeHandler<T>) c.newInstance(javaTypeClass); //实例化对象
      } catch (NoSuchMethodException ignored) {
        // ignored   如果找不到那也不跑出异常
      } catch (Exception e) {
        throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
      }
    }
    try {
      Constructor<?> c = typeHandlerClass.getConstructor();//无参实例化对象构造方法
      return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
      throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
    }
  }

//Class对象也是继承Type的
  private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); //获取类型处理类上的jdbc参数注解
    if (mappedJdbcTypes != null) { //typeHandler 类上存在注解
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {//如果jdbc 集合类型不为空
        register(javaType, handledJdbcType, typeHandler);
      }
      /*判断注解是否配置了支持空映射*/
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else { //说明没有注解Jdbc
      register(javaType, null, typeHandler);
    }
  }

private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType); //是否已经注册过
      if (map == null || map == NULL_TYPE_HANDLER_MAP) { //如果不存在
        map = new HashMap<>();  //创建
      }
      map.put(jdbcType, handler); //jdbctype 和类型处理器
      typeHandlerMap.put(javaType, map); //存入映射
    }
    allTypeHandlersMap.put(handler.getClass(), handler); //所有的映射器
  }

总结

整体流程差不多就是这样了, 并不多难, 主要还是java注解和反射技巧的使用。

走后在说一下, 我们在使用Mybatis的时候是不是很少会去定义类型处理器, 那么Mybatis为什么还是工作, 应为Mybatis一些基本的java类型与jdbc类型的类型处理器已经实现了且自动加载了。

TypeHandlerRegistry

public final class TypeHandlerRegistry {

  private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class); /*类型处理器与Jdbc类型的对应关系*/
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();/*java类型与 jdbcTypeHandlerMap的对应关系*/
  private final TypeHandler<Object> unknownTypeHandler;
  private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();/*所有的类型处理器*/

  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();/*空类型处理器*/

  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;/*空的类型处理器Class对象*/
  }

可以看见这个类是类型处理器的注册工厂。
其构造方法里面就注入了基本的类型处理器。

public TypeHandlerRegistry(Configuration configuration) {
    this.unknownTypeHandler = new UnknownTypeHandler(configuration);

    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(Reader.class, new ClobReaderTypeHandler());
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new StringTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, unknownTypeHandler);
    register(Object.class, JdbcType.OTHER, unknownTypeHandler);
    register(JdbcType.OTHER, unknownTypeHandler);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());

    register(Instant.class, new InstantTypeHandler());
    register(LocalDateTime.class, new LocalDateTimeTypeHandler());
    register(LocalDate.class, new LocalDateTypeHandler());
    register(LocalTime.class, new LocalTimeTypeHandler());
    register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
    register(OffsetTime.class, new OffsetTimeTypeHandler());
    register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
    register(Month.class, new MonthTypeHandler());
    register(Year.class, new YearTypeHandler());
    register(YearMonth.class, new YearMonthTypeHandler());
    register(JapaneseDate.class, new JapaneseDateTypeHandler());

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }

在配置类加载的时候会创建类型处理器对象。
在这里插入图片描述

到这里 类型处理器的使用和解析已经结束了, 但是注入处理器注册器工厂之后在Mybatis怎么在执行sql的使用调用参数处理器下篇博文在继续了解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值