【Mybatis】【配置文件解析】【三】Mybatis源码解析-typeHandler、objectWrapperFactory
1 前言
这节我们继续我们的配置文件分析,本节我们看看typeHandler和objectWrapperFactory的解析。
2 源码分析
2.1 解析typeHandlers
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
类型处理器 | Java 类型 | JDBC 类型 |
---|---|---|
BooleanTypeHandler |
java.lang.Boolean , boolean |
数据库兼容的 BOOLEAN |
ByteTypeHandler |
java.lang.Byte , byte |
数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler |
java.lang.Short , short |
数据库兼容的 NUMERIC 或 SMALLINT |
IntegerTypeHandler |
java.lang.Integer , int |
数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler |
java.lang.Long , long |
数据库兼容的 NUMERIC 或 BIGINT |
FloatTypeHandler |
java.lang.Float , float |
数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler |
java.lang.Double , double |
数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler |
java.math.BigDecimal |
数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler |
java.lang.String |
CHAR , VARCHAR |
ClobReaderTypeHandler |
java.io.Reader |
- |
ClobTypeHandler |
java.lang.String |
CLOB , LONGVARCHAR |
NStringTypeHandler |
java.lang.String |
NVARCHAR , NCHAR |
NClobTypeHandler |
java.lang.String |
NCLOB |
BlobInputStreamTypeHandler |
java.io.InputStream |
- |
ByteArrayTypeHandler |
byte[] |
数据库兼容的字节流类型 |
BlobTypeHandler |
byte[] |
BLOB , LONGVARBINARY |
DateTypeHandler |
java.util.Date |
TIMESTAMP |
DateOnlyTypeHandler |
java.util.Date |
DATE |
TimeOnlyTypeHandler |
java.util.Date |
TIME |
SqlTimestampTypeHandler |
java.sql.Timestamp |
TIMESTAMP |
SqlDateTypeHandler |
java.sql.Date |
DATE |
SqlTimeTypeHandler |
java.sql.Time |
TIME |
ObjectTypeHandler |
Any | OTHER 或未指定类型 |
EnumTypeHandler |
Enumeration Type | VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值) |
EnumOrdinalTypeHandler |
Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。 |
SqlxmlTypeHandler |
java.lang.String |
SQLXML |
InstantTypeHandler |
java.time.Instant |
TIMESTAMP |
LocalDateTimeTypeHandler |
java.time.LocalDateTime |
TIMESTAMP |
LocalDateTypeHandler |
java.time.LocalDate |
DATE |
LocalTimeTypeHandler |
java.time.LocalTime |
TIME |
OffsetDateTimeTypeHandler |
java.time.OffsetDateTime |
TIMESTAMP |
OffsetTimeTypeHandler |
java.time.OffsetTime |
TIME |
ZonedDateTimeTypeHandler |
java.time.ZonedDateTime |
TIMESTAMP |
YearTypeHandler |
java.time.Year |
INTEGER |
MonthTypeHandler |
java.time.Month |
INTEGER |
YearMonthTypeHandler |
java.time.YearMonth |
VARCHAR 或 LONGVARCHAR |
JapaneseDateTypeHandler |
java.time.chrono.JapaneseDate |
DATE |
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler
接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler
, 并且可以(可选地)将它映射到一个 JDBC 类型。
让我们来看下typeHandler的dtd约束:
<!ELEMENT typeHandlers (typeHandler*,package*)> <!ELEMENT typeHandler EMPTY> <!ATTLIST typeHandler javaType CDATA #IMPLIED jdbcType CDATA #IMPLIED handler CDATA #REQUIRED >
可以看见它分包扫描和配置的方式,包扫描也就是需要我们的注解应使用@MappedTypes
和@MappedJdbcTypes
注解配置javaType
和jdbcType。
那么让我们看下源码:
private void typeHandlerElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { // 包形式的解析 if ("package".equals(child.getName())) { String typeHandlerPackage = child.getStringAttribute("name"); // 获取到包的值 进行注册 typeHandlerRegistry.register(typeHandlerPackage); } else { /** * typeHandler 子标签的形式解析 * 解析出 javaType jdbcType handler的值 */ String javaTypeName = child.getStringAttribute("javaType"); String jdbcTypeName = child.getStringAttribute("jdbcType"); String handlerTypeName = child.getStringAttribute("handler"); // 转换成对应的类型 Class<?> javaTypeClass = resolveClass(javaTypeName); JdbcType jdbcType = resolveJdbcType(jdbcTypeName); Class<?> typeHandlerClass = resolveClass(handlerTypeName); // 根据 javaTypeClass 和 jdbcType 值的情况进行不同的注册策略 if (javaTypeClass != null) { if (jdbcType == null) { typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } else { typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); } } else { typeHandlerRegistry.register(typeHandlerClass); } } } } }
可以看到 typeHandlerRegistry 重载的方法就有4个里边还有互相调用衍生更多重载,一个个看进去迷糊了调来调去的画个图理一下:
我们就按图上的1、2、3、4进行一一的分析哈。
2.1.1 register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass)方法分析
当代码执行到此方法时,表示javaTypeClass != null && jdbcType != null
条件成立,即使用者明确配置了javaType
和jdbcType
属性的值。那下面我们来看一下该方法的分析。
public void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) { // 调用终点方法 register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass)); } 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) { if (javaType != null) { // JdbcType 到 TypeHandler 的映射 Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { map = new HashMap<>(); } map.put(jdbcType, handler); // 存储 javaType 到 Map<JdbcType, TypeHandler> 的映射 typeHandlerMap.put(javaType, map); } // 存储所有的 TypeHandler allTypeHandlersMap.put(handler.getClass(), handler); }
上面的代码经过三层调用调用,比较简单。同时,所谓的注册过程也就是把类型和处理器进行映射而已,没什么特别之处。关于这个方法就先分析到这里,继续往下分析。
2.1.2 register(Class<?> javaTypeClass, Class<?> typeHandlerClass)方法分析
当代码执行到此方法时,表示javaTypeClass != null && jdbcType == null
条件成立,即使用者仅设置了javaType
属性的值。下面我们来看一下该方法的分析。
public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) { // 调用重载方法 register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass)); } 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 注解 MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); if (mappedJdbcTypes != null) { // 遍历 @MappedJdbcTypes 注解中配置的值 for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { // 调用终点方法 register(javaType, handledJdbcType, typeHandler); } if (mappedJdbcTypes.includeNullJdbcType()) { // 调用终点方法,jdbcType = null register(javaType, null, typeHandler); } } else { // 调用终点方法,jdbcType = null register(javaType, null, typeHandler); }
上面的代码包含三层调用,其中终点方法的逻辑上一节已经分析过,这里不再赘述。上面的逻辑也比较简单,主要做的事情是尝试从注解中获取JdbcType
的值。这个方法就分析这么多,继续。
2.1.3 register(Class<?> typeHandlerClass)方法分析
当代码执行到此方法时,表示javaTypeClass == null && jdbcType != null
条件成立,即使用者未配置javaType
和jdbcType
属性的值。该方法的分析如下。
public void register(Class<?> typeHandlerClass) { boolean mappedTypeFound = false; // 获取 @MappedTypes 注解 MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { // 遍历 @MappedTypes 注解中配置的值 for (Class<?> javaTypeClass : mappedTypes.value()) { // 调用上面的第2个注册方法 register(javaTypeClass, typeHandlerClass); mappedTypeFound = true; } } if (!mappedTypeFound) { // 调用中间方法 register(TypeHandler) register(getInstance(null, typeHandlerClass)); } } public <T> void register(TypeHandler<T> typeHandler) { boolean mappedTypeFound = false; // 获取 @MappedTypes 注解 MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> handledType : mappedTypes.value()) { // 调用中间方法 register(Type, TypeHandler) register(handledType, typeHandler); mappedTypeFound = true; } } // 自动发现映射类型 // @since 3.1.0 - try to auto-discover the mapped type if (!mappedTypeFound && typeHandler instanceof TypeReference) { try { TypeReference<T> typeReference = (TypeReference<T>) typeHandler; // 获取参数模板中的参数类型,并调用中间方法 register(Type, 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, TypeHandler) register((Class<T>) null, typeHandler); } }
上面的代码比较多,不过不用太担心。不管是通过注解的方式,还是通过反射的方式,它们最终目的是为了解析出javaType
的值。解析完成后,这些方法会调用中间方法register(Type, TypeHandler)
,这个方法负责解析jdbcType
,该方法上一节已经分析过。一个负责解析 javaType,另一个负责解析 jdbcType,继续最后一个。
2.1.4 register(StringpackageName)方法分析
该方法主要是用于自动扫描类型处理器,并调用其他方法注册扫描结果,我们看源码:
public void register(String packageName) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); // 从指定包中查找 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())) { // 调用上边第3个注册方法 register(type); } } }
方法重载的多不,中间都是为了解析出数据值,最后都会划归到javaType、jdbcType、typeHandler的方法里,是不是。
2.2 解析objectWrapperFactory
关于objectWrapperFactory的解析也比较简单哈,我们简单看一下dtd约束和他的解析源码:
<!ELEMENT objectWrapperFactory EMPTY> <!ATTLIST objectWrapperFactory type CDATA #REQUIRED >
private void objectWrapperFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance(); configuration.setObjectWrapperFactory(factory); } }
3 小结
好了本节我们解析了typeHandler和objectWrapperFactory,我们还剩下一个很重要的,我单独拿一节来进行分析哈,有理解不对的地方欢迎指正哈。