前言
开始分析Type包前,说明下使用场景。数据构建语句使用PreparedStatement,需要输入的是jdbc类型,但我们一般写的是java类型。同理,数据库结果集返回的是jdbc类型,而我们需要java类型。这就涉及到一个类型转换问题,Type包就是解决这个问题。下面是Type包类图所在结构:
源码解析
1. BaseTypeHandle
mybatis中的默认类型处理器,自定义类型处理器都继承自BaseTypeHandle。是分析类型处理器的关键,查看其类图如下:
分析BaseTypeHandle
// 类型处理器接口,查询参数时将java类型转为jdbc类型。获取结果时将jdbc类型转问java类型。 public interface TypeHandler{ // 设置查询参数,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
public abstract class TypeReference{ private final Type rawType; // 保存所处理的java原生类型、个人理解即T泛型的类型。 protected TypeReference() { rawType = getSuperclassTypeParameter(getClass()); } // rawType的获取过程。任务类型处理器都需要继承BaseTypeHandle ,而BaseTypeHandle // 至于为什么使用这一变量,因为我们自定义类型处理器可以不指定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继承TypeReference ,此处为了获取T的java类型。 ,还有泛型T。就再次获取T的java类型。 rawType = ((ParameterizedType) rawType).getRawType(); } return rawType; } public final Type getRawType() { return rawType; // 返回解析的rawType } ..... }
TypeReference
接下来分析BaseTypeHandle
// 类型处理器基类,增加了输入参数为null时的处理 public abstract class BaseTypeHandlerextends TypeReference implements TypeHandler { 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
public class DateTypeHandler extends BaseTypeHandler{ // 日期转换处理器,泛型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>> reversePrimitiveMap = new HashMap , 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 > JDBC_TYPE_HANDLER_MAP = new EnumMap >(JdbcType.class); // java类型处理器map集合,由此可知,一个java类型可以对应对个 jdbc类型 private final Map >> TYPE_HANDLER_MAP = new HashMap >>(); // 默认的未知类型处理器 private final TypeHandler
分析完具体属性后,看具体注册方法。TypeHandleRegistry支持的注册方法有多种,重写的register()方法就有很多种。一般注册我们需要提供转换的java类型和jdbc类型,具体的转换器。mybatis除了支持这种默认的注册方式外,还提供了如java类型+处理器,jdbc类型+处理器,单个处理器四种处理方法,一个个分析。
2.1 java类型+jdbc类型+处理器方法
// java type + jdbc type + handler publicvoid register(Class 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 > map = TYPE_HANDLER_MAP.get(javaType); if (map == null) { // 若为空,则建立一个,并将此 jdbc+handle 集合放入TYPE_HANDLER_MAP中 map = new HashMap >(); 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类型+处理器方法
publicvoid register(Class javaType, TypeHandler extends T> typeHandler) { register((Type) javaType, typeHandler); } private 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") publicvoid register(TypeHandler 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 。前文分析了,任何一个类型注册器都会继承TypeReference if (!mappedTypeFound && typeHandler instanceof TypeReference) { try { TypeReference,所以后面的判断条件会为true typeReference = (TypeReference ) typeHandler; register(typeReference.getRawType(), typeHandler); // 调用typeReference .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) null, typeHandler); // 调用java类型+处理器类型的注册方法 } }
此方法一般用于用户注册自定义处理器。综上分析,用户自己实现BaseTypeHandle
总结
对于类型处理器这块,总体来说比较简单,分析起来较轻松。本来还打算写一篇事务包的分析,但发现mybatis其实自身没有实现任务事务的操作。仅是对数据库本身事务的简单封装。接下来准备开始分析session,execute数据库执行过程了,还得慢慢来。如觉得还可以,还请看官们点个赞支持下。