zoukankan      html  css  js  c++  java
  • 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);
        }
    
  • 相关阅读:
    Spring Boot 结合 Redis 序列化配置的一些问题
    基于SpringBoot的代码在线运行的简单实现
    将Spring实战第5版中Spring HATEOAS部分代码迁移到Spring HATEOAS 1.0
    用Spring Security, JWT, Vue实现一个前后端分离无状态认证Demo
    使用最新AndroidStudio编写Android编程权威指南(第3版)中的代码会遇到的一些问题
    C# 内存管理优化畅想----前言
    C# 内存管理优化实践
    C# 内存管理优化畅想(三)---- 其他方法&结语
    C# 内存管理优化畅想(二)---- 巧用堆栈
    C# 内存管理优化畅想(一)---- 大对象堆(LOH)的压缩
  • 原文地址:https://www.cnblogs.com/liukaifeng/p/10052622.html
Copyright © 2011-2022 走看看