简述

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

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

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抽象类

在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()方法有多个重载,这些重载之间的调用关系如下:



从调用关系中可以看出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()方法有多个重载,这些重载之间的关系如下:

由上图的调用关系我们可以看出,经过一系列类型转换之后,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);
}

最新文章

  1. python中IndentationError: expected an indented block错误的解决方法
  2. [转]Java反射之如何判断类或变量、方法的修饰符(Modifier解析)
  3. java-final关键字
  4. 使用WebDriver遇到的那些坑
  5. GZFramwork数据库层《二》单据表增删改查(自动生成单据号码)
  6. 《Pro Git》笔记2:Git基础操作
  7. fzu Problem 2148 Moon Game(几何 凸四多边形 叉积)
  8. jQuery之位置
  9. Seek the Name, Seek the Fame(Kmp)
  10. Black Jack
  11. fpSpread1 简单用法
  12. 继BAT之后 第四大巨头是谁
  13. 利用ASP.NET操作IIS (可以制作安装程序)
  14. JAVA_OPTS设置
  15. reentrantlocklock实现有界队列
  16. [A] 1046 Shortest Distance
  17. http://www.cnblogs.com/xalion/p/5111279.html
  18. Linux下./configure &amp;&amp; make &amp;&amp; make install 编译安装和卸载
  19. php使用xa规范实现分布式事务处理
  20. 20145302张薇《Java程序设计》第十周学习总结

热门文章

  1. IDEA的常见的设置和优化(功能)
  2. FFmpeg里面的时间单位
  3. javascript 利用数组制作分页效果
  4. Div实现水平垂直居中
  5. 如何在Marketing Cloud里创建extension field扩展字段
  6. C#面向对象(抽象类、接口、构造函数、重载、静态方法和静态成员)
  7. linux 下安装 jdk1.7
  8. Git---初入开源代码管理库的学习过程003
  9. HashMap 和 Hashtable 有什么区别?(未完成)
  10. RxJava——响应式编程