本文将从以下几个方面进行介绍
相关文章
前言
类型处理器
类型注册器
别名注册器
相关文章
前言
JDBC 提供的数据类型和Java的数据类型并不是完全对应的,当 Mybatis 在解析 SQL ,使用 PreparedStatement 来为 SQL 设置参数的时候,需要从 Java 类型转换为 JDBC 的类型,当从 ResultSet 中获取结果的时候,需要中 JDBC 类型转换为 Java 类型;Mybatis 的类型转换模块就是用来转换这两种数据类型的;比如在写 Mapper 文件的时候,可以有如下写法:
INSERT INTO user(id, name, age, height) VALUES ( #{id}, #{name, javaType=String, jdbcType=varchar}, #{age, javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler}, #{height, javaType=double, jdbcType=NUMERIC, numericScale=2} )
可以指定Java和数据库对应的类型,还可以指定自定义的类型处理器等,在 Mybatis 在解析 SQL 的时候,会通过类型转换处理器进行相应的转换
源码分析
Mybatis 的类型转换相关的代码主要在 type 包下,如下所示:
当然,type 包下不只是这些类,还有其他的一个内置的类型转换处理器,如 ArrayTypeHandler 等,还有三个类型的注册类 TypeHandlerRegistry,SimpleTypeRegistry 和 TypeAliasRegistry ,此外该包下还定义了一些注解等。
TypeHandler 接口
Mybatis 中所有的类型转换器都实现了 TypeHandler 接口,该接口下只有四个方法,共分为两类,一类是将 JDBC 类型转换为 Java 类型,一类是将 Java 类型转换为 JDBC 类型,源码如下:
public interface TypeHandler{ // 通过 PreparedStatement 绑定参数时,参数由 Jdbc 类型转换为 Java 类型 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; // 从 ResultSet 获取数据时,数据由 Java 类型转换为 Jdbc类型 T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException;}
TypeReference 抽象类
TypeReference 是一个抽象类,它表示引用一个泛型类型:
public abstract class TypeReference{ // 原始类型Type private final Type rawType; // 构造,获取原始类型 protected TypeReference() { rawType = getSuperclassTypeParameter(getClass()); } // 获取原始来下 Type getSuperclassTypeParameter(Class clazz) { // 获得带有泛型的父类 Type genericSuperclass = clazz.getGenericSuperclass(); if (genericSuperclass instanceof Class) { if (TypeReference.class != genericSuperclass) { return getSuperclassTypeParameter(clazz.getSuperclass()); } // 抛异常 } // 获取到泛型中的原始类型 // ParameterizedType是一个记录类型泛型的接口 // getActualTypeArguments():回泛型类型数组 Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; if (rawType instanceof ParameterizedType) { rawType = ((ParameterizedType) rawType).getRawType(); } // 返回原始类型 return rawType; } // 返回原始类型 public final Type getRawType() { return rawType; }}
BaseTypeHandler
在 Mybatis 中,提供了 TypeHandler 接口的唯一实现,即 BaseTypeHandler ,主要是为了方便用户的自定义实现 TypeHandler 接口。
在 BaseTypeHandler 抽象类中,实现了 TypeHandler 的 setParameter() 和 getResult() 方法,在这两个方法内部,对于非空数据的处理,由具体的子类进行实现;源码如下:
public abstract class BaseTypeHandlerextends TypeReference implements TypeHandler { // 表示一个配置文件类 protected Configuration configuration; public void setConfiguration(Configuration c) { this.configuration = c; } // 为 SQL 设置参数 @Override 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(e); } } else { try { // 如果参数不为空,则由子类实现,该方法是一个抽象方法 setNonNullParameter(ps, i, parameter, jdbcType); } catch (Exception e) { throw new TypeException(e); } } } // 从结果集中根据列名获取数据 @Override public T getResult(ResultSet rs, String columnName) throws SQLException { T result; try { // 获取结果,由子类实现 result = getNullableResult(rs, columnName); } catch (Exception e) { throw new ResultMapException( e); } // 如果为空,则返回 null if (rs.wasNull()) { return null; } else { return result; } } // 从结果集中根据列索引获取数据 @Override public T getResult(ResultSet rs, int columnIndex) throws SQLException { // 同上 } @Override public T getResult(CallableStatement cs, int columnIndex) throws SQLException { // 同上 } // 为 SQL 设置非空的参数,由各个子类自己实现 public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; // 获取结果,由各个子类自己实现 public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException; public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException; public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;}
StringTypeHandler
在上面的 BaseTypeHandler 抽象类中,为 SQL 设置参数和或结果集中获取数据,相应的都交由子类去实现,它大概有 31 个实现类,现在以StringTypeHandler 为例,看下它是怎么实现的;
public class StringTypeHandler extends BaseTypeHandler{ @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); }}
可以看到,String 类型的类型处理器会调用 PreparedStatement 的 setString 方法绑定参数,调用 ResultSet 的 getString 获取结果,其他的实现类大概都如此。
TypeHandlerRegistry
在 Mybatis 初始化的时候,会为所有已知的类型处理器 TypeHandler 创建对象,并注册到 TypeHandlerRegistry 中,由 TypeHandlerRegistry 来管理这些对象,接下来看下 TypeHandlerRegistry 的源码:
在 TypeHandlerRegistry 中定义了几个 Map 集合来存放相应的 TypeHandler 对象,如下所示:
// Jdbc 类型和类型处理器 TypeHandler 的对应关系// 该集合主要用于从结果集中读取数据时,从 Jdbc 类型转换为 Java 类型private final Map> JDBC_TYPE_HANDLER_MAP = new EnumMap >(JdbcType.class);// Java类型和Jdbc类型的对应关系,当Java类型向指定的Jdbc类型转换时,需要使用的 TypeHandler 对象// 一种Java类型可以对应多种Jdbc 类型,如 String 对应 char 和 varcharprivate final Map >> TYPE_HANDLER_MAP = new ConcurrentHashMap >>();// 为知类型,当找不到对应类型时,使用该类型,也就是 ObjectTypeHandlerprivate final TypeHandler
注册 TypeHandler
在创建该对象的时候,会对这些类型处理器进行注册:
public TypeHandlerRegistry() { // 多种Java类型可以对应一种类型处理器 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()); // 一种 Java 类型可以对应多种处理器 register(Date.class, new DateTypeHandler()); register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler()); register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler()); // ......... 注册其他类型........}
接下来看下这些类型处理器是如何注册的,即把相应的类型处理器存放到 上述定义的几个 Map 中去,在 TypeHandlerRegistry 中定义了 12 个重载的 register() 方法进行注册,下面看下几个主要的方法实现:
// 注册 Jdbc 类型和相应的处理器 public void register(JdbcType jdbcType, TypeHandler handler) { JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler); } // 注册 Java 类型和相应的处理器 privatevoid register(Type javaType, TypeHandler typeHandler) { // 处理注解的情况 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); } } // 根据 Java 类型 Jdbc 类型和类型处理器进行相应的注册 private void register(Type javaType, JdbcType jdbcType, TypeHandler handler) { if (javaType != null) { // 根据 Java 类型获取对应的 Jdbc 类型 Map > map = TYPE_HANDLER_MAP.get(javaType); // 如果为空,则新建一个 if (map == null) { map = new HashMap >(); TYPE_HANDLER_MAP.put(javaType, map); } // 注册 map.put(jdbcType, handler); } // 同时注册类型处理器类和对象的对应关系 ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); }
当注册完这些类型处理器对象后,如何去查找相应的类型处理器呢,TypeHandlerRegistry 也提供了相应的方法来进行查找,提供了 6 个重载的 getTypeHandler 方法,根据 Java 类型和 Jdbc 类型查找对应的 TypeHandler 对象:
// 查找或初始化 Java 类型对应的 TypeHandler 集合 privateTypeHandler getTypeHandler(Type type, JdbcType jdbcType) { // 根据 Java 类型查找对应的 TypeHandler 集合 Map > 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); } } return (TypeHandler ) handler; } // 根据 Java 类型查找对应的 TypeHandler 集合 private Map > getJdbcHandlerMap(Type type) { // 去 TYPE_HANDLER_MAP 进行查找 Map > 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; // 查找父类对应的 TypeHandler 集合,并作为初始集合 jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz); if (jdbcHandlerMap != null) { TYPE_HANDLER_MAP.put(type, jdbcHandlerMap); } else if (clazz.isEnum()) { // 枚举类的处理,进行注册 register(clazz, new EnumTypeHandler(clazz)); return TYPE_HANDLER_MAP.get(clazz); } } if (jdbcHandlerMap == null) { TYPE_HANDLER_MAP.put(type, NULL_TYPE_HANDLER_MAP); } return jdbcHandlerMap; }
上述就是类型注册器 TypeHandlerRegistry 的一个实现过程。
TypeAliasRegistry
在编写 Mapper SQL 的时候,可以使用别名,比如,
然后的解析 SQL 的时候,就可以获取对应的类型,如 Java.util.Map, Java.lang.Integer 等。Mybatis 通过 TypeAliasRegistry 来完成别名的注册和管理功能。
该方法比较简单,它提供了 5 个重载的 registerAlias 方法来进行别名的注册,提供一个方法 resolveAlias 来解析别名,最后在构造方法中进行别名的注册:
private final Map> TYPE_ALIASES = new HashMap >(); // 注册别名 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.put(key, value); } 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); }
解析别名:
// 解析别名 publicClass resolveAlias(String string) { try { if (string == null) { return null; } // 别名转换为小写,因为在注册的时候,转换过 String key = string.toLowerCase(Locale.ENGLISH); Class value; // 如果该别名已经注册,则获取对应的类型 if (TYPE_ALIASES.containsKey(key)) { value = (Class ) TYPE_ALIASES.get(key); } else { // 尝试使用反射来获取类型 value = (Class ) Resources.classForName(string); } // 返回对应的类型 return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); } }
注册别名,接下来就是进行别名的注册,通过构造方法进行注册
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); }
以上就是 Mybatis 进行类型转换的一个主要代码逻辑,还是挺好理解的。