专栏首页Java技术大杂烩Mybatis 类型转换源码分析

Mybatis 类型转换源码分析

本文首发于个人公众号 Java 技术大杂烩,欢迎关注

前言

JDBC 提供的数据类型和Java的数据类型并不是完全对应的,当 Mybatis 在解析 SQL ,使用 PreparedStatement 来为 SQL 设置参数的时候,需要从 Java 类型转换为 JDBC 的类型,当从 ResultSet 中获取结果的时候,需要中 JDBC 类型转换为 Java 类型;Mybatis 的类型转换模块就是用来转换这两种数据类型的;比如在写 Mapper 文件的时候,可以有如下写法:

    <insert id="addUser" parameterType="User">
        INSERT INTO user(id, name, age, height) VALUES (
        #{id},
        #{name, javaType=String, jdbcType=varchar},
        #{age, javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler},
        #{height, javaType=double, jdbcType=NUMERIC, numericScale=2}
        )
    </insert>

可以指定Java和数据库对应的类型,还可以指定自定义的类型处理器等,在 Mybatis 在解析 SQL 的时候,会通过类型转换处理器进行相应的转换

源码分析

Mybatis 的类型转换相关的代码主要在 type 包下,如下所示:

当然,type 包下不只是这些类,还有其他的一个内置的类型转换处理器,如 ArrayTypeHandler 等,还有三个类型的注册类 TypeHandlerRegistrySimpleTypeRegistryTypeAliasRegistry ,此外该包下还定义了一些注解等。

类型处理器

TypeHandler 接口

Mybatis 中所有的类型转换器都实现了 TypeHandler 接口,该接口下只有四个方法,共分为两类,一类是将 JDBC 类型转换为 Java 类型,一类是将 Java 类型转换为 JDBC 类型,源码如下:

public interface TypeHandler<T> {
  // 通过 PreparedStatement 绑定参数时,参数由 Jdbc 类型转换为 Java 类型
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  // 从 ResultSet 获取数据时,数据由 Java 类型转换为 Jdbc类型
  T getResult(ResultSet rs, String columnName) throws SQLException;

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

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

TypeReference 抽象类

TypeReference 是一个抽象类,它表示引用一个泛型类型:

public abstract class TypeReference<T> {
  // 原始类型Type
  private final Type rawType;
  // 构造,获取原始类型
  protected TypeReference() {
    rawType = getSuperclassTypeParameter(getClass());
  }

  // 获取原始类
  Type getSuperclassTypeParameter(Class<?> clazz) {
    // 获得带有泛型的父类
    Type genericSuperclass = clazz.getGenericSuperclass();
    if (genericSuperclass instanceof Class) {
      if (TypeReference.class != genericSuperclass) {
        return getSuperclassTypeParameter(clazz.getSuperclass());
      }
     // 抛异常
    }
    // 获取到泛型中的原始类型
    // ParameterizedType是一个记录类型泛型的接口
    // getActualTypeArguments():回泛型类型数组
    Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
    if (rawType instanceof ParameterizedType) {
      rawType = ((ParameterizedType) rawType).getRawType();
    }
    // 返回原始类型
    return rawType;
  }
 // 返回原始类型
  public final Type getRawType() {
    return rawType;
  }
}

BaseTypeHandler

Mybatis 中,提供了 TypeHandler 接口的唯一实现,即 BaseTypeHandler ,主要是为了方便用户的自定义实现 TypeHandler 接口。

BaseTypeHandler 抽象类中,实现了 TypeHandlersetParameter()getResult() 方法,在这两个方法内部,对于非空数据的处理,由具体的子类进行实现;源码如下:

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
  // 表示一个配置文件类
  protected Configuration configuration;

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }
  // 为 SQL 设置参数
  @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 {
        // 如果参数为空,则设置为 null
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException(e);
      }
    } else {
      try {
        // 如果参数不为空,则由子类实现,该方法是一个抽象方法
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException(e);
      }
    }
  }
  // 从结果集中根据列名获取数据
  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result;
    try {
      // 获取结果,由子类实现
      result = getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException( e);
    }
    // 如果为空,则返回 null
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  // 从结果集中根据列索引获取数据
  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    //   同上
  }
  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    //   同上
  }  
  // 为 SQL 设置非空的参数,由各个子类自己实现
  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  // 获取结果,由各个子类自己实现
  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;
}

