JDBC数据类型与Java语言中的数据类型并不是完全对应的,所以在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换成JDBC类型,而从结果集中获取数据时,则需要从JDBC类型转换成Java类型。MyBatis 使用类型处理器完成上述两种转换,如图所示。
在MyBatis中使用 JdbeType
这个枚举类型代表JIDBC中的数据类型,该枚举类型中定义了 TYPECODE
字段,记录了JDBC类型在 javasql.Types
中相应的常量编码,并通过一个静态集合 codelookup
( HashMap<nteger,JdbeTypec>
类型)维护了常量编码与JdbcType之间的对应关系。
TypeHandler
是类型处理器接口,MyBatis中所有的类型转换器都继承了 TypeHandler
接口,在 TypeHandler
接口中定义了如下四个方法,这四个方法分为两类: setParameter()
方法负责将数据由JdbeType类型转换成 Java
类型; getResult()
方法及其重载负责将数据由Java类型转换成JdbcType类型。
public interface TypeHandler<T> { //在通过PreparedStatement为SQL语句绑定参数时,会将数据由JdbcType类型转换成Java类型 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; //从 ResultSet 中获取数据时会调用此方法,会将数据由Java类型转换成JdbcType类型 T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
为方便用户自定义TypeHandler实现,MyBatis 提供了BaseTypeHandler 这个抽象类,它实现了 TypeHandler
接口,并继承了TypeReference抽象类,其继承结构如图所示。
<img src="http://qiniu-cdn.janker.top/oneblog/20200117165004531.jpg" style="zoom:33%;" />
在 BaseTypeHandler
中实现 TypeHandler.setParameter()
方法和 TypeHandler.getResult()
方法, 具体实现如下所示。需要注意的是,这两个方法对于非空数据的处理都交给了子类实现。
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("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); } } } 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); } }
BaseTypeHandler
的实现类比较多,如图所示,但大多是直接调用 PreparedStatement
和 ResultSet
或 CallableStatement
的对应方法,实现比较简单。
<img src="/Users/janker/Library/Application Support/typora-user-images/image-20200118165213276.png" style="zoom:50%;" />
这里以 IntegerTypeHandler
为例简单介绍,其他的实现类请读者参考相关源码:
public class IntegerTypeHandler extends BaseTypeHandler<Integer> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException { //调用PreparedStatement.setInt()实现参数绑定 ps.setInt(i, parameter); } @Override public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException { //调用PreparedStatement.getInt()获取指定列值 int result = rs.getInt(columnName); return result == 0 && rs.wasNull() ? null : result; } @Override public Integer getNullableResult(ResultSet rs, int columnIndex) throws SQLException { //调用ResultSet.getInt()获取指定列值 int result = rs.getInt(columnIndex); return result == 0 && rs.wasNull() ? null : result; } @Override public Integer getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { //调用CallableStatement.getInt()获取指定列值 int result = cs.getInt(columnIndex); return result == 0 && cs.wasNull() ? null : result; } }
一般情况下, TypeHandler
用于完成单个参数以及单个列值的类型转换,如果存在多列值转换成一个Java对象的需求,应该优先考虑使用在映射文件中定义合适的映射规则( <resultMap>
节点)完成映射。
介绍完 TypeHandler
接口及其功能后,我们来看一下 MyBatis
如何管理众多的 TypeHandler
接口实现,如何知道何时使用哪个 TypeHandler
接口完成转换。这些都是由 TypehandlerRegistry
完成的,在 MyBatis
初始化过程中,会为所有已知的 TypeHandler
创建对象,并实现注册到 TypeHandlerRegistry
中,由 TypeHandlerRegistry
负责挂你这些 TypeHandler
对象。
TypeHandlerRegistry
的核心字段的含义如下:
//记录JdbcType与TypeHandler之间的对应关系,其中JdbcType是一个枚举类型,它定义对应的JDBC类型 //该集合主要用于从结果集读取数据时,将数据从Jdbc类型转换成Java类型 private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class); //记录了Java类型向指定JdbcType转换时,需要使用的TypeHandler对象。例如: Java 类型中的String可能 //转换成数据库的char、varchar等多种类型,所以存在一对多关系 private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>(); private final TypeHandler<Object> unknownTypeHandler; //记录了全部TypeHandler类型以及该类型相应的TypeHandler对象 private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>(); //空TypeHandler集合的标识 private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap(); private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
TypeHandler
对象 TypeHandlerRegistry.register()
方法实现了注册TypeHandler对象的功能,该注册过程会向上述四个集合中添加 TypeHandler
对象。 register()
方法有多个重载,这些重载之间的调用关系如图所示。
<img src="http://qiniu-cdn.janker.top/oneblog/20200119114322840.jpg" style="zoom:33%;" />
从图中可以看出,多数register()方法最终会调用重载④完成注册功能,我们先来介绍该方法的实现,其三个参数分别指定了TypeHandler 能够处理的Java 类型、Jdbc 类型以及TypeHandler对象。
//register()方法的重载④的实现如下: private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { //检验是否明确指定了TypeHandler能够处理的Java类型 //获取指定Java类型在TYPE_ HANDLER_ MAP集合中对应的TypeHandler集合 Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { //创建新的TypeHandler集合,并添加到TYPE HANDLER MAP中 map = new HashMap<>(); typeHandlerMap.put(javaType, map); } //将TypeHandler对象注册到typeHandlerMap集合中 map.put(jdbcType, handler); } //向AllTypeHandler集合注册TypeHandler类型和对应的TypeHandler对象【以Class为key handler对象为value】 allTypeHandlersMap.put(handler.getClass(), handler); }
在①~③这个三个 registr()
方法重载中会尝试读取 TypeHandler
类中定义的 @MappedTypes
注解和 @MappedJdbcTypes
注解, @MappedTypes
注解用于指明该 TypeHandler
实现类能够处理的Java类型的集合, @MappedJdbcTypes
注解用于指明该 TypeHandler
实现类能够处理的 JDBC
类型集合。 register()
方法的重载①~③的具体实现如下:
//register()方法的重载①的实现如下: public void register(Class<?> typeHandlerClass) { boolean mappedTypeFound = false; //获取 @MappedTypes 注解 MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> javaTypeClass : mappedTypes.value()) { //经过强制类型转换 以及反射创建TypeHandler对象之后,交由重载③继续处理 register(javaTypeClass, typeHandlerClass); mappedTypeFound = true; } } if (!mappedTypeFound) { //未@MappedTypes 注解,调用重载方法 ② 处理 register(getInstance(null, typeHandlerClass)); } } //register()方法的重载②的实现如下: public <T> void register(TypeHandler<T> typeHandler) { boolean mappedTypeFound = false; //获取@MappedTypes注解 并根据MappedTypes注解指定的java类型进行注册,逻辑与重载①类似 MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> handledType : mappedTypes.value()) { //交由重载③进行处理 register(handledType, typeHandler); mappedTypeFound = true; } } //从3.1.0版本开始,可以根据TypeHandler类型自动查找对应的Java类型,这需要 //我们的TypeHandler实现类同时继承TypeReference这个抽象类 // @since 3.1.0 - try to auto-discover the mapped type 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) { //获取MappedJdbcTypes注解 MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); if (mappedJdbcTypes != null) { //根据指定类型进行注册 读取@MappedJdbcTypes value值 获取handlerJdbcType for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { register(javaType, handledJdbcType, typeHandler); } if (mappedJdbcTypes.includeNullJdbcType()) { register(javaType, null, typeHandler); } } else { //未指定MappedJdbcTypes注解 交由重载④继续处理 register(javaType, null, typeHandler); } }
上述全部的 register()
方法重 载都是在向 TYPE_ HANDLER_ _MAP
集合和 ALL_TYPE_HANDLERS MAP
集合注册TypeHandler 对象,而重载⑤是向 JDBC_ TYPE_ HANDLER_ MAP
集合注册 TypeHandler
对象,其具体实现如下:
//register()方法重载⑤的实现如下: public void register(JdbcType jdbcType, TypeHandler<?> handler) { // 注册JdbcType类型对一个的TypeHandler jdbcTypeHandlerMap.put(jdbcType, handler); }
TypeHandlerRegistry
除了提供注册单个 TypeHandler
的 register()
重载,还可以扫描整个包下的 TypeHandler
接口实现类,并将完成这些 TypeHandler
实现类的注册。下面来看重载⑥的具体实现:
//register()方法重载⑥的实现,主要用来自动扫描指定包下的TypeHandler实现并完成注册 public void register(String packageName) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); //查找指定包下的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(type); } } }
最后来看 TypeHandlerRegistry
构造方法,会通过上述 register()
方法为很多基础类型注册对应的 TypeHandler
对象,简略代码如下:
public TypeHandlerRegistry(Configuration configuration) { //...这里重点看下String相关的TypeHandler对象的注册 其他类型的TypeHandler的注册类型(略) // stringTypeHandler 能够将数据从String 类型转换成null ( JdbcType ),所以向 TYPE HANDLER MAP //集合注册该对象,并向ALL_ TYPE_ HANDLERS_ MAP集合注册StringTypeHandler register(String.class, new StringTypeHandler()); register(JdbcType.NVARCHAR, new NStringTypeHandler()); //向JDBC_TYPE_HANDLER_MAP集合注册NVARCHAR对应的NStringTypeHandler register(JdbcType.NVARCHAR, new NStringTypeHandler()); // ...注册其他JdbcType类型对应的TypeHandler的过程类似(略) }
介绍完注册TypeHandler对象的功能之后,再来介绍TypeHandlerRegistry提供的查找TypeHandler对象的功能。TypeHandlerRegistry.getTypeHandler()方法实现了从上述四个集合中获取对应TypeHandler对象的功能。TypeHandlerRegistry.getTypeHandler()方法有多个重载,这些重载之间的关系如图所示。
<img src="http://qiniu-cdn.janker.top/oneblog/20200119134802598.png" style="zoom:40%;" />
经过一系列类型转 换之后, TypeHandlerRegistry.getTypeHandler()
方法的多个重载都会调用 TypeHandlerRegistry.getTypeHandle(Type,JdbcType)
这个重载方法,它会根据指定的 Java
类型和 JdbcType
类型查找相应的 TypeHandler
对象,具体实现如下:
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) { if (ParamMap.class.equals(type)) { return null; } //查找或初始化Java类型对一个的TypeHandler对象 Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type); TypeHandler<?> handler = null; if (jdbcHandlerMap != null) { //根据JdbcType查找TypeHandler handler = jdbcHandlerMap.get(jdbcType); if (handler == null) { handler = jdbcHandlerMap.get(null); } if (handler == null) { // #591 //如果JdbcType只注册了一个TypeHandler,使用此TypeHandler对象 handler = pickSoleHandler(jdbcHandlerMap); } } // type drives generics here return (TypeHandler<T>) handler; }
在 TypeHandlerRegitry.getJdbcHandlerMap()
方法中,会检测 TYPE_HANDLER_MAP
集合中指定 Java
类型对应的 TypeHandler
集合是否已经初始化。如果未初始化,则尝试以该Java类型的、已初始化的父类对应的 TypeHandler
集合为初始集合;如不存在己初始化的父类,则将其对应的 TypeHandler
集合初始化为 NULL_TYPE_HANDLER_MAP
标识。 getJdbcHandlerMap()
方法具体实现如下:
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) { //查找指定Java类型对应的TypeHandler集合 Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type); if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) { //检测是否为空集合标识 return null; } //初始化指定的Java类型的TypeHandler集合 if (jdbcHandlerMap == null && type instanceof Class) { Class<?> clazz = (Class<?>) type; if (Enum.class.isAssignableFrom(clazz)) { Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz; jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass); if (jdbcHandlerMap == null) { //枚举类型的处理 register(enumClass, getInstance(enumClass, defaultEnumTypeHandler)); return typeHandlerMap.get(enumClass); } } else { //查找父类对应的TypeHandler集合,并作为初始化集合 jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz); } } // 以NULL_TYPE_HANDLER_MAP作为TypeHandler集合 typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap); return jdbcHandlerMap; } // getJdbcHandlerMapForSuperclass()实现 private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) { Class<?> superclass = clazz.getSuperclass(); if (superclass == null || Object.class.equals(superclass)) { return null; //父类为Object或null则查找结束 } Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass); if (jdbcHandlerMap != null) { return jdbcHandlerMap; } else { //继续递归查找父类对应的TypeHandler集合 return getJdbcHandlerMapForSuperclass(superclass); } }
TypeHandlerRegistry.getMappingTypeHandler()
方法会根据指定的TypeHandler类型,直接从 ALL_TYPE_HANDLERS_MAP
集合中查找 TypeHandler
对象。 TypeHandlerRegistry.getTypeHandler(JdbcType)
方法会根据指定的 JdbcType
类型,从 JDBC_TYPE_HANDLER_MAP
集合中查找 TypeHandler
对象。这两个方法实现相对简单,代码就不贴出来了。
MyBatis
本身提供的
TypeHandler
实现,我们也可以添加自定义的
TypeHandler
接口实现,添加方式是在 mybatisconfig.xml
配置文件中的 <typeHandlers>
节点下,添加相应的 <typeHandler>
节点配置,并指定自定义的 TypeHandler
接口实现类。在 MyBatis
初始化时会解析该节点,并将该 TypeHandler
类型的对象注册到 TypeHandlerRegistry
中,供 MyBatis
后续使用。在后面介绍 MyBatis
初始化时还会提到该配置。
在编写SQL语句时,使用别名可以方便理解以及维护,例如表名或列名很长时,我们一般会为其设计易懂易维护的别名。 MyBatis
将SQL语句中别名的概念进行了延伸和扩展, MyBatis
可以为一个类添加一一个别名,之后就可以通过别名引用该类。 MyBatis
通过 TypeAliasRegistry
类完成别名注册和管理的功能, TypeAliasRegistry
的结构比较简单,它通过 TYPE_ ALIASES
字段( Map<String, Class<?>>
类型)管理别名与 Java
类型之间的对应关系,通过 TypeAliasRegistry.registerAlias()
方法完成注册别名,该方法的具体实现如下:
public void registerAlias(String alias, Class<?> value) { if (alias == null) { //校验别名是否为空,为空则抛出类型异常 throw new TypeException("The parameter alias cannot be null"); } // issue #748 //将别名转换为小写 String key = alias.toLowerCase(Locale.ENGLISH); //校验别名是否已经存在 if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) { throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'."); } //注册别名 typeAliases.put(key, value); }
在 TypeAliasRegistry
的构造方法中,默认为Java的基本类型及其数组类型、基本类型的封装类及其数组类型、 Date
、 BigDecimal
、 BigInteger
、 Map
、 HashMap
、 List
、 ArrayList
、 Collection
、 Iterator
、 ResultSet
等类型添加了别名,代码比较简单,请读者参考 TypeAliasRegistry
的源码进行学习。
TypeAliasRegistry
中还有两个方法需要介绍一下, registerAliases(String,Class<?>)
重载会扫描指定包下所有的类,并为指定类的子类添加别名; registerAlias(Class<?>)
重 载中会尝试读取 @Alias
注解。这两个方法的实现如下:
public void registerAliases(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); //查找指定包下的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); } } } public void registerAlias(Class<?> type) { String alias = type.getSimpleName(); //类型的简单名称(不包含报名) //读取Alias注解 Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } //检测次别名不存在后,会将其记录到TYPE_ALIASES集合中 registerAlias(alias, type); }
本文由 Janker 创作,采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。