Mybatis源码学习之类型转换(四)

简述

JDBC数据类型与Java语言中的数据类型并不是完全对应的,所以在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换成JDBC类型,而从结果集中获取数据时,则需要从JDBC类型转换成Java类型。MyBatis使用类型处理器完成上述两种转换。

image

以下是包org.apache.ibatis.type下所有类的继承关系,每一个jdbc类型都对应一个相应的类型转换器(点击图片查看大图)。

image

JdbcType

在MyBatis中使用JdbcType这个枚举类型
代表JDBC中的数据类型,该枚举类型中定义了TYPE_CODE字段,记录了JDBC类型在java.sql.Types中相应的常量编码,并通过一个静态集合codeLookup(HashMap<Integer,JdbcType>类型)维护了常量编码与JdbcType之间的对应关系,它们的对应关系如下:

{
  -1=LONGVARCHAR,
  0=NULL,
  1=CHAR,
  -2=BINARY,
  2=NUMERIC,
  -3=VARBINARY,
  3=DECIMAL,
  -4=LONGVARBINARY,
  4=INTEGER,
  -5=BIGINT,
  -6=TINYINT,
  5=SMALLINT,
  -7=BIT,
  6=FLOAT,
  70=DATALINK,
  7=REAL,
  -8=ROWID,
  8=DOUBLE,
  -9=NVARCHAR,
  -10=CURSOR,
  12=VARCHAR,
  -15=NCHAR,
  -16=LONGNVARCHAR,
  16=BOOLEAN,
  2000=JAVA_OBJECT,
  2001=DISTINCT,
  2002=STRUCT,
  2003=ARRAY,
  2004=BLOB,
  2005=CLOB,
  2006=REF,
  1111=OTHER,
  2009=SQLXML,
  -155=DATETIMEOFFSET,
  91=DATE,
  2011=NCLOB,
  92=TIME,
  93=TIMESTAMP,
  -2147482648=UNDEFINED
}

TypeHandler

MyBatis中所有的类型转换器都继承了TypeHandler接口,在TypeHandler接口中定义了四个方法,这四个方法分为两类:
- setParameter()方法负责将数据由JdbcType类型转换成Java类型
- getResult()方法及其重载负责将数据由Java类型转换成JdbcType类型

/**
 * 类型转换器接口
 *
 * @author Clinton Begin
 */
public interface TypeHandler<T> {

    /**
     * 通过PreparedStatement为sql语句绑定参数时,将数据类型由jdbc类型转换成java类型
     */
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

    /**
     * 从ResultSet获取结果时,将数据由java类型转换成jdbc类型
     *
     * @param columnName 列名
     */
    T getResult(ResultSet rs, String columnName) throws SQLException;

    /**
     * @param columnIndex 列索引
     */
    T getResult(ResultSet rs, int columnIndex) throws SQLException;

    /**
     * @param cs          调用数据库中的存储过程并获取返回值
     * @param columnIndex 列索引
     */
    T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

为方便用户自定义TypeHandler实现,MyBatis提供了BaseTypeHandler这个抽象类,它实现了TypeHandler接口,并继承了TypeReference抽象类
image

在BaseTypeHandler中实现了TypeHandler.setParameter()方法和TypeHandler.getResult()方法。需要注意的是,这两个方法对于非空数据的处理都交给了子类实现。
每个基本类型都会继承BaseTypeHandler实现对应类型的转换,因为实现类比较多,大多是直接调用PreparedStatement 和ResultSet或CallableStatement的对应方法,这里就以 IntegerTypeHandler 为例进行学习:

/**
 * Integer类型转换器
 * @author Clinton Begin
 */
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {

    /**
     * 参数绑定
     * @param i 表示第几个参数
     * @param parameter 参数值
     * */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
            throws SQLException {
        //调用PreparedStatement.setInt()实现参数绑定
        ps.setInt(i, parameter);
    }

    /**
     * 获取指定列值
     * @param columnName 列名
     * */
    @Override
    public Integer getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        //获取指定列值
        return rs.getInt(columnName);
    }

    /**
     * 获取指定列值
     * @param columnIndex 列索引
     * */
    @Override
    public Integer getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return rs.getInt(columnIndex);
    }

    /**
     * 获取指定列值
     * @param columnIndex 列索引
     * */
    @Override
    public Integer getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return cs.getInt(columnIndex);
    }
}

TypeHandlerRegistry

MyBatis如何管理众多的TypeHandler接口实现,如何知道何时使用哪个TypeHandler接口实现完成转换呢?这就是TypeHandlerRegistry完成的,在MyBatis初始化过程中,会为所有已知的TypeHandler创建对象,并实现注册到TypeHandlerRegistry中,由TypeHandlerRegistry负责管理这些TypeHandler对象。

首先看一下TypeHandlerRegistry中的几个核心字段,如下:

/**
     * 记录JdbcType对应的类型转换对象,从结果中读取数据时将数据由jdbc类型转换成java类型
     * */
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);


    /**
     * java类型向指定JdbcType类型转换时,需要使用的TypeHandler对象
     * eg:
     * java中的String可能转换成数据库中的char、varchar等多种类型,存在一对多的关系
     * */
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();


    /**
     * 全部TypeHandler及对应的对象
     * */
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();


    /**
     * 空TypeHandler集合标识
     * */
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();

