原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6709157.html
1、回顾
上一篇研究的是类型别名注册器TypeAliasRegister,它主要用于将基本类型和用户自定义的类型进行别名注册,将别名及其对应类类型保存在一个HashMap中,方便存取,是映射器映射功能实现的基础,本篇所研究的类型处理器注册器TypeHandlerReister是用来统筹管理类型处理器的,类型处理器是真正用于进行java类型与数据库类型映射的工具。
这一篇我们还是重点研究类型处理器的注册器,有关具体类型处理器的研究放到之后进行。
2、类型处理器
为了研究类型处理器注册器,我们需要对类型处理器有一定的基础和认识,这里简单介绍一下,具体内容可等下一篇。
类型处理器简单点说就是用于处理javaType与jdbcType之间类型转换用的处理器,MyBatis针对诸多Java类型与数据库类型进行了匹配处理。
它主要用于映射器配置文件的工作,在通过类型别名注册器获取类型别名代表的类型之后,就可以使用获取的类型通过类型处理器注册器来得到其对应的JdbcType和对应的类型处理器。
由此可见每个类型处理器都针对两个类型,一个Java类型,一个数据库类型。而类型处理器的作用就是进行二者之间的匹配、对应、转换。
3、类型处理器注册器
类型处理器注册器既能完成类型处理器的注册功能,同时也能对类型处理器进行统筹管理,其内部定义了集合来进行类型处理器的存取,同时定义了存取方法。
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
以上是TypeHandlerRegister中定义的三个Map集合,这三个集合是用来保存类型处理器的注册信息的。
第一种:JDBC_TYPE_HANDLER_MAP,这是一个枚举Map集合,其内部是以JdbcType枚举类中枚举值为键创建的一种集合,这种集合先天存在键(枚举值),它是以数据库类型为键来保存类型处理器,亦即将类型处理器注册到对应的数据库类型上。
第二种:TYPE_HANDLER_MAP,这是一个前套Map集合,内层集合是以数据库类型为键保存处理器,外层集合为以Java类型来保存对应的数据库类型及其处理器,这个集合将三者联系起来,是真正进行三者对应关系匹配的集合。
第三种:ALL_TYPE_HANDLERS_MAP,这个集合中保存着所有的类型处理器,是以类型处理器的类类型为键值保存的,它可以统筹所有的类型处理器(带有统计的效果)。
3.1 基础类型处理器
在创建类型处理器注册器的时候,在其无参构造器中会进行基础类型处理器的注册,这些注册包括三种方式,一种是将以JavaType为键的保存方式,一种是以JdbcTye为键的保存方式,还有一种就是以JavaType与JdbcType为键的保存方式,这最后一种保存方式是一种嵌套的Map集合,前面的两种只是简单的Map集合。
下面将该无参构造器源码罗列:
//构造函数里注册系统内置的类型处理器
public TypeHandlerRegistry() {
//以下是为多个类型注册到同一个handler
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()); register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler()); register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler()); register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler()); register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler()); register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler()); //以下是为同一个类型的多种变种注册到多个不同的handler
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler()); register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler()); register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler()); register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler()); register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler()); register(Object.class, UNKNOWN_TYPE_HANDLER);
register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler()); register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler()); // issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
通过源码可以发现:MyBatis内置注册的类型处理器很是全面,几乎囊括了所有常用的类型,所以一般情况下我们直接使用内置的类型处理器进行类型处理即可。
3.1.1 针对JavaType,MyBatis内置注册了一下类型:
序号 | javaType | JdbcType | TypeHandler | 入口 | 说明 |
1 | Boolean.class | null | BooleanTypeHandler | 入口2 | |
2 | boolean.class | null | BooleanTypeHandler | ||
3 | Byte.class | null | ByteTypeHandler | ||
4 | byte.class | null | ByteTypeHandler | ||
5 | Short.class | null | ShortTypeHandler | ||
6 | short.class | null | ShortTypeHandler | ||
7 | Integer.class | null | IntegerTypeHandler | ||
8 | int.class | null | IntegerTypeHandler | ||
9 | Long.class | null | LongTypeHandler | ||
10 | long.class | null | LongTypeHandler | ||
11 | Float.class | null | FloatTypeHandler | ||
12 | float.class | null | FloatTypeHandler | ||
13 | Double.class | null | DoubleTypeHandler | ||
14 | double.class | null | DoubleTypeHandler | ||
15 | String.class | null | StringTypeHandler | ||
16 | BigDecimal.class | null | BigDecimalTypeHandler | ||
17 | BigInteger.class | null | BigIntegerTypeHandler | ||
18 | Byte[].class | null | ByteObjectArrayTypeHandler | ||
19 | byte[].class | null | ByteArrayTypeHandler | ||
20 | Object.class | null | UNKNOWN_TYPE_HANDLER | ||
21 | Date.class | null | DateTypeHandler | ||
22 | java.sql.Date.class | null | SqlDateTypeHandler | ||
23 | java.sql.Time.class | null | SqlTimeTypeHandler | ||
24 | Character.class | null | CharacterTypeHandler | ||
25 | char.class | null | CharacterTypeHandler | ||
26 | java.sql.Timestamp.class | null | SqlTimestampTypeHandler |
3.1.2 针对JdbcType,MyBatis内置注册了一下类型:
序号 | javaType | JdbcType | TypeHandler | 入口 | 说明 |
1 | JdbcType.BOOLEAN | BooleanTypeHandler | 入口1 | ||
2 | JdbcType.BIT | BooleanTypeHandler | |||
3 | JdbcType.TINYINT | ByteTypeHandler | |||
4 | JdbcType.SMALLINT | ShortTypeHandler | |||
5 | JdbcType.INTEGER | IntegerTypeHandler | |||
6 | JdbcType.FLOAT | FloatTypeHandler | |||
7 | JdbcType.DOUBLE | DoubleTypeHandler | |||
8 | JdbcType.CHAR | StringTypeHandler | |||
9 | JdbcType.VARCHAR | StringTypeHandler | |||
10 | JdbcType.CLOB | ClobTypeHandler | |||
11 | JdbcType.LONGVARCHAR | ClobTypeHandler | |||
12 | JdbcType.NVARCHAR | NStringTypeHandler | |||
13 | JdbcType.NCHAR | NStringTypeHandler | |||
14 | JdbcType.NCLOB | NClobTypeHandler | |||
15 | dbcType.ARRAY | ArrayTypeHandler | |||
16 | JdbcType.BIGINT | LongTypeHandler | |||
17 | JdbcType.REAL | BigDecimalTypeHandler | |||
18 | JdbcType.DECIMAL | BigDecimalTypeHandler | |||
19 | JdbcType.NUMERIC | BigDecimalTypeHandler | |||
20 | JdbcType.LONGVARBINARY | BlobTypeHandler | |||
21 | JdbcType.BLOB | BlobTypeHandler | |||
22 | JdbcType.OTHER | UNKNOWN_TYPE_HANDLER | |||
23 | JdbcType.TIMESTAMP | DateTypeHandler | |||
24 | JdbcType.DATE | DateOnlyTypeHandler | |||
25 | JdbcType.TIME | TimeOnlyTypeHandler |
3.1.3 针对JdbcType和JavaType,MyBatis内置注册了一下类型:
序号 | JavaType | JdbcType | TypeHandler | 入口 | 说明 |
1 | Date.class | JdbcType.DATE | DateOnlyTypeHandler | 入口3 | |
2 | Date.class | JdbcType.TIME | TimeOnlyTypeHandler | ||
3 | Object.class | JdbcType.OTHER | UNKNOWN_TYPE_HANDLER | ||
4 | byte[].class | JdbcType.BLOB | BlobTypeHandler | ||
5 | byte[].class | JdbcType.LONGVARBINARY | BlobTypeHandler | ||
6 | Byte[].class | JdbcType.BLOB | BlobByteObjectArrayTypeHandler | ||
7 | Byte[].class | JdbcType.LONGVARBINARY | BlobByteObjectArrayTypeHandler | ||
8 | String.class | JdbcType.CHAR | StringTypeHandler | ||
9 | String.class | JdbcType.CLOB | ClobTypeHandler | ||
10 | String.class | JdbcType.VARCHAR | StringTypeHandler | ||
11 | String.class | JdbcType.LONGVARCHAR | ClobTypeHandler | ||
12 | String.class | JdbcType.NVARCHAR | NStringTypeHandler | ||
13 | String.class | JdbcType.NCHAR | NStringTypeHandler | ||
14 | String.class | JdbcType.NCLOB | NClobTypeHandler |
3.2 注册入口方法
通过观察源码我们也可以发现这三种注册方式,我在这里将这三种方式的register方法看做三个入口,分别起名为:入口1、入口2、入口3。
其中:
入口1:对应之前介绍的第一种集合(枚举集合),其入口方法为:
//入口1
public void register(JdbcType jdbcType, TypeHandler<?> handler) {
JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
}
该入口方法用于将类型处理器注册到对应的数据库类型。
入口2:对应之前介绍的第二种嵌套集合(其中内层集合的键为null),其入口方法为:
//入口2
public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
register((Type) javaType, typeHandler);
}
该入口方法用于将类型处理器注册到对应的Java类型。
入口3:对应之前介绍的第二种嵌套集合,其入口方法为:
//入口3
public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
register((Type) type, jdbcType, handler);
}
该入口方法用于将类型处理器与Java类型和数据库类型联系起来。
上述的三个入口方法均是对内而设的入口方法,也就是说是用于注册器内部基础类型处理器(MyBatis内置的类型处理器)注册使用的。而MyBatis还提供了自定义类型处理器的功能,也就是说在该类中还提供了对外的自定义类型处理器注册入口。
这么理解:对内就是该方法被类内部调用进行注册,对外就是该方法被类外部的其他类进行调用而进行注册,这里的其他类其实就是XMLConfigBuilder类,它在构建Configuration对象时就会调用对外的注册方法,来将用户自定义的类型处理器注册到注册器中。
对外入口1:只指定包名的情况下,这种情况一般需要配合注解@MappedTypes使用,使用该注解进行JavaType的设置(即注解的value值)
//对外入口1:扫描器
public void register(String packageName) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
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())) {
register(type);
}
}
}
这个入口是一个扫描器,它会使用ResolverUtil工具类在给定包名下扫描所有类,然后进行循环注册,注册之前会进行排除操作,将内部类、接口、抽象类排除在外。这个扫描器针对的就是我们自定义的类型处理器进行注册,这个入口方法会在构建Configuration配置类时由XMLConfigBuilder进行调用,用于将用户自定义的类型处理器注册到注册器中。
上面的入口是在指定包名的情况下进行包扫描来获取包下所有类来进行类型处理器注册,一般会配合注解一起使用,但是如果不配合注解也能成功。如果配合注解指定JavaType,那么它将与对外入口2的情况一致(指定JavaType与TypeHandler),如果没有配合注解,那么就只有TypeHandler,这时候会调用另外一个注册方法,在这个方法中会再次验证是否存在注解,不存在的话,那么验证获取的类是否是TypeReference接口的实现类,如果是其实现类,说明这个类是一个类型处理器,那么再次调用另外一个注册方法,以该类型处理器的原生类型为参数进行调用,在这个方法中需要查询该类型处理器是否有注解@MappedJdbcTypes来指定JdbcType,如果有则以此JdbcType值为数据库类型,如果没有或者是Null类型,则直接将JdbcType置null再调用核心注册方法,将该类型处理器注册到TYPE_HANDLER_MAP集合中,最后还有将该注册器注册到ALL_TYPE_HANDLERS_MAP中用于统一管理。
对外入口2:指定Java类型与类型处理器的情况
//对外入口2
public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
}
对于第二种对外入口其实在第一种的情况中已经有所描述,这个方法会调用另外一个注册方法,来使用@MappedJdbcTypes获取jdbcType类型,其余步骤同上。
对外入口3:指定JavaType、JdbcType、TypeHandler三者的情况
//对外入口3
public void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) {
register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass));
}
这种情况可以直接调用核心注册进行注册即可。
对外入口4:只指定TypeHandler的情况
//对外入口4
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> javaTypeClass : mappedTypes.value()) {
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
register(getInstance(null, typeHandlerClass));
}
}
这种情况下,需要先验证是否有@MappedType指定JavaType,再验证是否有@MappedJdbcType指定JdbcType,分各种情况进行考虑,这在第一个入口方法中已经描述。
3.3 核心注册方法
虽然拥有诸多对内对外的注册入口方法,但是几乎都会指向核心注册方法,只有对内入口1不会指向核心注册方法,因为第一种对内入口方法的执行效果是往枚举集合JDBC_TYPE_HANDLER_MAP中注册数据库类型处理器。这与其他的注册情况不同,一般我们的注册是指往TYPE_HANDLER_MAP嵌套集合和ALL_TYPE_HANDLERS_MAP集合中注册类型处理器。
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null) {
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
map.put(jdbcType, handler);
}
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
然我来总述一下注册的过程,对上面的情况作何总结:
注册就是要三者兼备,哪三者:javaType、JdbcType、TypeHandler三者兼备,针对自定义类型处理器而言,我们可以通过继承BaseTypeHandler抽象类或者实现TypeHandler接口的方式来进行类型处理器的自定义实现。但是为了使其能在MyBatis中发挥作用,我们要将其注册到类型处理器注册器中。通过简单的配置即可实现,配置方式有两种:
<typeHandlers>
<package name="com.xx.xx"/>
<typeHandler handler="com.xx.xx.XxxTypeHandler" javaType="xxx" jdbcType="JdbcType.xxx" />
</typeHandlers>
若是用第二种方式配置即可直接进行注册,但是有时我们会省去javaType设置,而使用@MappedTypes注解来指定多个JavaType,或者省去JdbcType配置,采用@MappedJdbcTypes注解来指定多个jdbcType(毕竟配置文件只能指定一个,当需要设置多个时,就只能采用注解的方式实现),这时就需要查询目标处理器类的注解来获取类型,如果既没有在配置文件中配置,也没有通过注解配置,那么就只能置为null(这种情况毕竟,少见,一般我们要自定义类型处理器,必定是有某种类型处理器处理的不满意,我们肯定会指定对应的Java类型与数据库类型,如果听之任之的话我们又何必多此一举呢?)
极端情况就是采用包名配置或者只指定处理器类型进行注册,这时需要逐步查看类型处理器类的注解配置来获取该处理器处理的Java类型与数据库类型,最后在双方都获取到的情况下,三者齐备,调用核心注册方法,将这个类型处理器注册到TYPE_HANDLER_MAP嵌套集合和ALL_TYPE_HANDLERS_MAP集合中。