JDBC数据类型与Java语言中的数据类型并不是完全对应的,所以在PreparedStatement为SQL绑定参数时,需要从Java类型转换成JDBC类型,从而结果集中获取数据时,则需要从JDBC类型转换成Java类型。MyBatis使用类型处理器完成上述两种转换
MyBatis中使用JdbcType这个枚举类型代表JDBC中的数据类型,该枚举类型中定义了TYPE_CODE字段,记录了JDBC类型在java.sql.Types中相应的变量编码,并通过一个静态集合codeLoolup(Map类型)维护变量编码与JdbcType之间的关系对应
1.TypeHandler
Mybatis所有的类型转换器都继承了TypeHandler接口,在TypeHandler接口中定义了4个方法。分为两类:setParameter()方法负责将数据由Java类型转为JdbcType类型;getReslut()方法及其重载负责将数据由JdbcType类型转换成Java类型
/**
* Mybatis所有的类型转换器都继承了TypeHandler接口
* 用于JdbcType与Java类型之间的转换
*
* @author Clinton Begin
*/
public interface TypeHandler<T> {
/**
* 在通过PreparedStatement绑定SQL参数时,会将数据由Java类型转换成JdbcType类型
*
* @param ps
* @param i
* @param parameter
* @param jdbcType
* @throws SQLException
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 从ResultSet中获取数据时,会将JdbcType转为Java类型
*
* @param rs
* @param columnName
* @return
* @throws SQLException
*/
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抽象类。
在BaseTypeHandler中实现了setParameter()方法和getResult()方法,具体实现如下所示。需要注意的是,这两个方法对于非空数据的处理都交给子类实现。
以IntegerTypeHandler为例:
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
// 参数绑定
ps.setInt(i, parameter);
}
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
// 获取指定列值
int result = rs.getInt(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
// 获取指定列值
int result = rs.getInt(columnIndex);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
int result = cs.getInt(columnIndex);
return result == 0 && cs.wasNull() ? null : result;
}
}
一般情况下TypeHandler用于完成单个参数以及单个列值的类型转换,如果存在多列值转换成一个Java对象的需求,应该优先考虑使用在映射文件中定义合适的映射规则
2.TypeHandlerRegistry
Mybatis中有很多的TypeHandler的实现类,那边它是如何知道何时该用哪个TypeHandler的呢?在MyBatis初始化的时候,就会将所有已知TypeHandler创建对象,并实现注册到TypeHandlerRegistry中,由它来管理这些对象。
/**
* 实现对众多TypeHandler的管理
*
* @author Clinton Begin
* @author Kazuki Shimizu
*/
public final class TypeHandlerRegistry {
/**
* 记录JdbcType与TypeHandler之间的对应管理
*/
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
/**
* 记录Java类型与JdbcType之间的对应管理,可能会有多个例如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;
1.注册TypeHandler对象
TypeHandlerRegister中有很多重载的register()方法。但多数的都调用了register(Type javaType, JdbcType jdbcType, TypeHandler<?> typeHandler)
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
// 检查是否明确指定了TypeHandler能够处理的Java类型
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<>();
}
map.put(jdbcType, handler);
// 将TypeHandler对象注册到typeHandlerMap中
typeHandlerMap.put(javaType, map);
}
// 向allTypeHandlersMap集合注册TypeHandler类型和对应TypeHandler对象
allTypeHandlersMap.put(handler.getClass(), handler);
}
部分的register()haiihui 读取@MappedTypes注解和@MappedJdbcType注解。都是为了向typeHandlerMap、allTypeHandlersMap集合注册TypeHandler对象。同时还可以扫描指定包下的TypeHandler实现类并完成注册。
2.查找TypeHandler
getTypeHandler()实现了获取对应TypeHandler对象的功能。会根据指定的java类型活着jdbc类型查找想要的TypeHandler对象。还有getJdbcTypeHandlerMap()方法。
3.TypeAliasRegistry
为一个类添加一个别名,通过TypeAliasRegistry完成别名注册和管理的功能,TypeAliasRegistry的结构比较简单通过一个Map类型来管理别名与Java类型之间的对应关系,通过registerAlias()方法完成注册别名。
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 (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);
}
默认为Java的基本数据类型及数字类型、基本类型的封装及其数组类型、Date等
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);
}
registerAliases()还会扫描指定包下所有的类
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) {
// 过滤内部类、接口及抽象类
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
registerAlias()还会解析@Alias注解,来注册java类的别名
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);
}