myBatis源码解析-类型转换篇(5)
前言
开始分析Type包前,说明下使用场景。数据构建语句使用PreparedStatement,需要输入的是jdbc类型,但我们一般写的是java类型。同理,数据库结果集返回的是jdbc类型,而我们需要java类型。这就涉及到一个类型转换问题,Type包就是解决这个问题。下面是Type包类图所在结构:
源码解析
1. BaseTypeHandle<T> - 类型处理器实现的基类
mybatis中的默认类型处理器,自定义类型处理器都继承自BaseTypeHandle。是分析类型处理器的关键,查看其类图如下:
分析BaseTypeHandle<T>前,分析其接口TypeHandle。
// 类型处理器接口,查询参数时将java类型转为jdbc类型。获取结果时将jdbc类型转问java类型。 public interface TypeHandler<T> { // 设置查询参数,java类型转为jdbc类型 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; // 获取结果参数,jdbc转为java类型 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接口定义了参数转换的方法。查询时将java类型转为jdbc类型,获取结果时将jdbc类型转为java类型。
分析继承的抽象类,TypeReference<T>,主要是获取泛型T的原生类型。
public abstract class TypeReference<T> { private final Type rawType; // 保存所处理的java原生类型、个人理解即T泛型的类型。 protected TypeReference() { rawType = getSuperclassTypeParameter(getClass()); } // rawType的获取过程。任务类型处理器都需要继承BaseTypeHandle<T>,而BaseTypeHandle<T>继承TypeReference<T>,此处为了获取T的java类型。 // 至于为什么使用这一变量,因为我们自定义类型处理器可以不指定java类型,只指定jdbc类型,这样java类型默认就是T类型。 Type getSuperclassTypeParameter(Class<?> clazz) { Type genericSuperclass = clazz.getGenericSuperclass(); // 获取分类,包括T。此处和getSuperClass的区别是,getSuperClass只返回直接父类,并不包括父类带的泛型T if (genericSuperclass instanceof Class) { // 任何类型处理器都有泛型T,一直循环找,如果没找到,直接报错。 // try to climb up the hierarchy until meet something useful if (TypeReference.class != genericSuperclass) { return getSuperclassTypeParameter(clazz.getSuperclass()); } throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. " + "Remove the extension or add a type parameter to it."); } Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; // 获取泛型T的java类型 // TODO remove this when Reflector is fixed to return Types if (rawType instanceof ParameterizedType) { // ?自己debug没有进入到这一步,保留。个人理解是万一还是类型嵌套模式如user<T>,还有泛型T。就再次获取T的java类型。 rawType = ((ParameterizedType) rawType).getRawType(); } return rawType; } public final Type getRawType() { return rawType; // 返回解析的rawType } ..... }
TypeReference<T>类提供了获取rawType的方法。对于我们自定义类型转换器,可以只输入要转换的jdbc类型,那默认的待转换java类型就是rawType类型。
接下来分析BaseTypeHandle<T>,也是一个抽象类,查看其源码,主要实现了对输入参数,结果参数为空的处理,若不为空,则交给子类具体实现。
// 类型处理器基类,增加了输入参数为null时的处理 public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> { protected Configuration configuration; // 全局配置参数 public void setConfiguration(Configuration c) { this.configuration = c; } public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { // 对输入参数为null时的处理 if (jdbcType == null) { // jdbcType不能为空 throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } try { 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 { setNonNullParameter(ps, i, parameter, jdbcType); // 输入参数不为空的处理 } } ....... // 对非空参数需要子类实现。 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;
BaseTypeHandler<T>其实只对空数据进行处理,非空数据交给子类实现,此处使用了模板设计模式。查看具体的类型处理器,如DateTypeHandle进行验证。
public class DateTypeHandler extends BaseTypeHandler<Date> { // 日期转换处理器,泛型T为java.util.date @Override public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException { ps.setTimestamp(i, new Timestamp((parameter).getTime())); // 设置PreparedStatement,其实就是java类型转为数据库识别的jdbc类型 } @Override public Date getNullableResult(ResultSet rs, String columnName) throws SQLException { Timestamp sqlTimestamp = rs.getTimestamp(columnName); // 获取指定列的结果 if (sqlTimestamp != null) { return new Date(sqlTimestamp.getTime()); // 获取Date结果,jdbc类型转为java类型。 } return null; }
类型处理器实现也很简单,是将java数据类型和数据库识别的jdbc类型的相互转换。其余的类型处理器太多了,暂且不分析了,大家感兴趣的可以自己看下。
2. TypeHandleRegistry - 类型处理器注册类
类型转换处理器定义完毕后,需要有个仓库进行注册,后续使用直接去拿即可。TypeHandleRegistry就是处理器注册的地方。现在对TypeHandleRegistry源码进行分析。
private static final Map<Class<?>, Class<?>> reversePrimitiveMap = new HashMap<Class<?>, Class<?>>() { private static final long serialVersionUID = 1L; { put(Byte.class, byte.class); put(Short.class, short.class); put(Integer.class, int.class); put(Long.class, long.class); put(Float.class, float.class); put(Double.class, double.class); put(Boolean.class, boolean.class); put(Character.class, char.class); } }; // 数据库类型处理器map集合,jdbc类型为key,TypeHandle为value private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); // java类型处理器map集合,由此可知,一个java类型可以对应对个 jdbc类型 private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>(); // 默认的未知类型处理器 private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); // 所有类型的处理器 private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
分析完具体属性后,看具体注册方法。TypeHandleRegistry支持的注册方法有多种,重写的register()方法就有很多种。一般注册我们需要提供转换的java类型和jdbc类型,具体的转换器。mybatis除了支持这种默认的注册方式外,还提供了如java类型+处理器,jdbc类型+处理器,单个处理器四种处理方法,一个个分析。
2.1 java类型+jdbc类型+处理器方法
// java type + jdbc type + handler public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) { register((Type) type, jdbcType, handler); } private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { // 根据java类型,jdbc类型,typeHandle来注册 if (javaType != null) { // 若对应的java类型不为空 // 获取java类型对应的 jdbc+handle 集合 Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); if (map == null) { // 若为空,则建立一个,并将此 jdbc+handle 集合放入TYPE_HANDLER_MAP中 map = new HashMap<JdbcType, TypeHandler<?>>(); TYPE_HANDLER_MAP.put(javaType, map); } map.put(jdbcType, handler); // 如果当前的java类型是基本数据类型的包装类(Integer,Long等),则将其对应的基本数据类型(int,long等)也注册进去 if (reversePrimitiveMap.containsKey(javaType)) { register(reversePrimitiveMap.get(javaType), jdbcType, handler); } } ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); }
流程很简单,其余的注册方法基本会调用这个方法。流程如下:
2.2 java类型+处理器方法
public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) { register((Type) javaType, typeHandler); } private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) { // 获取类型处理器中@MappedJdbcTypes注解,该注解作用是定义类型处理器所处理的jdbc类型列表 MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); if (mappedJdbcTypes != null) { // 若没有定义,则默认为空 for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { // 一个java类型可对应多个jdbc类型。 register(javaType, handledJdbcType, typeHandler); } if (mappedJdbcTypes.includeNullJdbcType()) { register(javaType, null, typeHandler); } } else { register(javaType, null, typeHandler); } }
java类型+处理器类型其实也是调用java类型+jdbc类型+处理器注册方法。只是提供了一个获取@MappedJdbcTypes注解的功能。此注解用在自定义处理器类,用来获取处理器指定的jdbc类型。
2.3 jdbc类型+处理器方法
public void register(JdbcType jdbcType, TypeHandler<?> handler) { JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler); // 在map中添加条记录而已 }
jdbc类型+处理器方法实际上只是在jdbc与处理器关联的map中放了条记录。
2.4 只提供处理器
// Only handler @SuppressWarnings("unchecked") public <T> void register(TypeHandler<T> typeHandler) { boolean mappedTypeFound = false; // 获取java类型,我们在自定义类型处理器使用@MappedTypes注解来定义我们要转换的java类型 MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class); if (mappedTypes != null) { // 若定义了转换的java类型 for (Class<?> handledType : mappedTypes.value()) { // 则遍历java类型的列表,在调用java类型+处理器类型的注册方法 register(handledType, typeHandler); mappedTypeFound = true; // 找到了待转换的java类型 } } // @since 3.1.0 - try to auto-discover the mapped type // 若没找到待注册的java类型且继承了TypeReference<T>。前文分析了,任何一个类型注册器都会继承TypeReference<T>,所以后面的判断条件会为true if (!mappedTypeFound && typeHandler instanceof TypeReference) { try { TypeReference<T> typeReference = (TypeReference<T>) typeHandler; register(typeReference.getRawType(), typeHandler); // 调用typeReference<T>.getRawType()其实获取的是泛型T的类型 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); // 调用java类型+处理器类型的注册方法 } }
此方法一般用于用户注册自定义处理器。综上分析,用户自己实现BaseTypeHandle<T>时,可以使用@MappedTypes注解来指定要转换的java类型,若没有指定,则默认为TypeReference<T>泛型T的java类型。也可以使用@MappedJdbcTypes注解来指定要转换的jdbc类型,若没有指定,则默认为null。这也是为什么网上很多自定义类型处理器有的使用了注解,有的没有使用注解。此处,算是做了一个解释。另外还需注意的是,通过JDBC_TYPE_HANDLER_MAP这一属性可知,对于一个java类型,其实支持多种jdbc类型与之对应。
总结
对于类型处理器这块,总体来说比较简单,分析起来较轻松。本来还打算写一篇事务包的分析,但发现mybatis其实自身没有实现任务事务的操作。仅是对数据库本身事务的简单封装。接下来准备开始分析session,execute数据库执行过程了,还得慢慢来。如觉得还可以,还请看官们点个赞支持下。