1、注册TypeHandler对象

TypeHandlerRegistry.register()方法实现了注册TypeHandler对象的功能,该注册过程会向上述四个集合中添加TypeHandler对象。

register()方法有多个重载,这些重载之间的调用关系如下:

image
从调用关系中可以看出register()方法大多最后都是调用④来完成注册功能的,我们先看一④方法的实现:

    /**
     * register()重载④
     * @param javaType java类型
     * @param jdbcType jdbc类型
     * @param handler 类型转换器对象
     * */
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        //判断指定的java类型是否为null
        if (javaType != null) {
            //获取指定的Java类型在集合TYPE_HANDLER_MAP中对应的TypeHandler
            Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
            if (map == null || map == NULL_TYPE_HANDLER_MAP) {
                //创建新的TypeHandler集合并添加到TYPE_HANDLER_MAP中
                map = new HashMap<JdbcType, TypeHandler<?>>();
                TYPE_HANDLER_MAP.put(javaType, map);
            }
            //将handler对象注册到TYPE_HANDLER_MAP集合中
            map.put(jdbcType, handler);
        }
        //向ALL_TYPE_HANDLERS_MAP集合中注册TypeHandler类型及对应的TypeHandler对象
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }

在①~③这个三个register()方法重载中会尝试读取TypeHandler类中定义的@MappedTypes注解和@MappedJdbcTypes注解:

  • @MappedTypes注解用于指明该TypeHandler实现类能够处理的Java类型的集合
  • @MappedJdbcTypes注解用于指明该TypeHandler实现类能够能够处理的JDBC类型集合
/**
     * register()方法重载①的实现
     */
    public void register(Class<?> typeHandlerClass) {
        boolean mappedTypeFound = false;
        //获取注解@MappedTypes
        MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
        if (mappedTypes != null) {
            //根据注解中@MappedTypes指定的java类型进行注册
            for (Class<?> javaTypeClass : mappedTypes.value()) {
                //经过强制类型转换及反射创建TypeHandler对象后,交给重载③处理
                register(javaTypeClass, typeHandlerClass);
                mappedTypeFound = true;
            }
        }
        if (!mappedTypeFound) {
            register(getInstance(null, typeHandlerClass));
        }
    }

     /**
     * register()重载②
     */
    @SuppressWarnings("unchecked")
    public <T> void register(TypeHandler<T> typeHandler) {
        boolean mappedTypeFound = false;
        //获取注解@MappedTypes,根据注解指定的java类型进行注册,逻辑与①类似
        MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
        if (mappedTypes != null) {
            for (Class<?> handledType : mappedTypes.value()) {
                //交给重载③处理
                register(handledType, typeHandler);
                mappedTypeFound = true;
            }
        }
        // @since 3.1.0 - try to auto-discover the mapped type
        //从3.1.0开始,可以根据TypeHandler类型自动查找对应的java类型,这就要求TypeHandler的实现类同时继承TypeReference抽象类
        if (!mappedTypeFound && typeHandler instanceof TypeReference) {
            try {
                TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
                register(typeReference.getRawType(), typeHandler);
                mappedTypeFound = true;
            } catch (Throwable t) {
                // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
            }
        }
        if (!mappedTypeFound) {
            //交给重载③处理
            register((Class<T>) null, typeHandler);
        }
    }

     /**
     * register()重载③
     */
    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        //获取注解@MappedTypes,根据注解指定的java类型进行注册
        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);
        }
    }

上述4个register()方法重载都是在向TYPE_HANDLER_MAP集合和ALL_TYPE_ HANDLERS_MAP集合注册TypeHandler对象,而重载⑤是向JDBC_TYPE_HANDLER_MAP集合注册TypeHandler对象,其具体实现如下:

    /**
     * register()重载⑤
     */
    public void register(JdbcType jdbcType, TypeHandler<?> handler) {
        //注册JdbcType类型对应的TypeHandler
        JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
    }

TypeHandlerRegistry除了提供注册单个TypeHandler的register()重载,还可以扫描整个包下的TypeHandler接口实现类,并将完成这些TypeHandler实现类的注册。下面来看重载⑥的具体实现:

 /**
     * register()重载⑥
     * 扫描指定包下TypeHandler的实现类,并完成注册
     */
    public void register(String packageName) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        //查找指定包下TypeHandler的实现类
        resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
        Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
        for (Class<?> type : handlerSet) {
            //Ignore inner classes and interfaces (including package-info.java) and abstract classes
            //过滤掉内部类、接口及抽象类
            if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
                //交给register()重载①处理
                register(type);
            }
        }
    }

最后来看TypeHandlerRegistry构造方法,会通过上述register()方法为基础类型注册对应的TypeHandler对象:

public TypeHandlerRegistry() {
        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());

        /***********************省略*********************/
}

2、获取TypeHandler对象