StringTypeHandler

在上面的 BaseTypeHandler 抽象类中,为 SQL 设置参数和或结果集中获取数据,相应的都交由子类去实现,它大概有 31 个实现类,现在以StringTypeHandler 为例,看下它是怎么实现的;

public class StringTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getString(columnIndex);
  }
}

可以看到,String 类型的类型处理器会调用 PreparedStatementsetString 方法绑定参数,调用 ResultSetgetString 获取结果,其他的实现类大概都如此。

类型注册器 TypeHandlerRegistry

Mybatis 初始化的时候,会为所有已知的类型处理器 TypeHandler 创建对象,并注册到 TypeHandlerRegistry 中,由 TypeHandlerRegistry 来管理这些对象,接下来看下 TypeHandlerRegistry 的源码:

TypeHandlerRegistry 中定义了几个 Map 集合来存放相应的 TypeHandler 对象,如下所示:

// Jdbc 类型和类型处理器 TypeHandler 的对应关系
// 该集合主要用于从结果集中读取数据时,从 Jdbc 类型转换为 Java 类型
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

// Java类型和Jdbc类型的对应关系,当Java类型向指定的Jdbc类型转换时,需要使用的 TypeHandler 对象
// 一种Java类型可以对应多种Jdbc 类型,如 String 对应 char 和 varchar
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

// 为知类型,当找不到对应类型时,使用该类型,也就是 ObjectTypeHandler
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);

// 存放全部的 TypeHandler 类型以及该类型相应的 TypeHandler 对象
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

// 空类型
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = new HashMap<JdbcType, TypeHandler<?>>();

注册 TypeHandler

在创建该对象的时候,会对这些类型处理器进行注册:

  public TypeHandlerRegistry() {
    // 多种Java类型可以对应一种类型处理器
    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());

    // 一种 Java 类型可以对应多种处理器
    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    // ......... 注册其他类型........
}

接下来看下这些类型处理器是如何注册的,即把相应的类型处理器存放到 上述定义的几个 Map 中去,在 TypeHandlerRegistry 中定义了 12 个重载的 register() 方法进行注册,下面看下几个主要的方法实现:

 // 注册 Jdbc 类型和相应的处理器
  public void register(JdbcType jdbcType, TypeHandler<?> handler) {
    JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
  }

  // 注册 Java 类型和相应的处理器
  private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    // 处理注解的情况
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      register(javaType, null, typeHandler);
    }
  }
  // 根据 Java 类型 Jdbc 类型和类型处理器进行相应的注册
  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      // 根据 Java 类型获取对应的 Jdbc 类型
      Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
      // 如果为空,则新建一个
      if (map == null) {
        map = new HashMap<JdbcType, TypeHandler<?>>();
        TYPE_HANDLER_MAP.put(javaType, map);
      }
      // 注册
      map.put(jdbcType, handler);
    }
    // 同时注册类型处理器类和对象的对应关系
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
  }

当注册完这些类型处理器对象后,如何去查找相应的类型处理器呢,TypeHandlerRegistry 也提供了相应的方法来进行查找,提供了 6 个重载的 getTypeHandler 方法,根据 Java 类型和 Jdbc 类型查找对应的 TypeHandler对象:

  // 查找或初始化 Java 类型对应的 TypeHandler 集合
  private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    // 根据 Java 类型查找对应的 TypeHandler 集合
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      // 根据 Jdbc 类型查找 TypeHandler  对象
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = jdbcHandlerMap.get(null);
      }
      if (handler == null) {
        // 如果 jdbcHandlerMap 只注册了一个 TypeHandler 对象,则使用此 TypeHandler 对象
        handler = pickSoleHandler(jdbcHandlerMap);
      }
    }
    return (TypeHandler<T>) handler;
  }

  // 根据 Java 类型查找对应的 TypeHandler 集合
  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
    // 去 TYPE_HANDLER_MAP 进行查找
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
    // 检测是否为空集合
    if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
      return null;
    }
    // 初始化指定 Java 类型的 TypeHandler 集合
    if (jdbcHandlerMap == null && type instanceof Class) {
      Class<?> clazz = (Class<?>) type;
      // 查找父类对应的 TypeHandler 集合,并作为初始集合
      jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
      if (jdbcHandlerMap != null) {
        TYPE_HANDLER_MAP.put(type, jdbcHandlerMap);
      } else if (clazz.isEnum()) {
        // 枚举类的处理,进行注册
        register(clazz, new EnumTypeHandler(clazz));
        return TYPE_HANDLER_MAP.get(clazz);
      }
    }
    if (jdbcHandlerMap == null) {
      TYPE_HANDLER_MAP.put(type, NULL_TYPE_HANDLER_MAP);
    }
    return jdbcHandlerMap;
  }

上述就是类型注册器 TypeHandlerRegistry 的一个实现过程

别名注册器 TypeAliasRegistry

在编写 Mapper SQL 的时候,可以使用别名,比如,

<select id="findByName" resultType="map" parameterType="int">

然后的解析 SQL 的时候,就可以获取对应的类型,如 Java.util.MapJava.lang.Integer 等。Mybatis 通过 TypeAliasRegistry 来完成别名的注册和管理功能。

该方法比较简单,它提供了 5 个重载的 registerAlias方法来进行别名的注册,提供一个方法 resolveAlias来解析别名,最后在构造方法中进行别名的注册:

  private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  // 注册别名
  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // 将名称转换为小写
    String key = alias.toLowerCase(Locale.ENGLISH);
    // 判断名称是否存在,如果别名已存在,且对应的类型不一致,则抛异常
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    // 注册,别名和类型的对应关系
    TYPE_ALIASES.put(key, value);
  }

  public void registerAlias(Class<?> type) {
    String alias = type.getSimpleName();
    // 处理 @Alias 注解的情况
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    } 
    registerAlias(alias, type);
  }

解析别名:

 // 解析别名
  public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
      // 别名转换为小写,因为在注册的时候,转换过
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      // 如果该别名已经注册,则获取对应的类型
      if (TYPE_ALIASES.containsKey(key)) {
        value = (Class<T>) TYPE_ALIASES.get(key);
      } else {
        // 尝试使用反射来获取类型
        value = (Class<T>) Resources.classForName(string);
      }
      // 返回对应的类型
      return value;
    } catch (ClassNotFoundException e) {
      throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
    }
  }

注册别名,接下来就是进行别名的注册,通过构造方法进行注册

  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

以上就是 Mybatis 进行类型转换的一个主要代码逻辑,还是挺好理解的。

相关文章

Mybatis 解析配置文件的源码解析

本文分享自微信公众号 - Java技术大杂烩(tsmyk0715),作者:TSMYK

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-02-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring 事务提交回滚源码解析

    在上篇文章 Spring 事务初始化源码分析 中分析了 Spring 事务初始化的一个过程,当初始化完成后,Spring 是如何去获取事务,当目标方法异常后,...

    Java技术大杂烩
  • Spring 事务使用详解

    什么是事务?根据 维基百科事务 介绍,数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。简单来说,事务就是将一系...

    Java技术大杂烩
  • Spring Eureka 初探及集群搭建

    Eureka 主要是用于进行服务的注册和发现,今天就来学习一下如何使用 Eureka

    Java技术大杂烩
  • bootstrap 按钮组 btn-group

    <div class="btn-group"> <button type="button" class="btn btn-default">Left</but...

    用户5760343
  • 获取注解信息

    何白白
  • Bootstrap响应式前端框架笔记五——按钮

        Bootstrap中预设了default,primary,info,warning,danger和link6种按钮风格,示例如下:

    珲少
  • Bootstrap响应式前端框架笔记八——按钮组

        在Bootstrap定义的Css样式中,开发者可以将一组btn控件包裹在btn-group类中使其组合成按钮组控件,组合后的控件左右两侧的按钮将被圆角处...

    珲少
  • R语言基础操作①基础指令

    q()——退出R程序 tab——自动补全 ctrl+L——清空console ESC——中断当前计算

    用户1359560
  • 2.1 ASM-类-结构

    本章介绍了使用ASM core的API,生成编译后的class和转换编译后的class。 首先展示了编译后的class文件,然后介绍相应的ASM接口、组件和工具...

    白凡
  • bootstrap 按钮组 水平导航栏

    <div class="btn-group"> <button class="btn btn-default" type="button">首页</butto...

    用户5760343

扫码关注云+社区

领取腾讯云代金券