TypeHandlerRegistry.getTypeHandler()方法实现了从上述四个集合中获取对应TypeHandler对象的功能。TypeHandlerRegistry.getTypeHandler()方法有多个重载,这些重载之间的关系如下:

image

由上图的调用关系我们可以看出,经过一系列类型转换之后,TypeHandlerRegistry.getTypeHandler()方法的多个重载都会调用TypeHandlerRegistry.getTypeHandle(Type,JdbcType)这个重载方法,它会根据指定的Java类型和JdbcType类型查找相应的TypeHandler对象,具体实现如下:

/**
     * 根据指定的java类型和jdbc类型,查找对应的TypeHandler对象
     */
    @SuppressWarnings("unchecked")
    private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
        if (ParamMap.class.equals(type)) {
            return null;
        }
        //根据java类型获取对应jdbc类型的集合jdbcHandlerMap
        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);
            }
        }
        // type drives generics here
        return (TypeHandler<T>) handler;
    }

在getJdbcHandlerMap()方法中,会检测TYPE_HANDLER_MAP集合中指定Java类型对应的TypeHandler集合是否已经初始化。如果未初始化,则尝试以该Java类型的、已初始化的父类对应的TypeHandler集合为初始集合;如不存在已初始化的父类,则将其对应的TypeHandler集合初始化为NULL_TYPE_HANDLER_MAP标识。

getJdbcHandlerMap()方法具体实现如下:

    /**
     * 根据指定的java类型获取对应的jdbc和TypeHandler的集合
     */
    private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
        //查找指定java类型对应的TypeHandler集合
        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;
            //枚举类型处理
            if (clazz.isEnum()) {
                jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
                if (jdbcHandlerMap == null) {
                    register(clazz, getInstance(clazz, defaultEnumTypeHandler));
                    return TYPE_HANDLER_MAP.get(clazz);
                }
            } else {
                //查找父类对应的TypeHandler集合
                jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
            }
        }
        TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
        return jdbcHandlerMap;
    }

    /**
     * 获取父类对应的jdbc和TypeHandler集合
     */
    private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
        Class<?> superclass = clazz.getSuperclass();
        //父类为null终止
        if (superclass == null || Object.class.equals(superclass)) {
            return null;
        }
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(superclass);
        if (jdbcHandlerMap != null) {
            return jdbcHandlerMap;
        } else {
            //递归查找父类对应的集合
            return getJdbcHandlerMapForSuperclass(superclass);
        }
    }

最后,除了MyBatis本身提供的TypeHandler实现,我们也可以添加自定义的TypeHandler接口实现,添加方式是在mybatis-config.xml配置文件中的<typeHandlers>节点下,添加相应的<typeHandler>节点配置,并指定自定义的TypeHandler接口实现类。在MyBatis初始化时会解析该节点,并将该TypeHandler类型的对象注册到TypeHandlerRegistry中,以供MyBatis后续使用。

TypeAliasRegistry

我们在使用mybatis时,经常会对表名或列名起一些别名,那么mybatis是如何识别别名与原始名字的对应关系呢,这就是接下来要学习的TypeAliasRegistry的作用了。

MyBatis通过TypeAliasRegistry类完成别名注册和管理的功能,TypeAliasRegistry的结构比较简单,它通过TYPE_ALIASES字段(Map<String, Class<?>>类型)管理别名与Java类型之间的对应关系,通过TypeAliasRegistry.registerAlias()方法完成注册别名,该方法的具体实现如下:

 /**
     * 注册别名
     *
     * @param alias 别名
     * @param value 类
     */
    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集合,完成注册
        TYPE_ALIASES.put(key, value);
    }

在TypeAliasRegistry的构造方法中,默认为Java的基本类型及其数组类型、基本类型的封装类及其数组类型、Date、BigDecimal、BigInteger、Map、HashMap、List、ArrayList、Collection、Iterator、ResultSet等类型添加了别名。

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

    ..................省略.................
 }

TypeAliasRegistry中还有两个方法需要介绍一下:

  • registerAliases(String,Class<?>)重载会扫描指定包下所有的类,并为指定类的子类添加别名;
  • registerAlias(Class<?>)重载中会尝试读取@Alias注解
/**
     * 注册别名
     *
     * @param packageName 包名
     * @param superType   父类类型
     */
    public void registerAliases(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        //查找指定包下superType类型的类
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
        for (Class<?> type : typeSet) {
            // Ignore inner classes and interfaces (including package-info.java)
            // Skip also inner classes. See issue #6
            //过滤掉内部类、接口及抽象类
            if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
                registerAlias(type);
            }
        }
    }

    /**
     * 注册别名
     *
     * @param type 普通类类型
     */
    public void registerAlias(Class<?> type) {
        //类名(不包括包名)
        String alias = type.getSimpleName();
        //获取@Alias注解
        Alias aliasAnnotation = type.getAnnotation(Alias.class);
        if (aliasAnnotation != null) {
            //获取@Alias中的别名
            alias = aliasAnnotation.value();
        }
        registerAlias(alias, type);
    }
posted @ 2018-06-17 17:57  IT码客  阅读(643)  评论(0编辑  收藏  举报