网站开发用什么技术,网页微信怎么换行,建百度网站,海口网站建设平台文章目录 TypeHandler 接口TypeHandler 注册TypeHandler 查询别名管理总结 TypeHandler 接口
TypeHandler 这个接口 就是Mybatis的类型转换器
/*** author Clinton Begin*/
public interface TypeHandlerT {// 在通过PreparedStatement为SQL语句绑定参数时#xff0… 文章目录 TypeHandler 接口TypeHandler 注册TypeHandler 查询别名管理总结 TypeHandler 接口
TypeHandler 这个接口 就是Mybatis的类型转换器
/*** author Clinton Begin*/
public interface TypeHandlerT {// 在通过PreparedStatement为SQL语句绑定参数时会将传入的实参数据由JdbcType类型转换成Java类型void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;// 从ResultSet中获取数据时会使用getResult()方法其中会将读取到的数据由Java类型转换成JdbcType类型T getResult(ResultSet rs, String columnName) throws SQLException;T getResult(ResultSet rs, int columnIndex) throws SQLException;T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}MyBatis 中定义了 BaseTypeHandler 抽象类来实现一些 TypeHandler 的公共逻辑BaseTypeHandler 在实现 TypeHandler 的同时还实现了 TypeReference 抽象类。其继承关系如下图所示 在 BaseTypeHandler 中简单实现了 TypeHandler 接口的 setParameter() 方法和 getResult() 方法。
在 setParameter() 实现中会判断传入的 parameter 实参是否为空如果为空则调用 PreparedStatement.setNull() 方法进行设置如果不为空则委托 setNonNullParameter() 这个抽象方法进行处理setNonNullParameter() 方法由 BaseTypeHandler 的子类提供具体实现。在 getResult() 的三个重载实现中会直接调用相应的 getNullableResult() 抽象方法这里有三个重载的 getNullableResult() 抽象方法它们都由 BaseTypeHandler 的子类提供具体实现。
下图展示了 BaseTypeHandler 的全部实现类虽然实现类比较多但是它们的实现方式大同小异。 这里我们以 LongTypeHandler 为例进行分析具体实现如下
/*** author Clinton Begin*/
public class LongTypeHandler extends BaseTypeHandlerLong {Overridepublic void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType)throws SQLException {// 调用PreparedStatement.setLong()实现参数绑定ps.setLong(i, parameter);}Overridepublic Long getNullableResult(ResultSet rs, String columnName)throws SQLException {// 调用ResultSet.getLong()获取指定列值long result rs.getLong(columnName);return result 0 rs.wasNull() ? null : result;}Overridepublic Long getNullableResult(ResultSet rs, int columnIndex)throws SQLException {// 调用ResultSet.getLong()获取指定列值long result rs.getLong(columnIndex);return result 0 rs.wasNull() ? null : result;}Overridepublic Long getNullableResult(CallableStatement cs, int columnIndex)throws SQLException {// 调用ResultSet.getLong()获取指定列值long result cs.getLong(columnIndex);return result 0 cs.wasNull() ? null : result;}
}可以看到LongTypeHandler 的核心还是通过 PreparedStatement.setLong() 方法以及 ResultSet.getLong() 方法实现的。至于其他 BaseTypeHandler 的核心实现同样也是依赖了 JDBC 的 API这里就不再展开介绍了。
TypeHandler 注册
了解了 TypeHandler 接口实现类的核心原理之后我们就来解决下面两个问题
MyBatis 如何管理这么多的 TypeHandler 接口实现呢如何在合适的场景中使用合适的 TypeHandler 实现进行类型转换呢
你若使用过 MyBatis 的话应该知道我们可以在 mybatis-config.xml 中通过 标签配置自定义的 TypeHandler 实现也可以在 Mapper.xml 配置文件定义 的时候指定 typeHandler 属性。无论是哪种配置方式MyBatis 都会在初始化过程中获取所有已知的 TypeHandler包括内置实现和自定义实现然后创建所有 TypeHandler 实例并注册到 TypeHandlerRegistry 中由 TypeHandlerRegistry 统一管理所有 TypeHandler 实例。 TypeHandlerRegistry 管理 TypeHandler 的时候用到了以下四个最核心的集合。
jdbcTypeHandlerMapMap类型该集合记录了 JdbcType 与 TypeHandler 之间的关联关系。JdbcType 是一个枚举类型每个 JdbcType 枚举值对应一种 JDBC 类型例如JdbcType.VARCHAR 对应的就是 JDBC 中的 varchar 类型。在从 ResultSet 中读取数据的时候就会从 JDBC_TYPE_HANDLER_MAP 集合中根据 JDBC 类型查找对应的 TypeHandler将数据转换成 Java 类型。typeHandlerMapMap类型该集合第一层 Key 是需要转换的 Java 类型第二层 Key 是转换的目标 JdbcType最终的 Value 是完成此次转换时所需要使用的 TypeHandler 对象。那为什么要有两层 Map 的设计呢这里我们举个例子Java 类型中的 String 可能转换成数据库中的 varchar、char、text 等多种类型存在一对多关系所以就可能有不同的 TypeHandler 实现。allTypeHandlersMapMap类型该集合记录了全部 TypeHandler 的类型以及对应的 TypeHandler 实例对象。NULL_TYPE_HANDLER_MAPMap类型空 TypeHandler 集合的标识默认值为 Collections.emptyMap()。
在 MyBatis 初始化的时候实例化全部 TypeHandler 对象之后会立即调用 TypeHandlerRegistry 的 register() 方法完成这些 TypeHandler 对象的注册这个注册过程的核心逻辑就是向上述四个核心集合中添加 TypeHandler 实例以及与 Java 类型、JDBC 类型之间的映射。
TypeHandlerRegistry.register() 方法有多个重载实现这些重载中最基础的实现是三个参数的重载实现具体实现如下
private void register(Type javaType, JdbcType jdbcType, TypeHandler? handler) {if (javaType ! null) { // 检测是否明确指定了TypeHandler能够处理的Java类型// 根据指定的Java类型从typeHandlerMap集合中获取相应的TypeHandler集合MapJdbcType, TypeHandler? map typeHandlerMap.get(javaType);if (map null || map NULL_TYPE_HANDLER_MAP) {map new HashMap();}// 将TypeHandler实例记录到typeHandlerMap集合map.put(jdbcType, handler);typeHandlerMap.put(javaType, map);}// 向allTypeHandlersMap集合注册TypeHandler类型和对应的TypeHandler对象allTypeHandlersMap.put(handler.getClass(), handler);}除了上面的 register() 重载在有的 register() 重载中会尝试从 TypeHandler 类中的MappedTypes 注解和 MappedJdbcTypes 注解中读取信息。其中MappedTypes 注解中可以配置 TypeHandler 实现类能够处理的 Java 类型的集合MappedJdbcTypes 注解中可以配置该 TypeHandler 实现类能够处理的 JDBC 类型集合。
如下就是读取 MappedJdbcTypes 注解的 register() 重载方法 private T void register(Type javaType, TypeHandler? extends T typeHandler) {// 尝试从TypeHandler类中获取MappedJdbcTypes注解MappedJdbcTypes mappedJdbcTypes typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);if (mappedJdbcTypes ! null) {// 根据MappedJdbcTypes注解指定的JDBC类型进行注册for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {// 交给前面的三参数重载处理register(javaType, handledJdbcType, typeHandler);}// 如果支持jdbcType为null也是交给前面的三参数重载处理if (mappedJdbcTypes.includeNullJdbcType()) {register(javaType, null, typeHandler);}} else {// 如果没有配置MappedJdbcTypes注解也是交给前面的三参数重载处理register(javaType, null, typeHandler);}}下面是读取 MappedTypes 注解的 register() 方法重载
SuppressWarnings(unchecked)public T void register(TypeHandlerT typeHandler) {boolean mappedTypeFound false;// 读取TypeHandler类中定义的MappedTypes注解MappedTypes mappedTypes typeHandler.getClass().getAnnotation(MappedTypes.class);if (mappedTypes ! null) {// 根据MappedTypes注解中指定的Java类型进行注册for (Class? handledType : mappedTypes.value()) {// 交给前面介绍的register()重载读取MappedJdbcTypes注解并完成注册register(handledType, typeHandler);mappedTypeFound true;}}// 从3.1.0版本开始如果TypeHandler实现类同时继承了TypeReference这个抽象类// 这里会尝试自动查找对应的Java类型if (!mappedTypeFound typeHandler instanceof TypeReference) {try {TypeReferenceT typeReference (TypeReferenceT) typeHandler;// 交给前面介绍的register()重载读取MappedJdbcTypes注解并完成注册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((ClassT) null, typeHandler);}}我们接下来看最后一个 register() 重载。TypeHandlerRegistry 提供了扫描一个包下的全部 TypeHandler 接口实现类的 register() 重载。在该重载中会首先读取指定包下面的全部的 TypeHandler 实现类然后再交给 register() 重载读取 MappedTypes 注解和 MappedJdbcTypes 注解并最终完成注册。这个 register() 重载的具体实现比较简单这里就不再展示你若感兴趣的话可以参考源码进行学习。
最后我们再来看看 TypeHandlerRegistry 的构造方法其中会通过 register() 方法注册多个 TypeHandler 对象下面就展示了为 String 类型注册 TypeHandler 的核心实现
public TypeHandlerRegistry() {// StringTypeHandler可以实现String类型与char、varchar、longvarchar类型之间的转换register(String.class, JdbcType.CHAR, new StringTypeHandler());register(String.class, JdbcType.VARCHAR, new StringTypeHandler());register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());// ClobTypeHandler可以完成String类型与clob类型之间的转换register(String.class, JdbcType.CLOB, new ClobTypeHandler());// NStringTypeHandler可以完成String类型与NVARCHAR、NCHAR类型之间的转换register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());register(String.class, JdbcType.NCHAR, new NStringTypeHandler());// NClobTypeHandler可以完成String类型与NCLOB类型之间的转换register(String.class, JdbcType.NCLOB, new NClobTypeHandler());// 省略其他TypeHandler实现的注册逻辑}TypeHandler 查询
分析完注册 TypeHandler 实例的具体实现之后我们接下来就来看看 MyBatis 是如何从 TypeHandlerRegistry 底层的这几个集合中查找正确的 TypeHandler 实例该功能的具体实现是在 TypeHandlerRegistry 的 getTypeHandler() 方法中
这里的 getTypeHandler() 方法也有多个重载最核心的重载是 getTypeHandler(Type,JdbcType) 这个重载方法其中会根据传入的 Java 类型和 JDBC 类型从底层的几个集合中查询相应的 TypeHandler 实例具体实现如下
private T TypeHandlerT getTypeHandler(Type type, JdbcType jdbcType) {if (ParamMap.class.equals(type)) {return null; // 过滤掉ParamMap类型}// 根据Java类型查找对应的TypeHandler集合MapJdbcType, TypeHandler? jdbcHandlerMap getJdbcHandlerMap(type);TypeHandler? handler null;if (jdbcHandlerMap ! null) {// 根据JdbcType类型查找对应的TypeHandler实例handler jdbcHandlerMap.get(jdbcType);if (handler null) {// 没有对应的TypeHandler实例则使用null对应的TypeHandlerhandler jdbcHandlerMap.get(null);}if (handler null) {// 如果jdbcHandlerMap只注册了一个TypeHandler则使用此TypeHandler对象handler pickSoleHandler(jdbcHandlerMap);}}return (TypeHandlerT) handler;}在 getTypeHandler() 方法中会调用 getJdbcHandlerMap() 方法检测 typeHandlerMap 集合中相应的 TypeHandler 集合是否已经初始化
如果已初始化则直接使用该集合进行查询如果未初始化则尝试以传入的 Java 类型的、已初始化的父类对应的 TypeHandler 集合作为初始集合如果该 Java 类型的父类没有关联任何已初始化的 TypeHandler 集合则将该 Java 类型对应的 TypeHandler 集合初始化为 NULL_TYPE_HANDLER_MAP 标识。
getJdbcHandlerMap() 方法具体实现如下
private MapJdbcType, TypeHandler? getJdbcHandlerMap(Type type) {// 首先查找指定Java类型对应的TypeHandler集合MapJdbcType, TypeHandler? jdbcHandlerMap typeHandlerMap.get(type);if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) { // 检测是否为空集合标识return null;}// 初始化指定Java类型的TypeHandler集合if (jdbcHandlerMap null type instanceof Class) {Class? clazz (Class?) type;if (Enum.class.isAssignableFrom(clazz)) { // 针对枚举类型的处理Class? enumClass clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;jdbcHandlerMap getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);if (jdbcHandlerMap null) {register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));return typeHandlerMap.get(enumClass);}} else {// 查找父类关联的TypeHandler集合并将其作为clazz对应的TypeHandler集合jdbcHandlerMap getJdbcHandlerMapForSuperclass(clazz);}}// 如果上述查找皆失败则以NULL_TYPE_HANDLER_MAP作为clazz对应的TypeHandler集合typeHandlerMap.put(type, jdbcHandlerMap null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);return jdbcHandlerMap;}这里调用的 getJdbcHandlerMapForSuperclass() 方法会判断传入的 clazz 的父类是否为空或 Object。如果是则方法直接返回 null如果不是则尝试从 typeHandlerMap 集合中获取父类对应的 TypeHandler 集合但如果父类没有关联 TypeHandler 集合则递归调用 getJdbcHandlerMapForSuperclass() 方法顺着继承树继续向上查找父类直到查找到父类的 TypeHandler 集合然后直接返回。
下面是 getJdbcHandlerMapForSuperclass() 方法的具体实现
private MapJdbcType, TypeHandler? getJdbcHandlerMapForSuperclass(Class? clazz) {Class? superclass clazz.getSuperclass();if (superclass null || Object.class.equals(superclass)) {return null; // 父类为Object或null则查找结束}MapJdbcType, TypeHandler? jdbcHandlerMap typeHandlerMap.get(superclass);if (jdbcHandlerMap ! null) {return jdbcHandlerMap;} else {// 顺着继承树递归查找父类对应的TypeHandler集合return getJdbcHandlerMapForSuperclass(superclass);}}
别名管理
TypeAliasRegistry 是维护别名配置的核心实现所在其中提供了别名注册、别名查询的基本功能。在 TypeAliasRegistry 的 typeAliases 字段Map类型中记录了别名与 Java 类型之间的对应关系我们可以通过 registerAlias() 方法完成别名的注册具体实现如下
public void registerAlias(String alias, Class? value) {if (alias null) { // 传入的别名为null直接抛出异常throw new TypeException(The parameter alias cannot be null);}// 将别名全部转换为小写String key alias.toLowerCase(Locale.ENGLISH);// 检测别名是否存在冲突如果存在冲突则直接抛出异常if (typeAliases.containsKey(key) typeAliases.get(key) ! null !typeAliases.get(key).equals(value)) {throw new TypeException(...);}// 在typeAliases集合中记录别名与类之间的映射关系typeAliases.put(key, value);}在 TypeAliasRegistry 的构造方法中会通过上述 registerAlias() 方法将 Java 的基本类型、基本类型的数组类型、基本类型的封装类、封装类型的数组类型、Date、BigDecimal、BigInteger、Map、HashMap、List、ArrayList、Collection、Iterator、ResultSet 等常用类型添加了别名具体实现比较简单这里就不再展示你若感兴趣的话可以参考源码进行学习。
除了明确传入别名与相应的 Java 类型之外TypeAliasRegistry 还提供了扫描指定包名下所有的类中的 Alias 注解获取别名配置并完成注册的功能这个功能涉及两个 registerAliases() 方法的重载相关实现如下
public void registerAliases(String packageName, Class? superType) {ResolverUtilClass? resolverUtil new ResolverUtil();// 查找指定包下所有的superType类型resolverUtil.find(new ResolverUtil.IsA(superType), packageName);SetClass? extends Class? typeSet resolverUtil.getClasses();for (Class? type : typeSet) {// 过滤掉内部类、接口以及抽象类if (!type.isAnonymousClass() !type.isInterface() !type.isMemberClass()) {// 扫描类中的Alias注解registerAlias(type);}}}public void registerAlias(Class? type) {// 获取类的简单名称其中不会包含包名String alias type.getSimpleName();// 获取类中的Alias注解Alias aliasAnnotation type.getAnnotation(Alias.class);if (aliasAnnotation ! null) { // 获取特定别名alias aliasAnnotation.value();}// 这里的Alias注解指定的别名与type类型绑定registerAlias(alias, type);}总结
在这一讲我们重点介绍了 MyBatis 中 JdbcType 与 Java 类型之间转换的相关实现。
首先介绍了 JdbcType 与 Java 类型之间的常见映射关系以及两种类型之间转换的基础知识深入分析了 TypeHandler 接口及其核心实现了解了两种类型转换的原理接下来又讲解了 TypeHandler 的注册和查询机制明确了 MyBatis 是如何管理和使用众多的 TypeHandler 实现最后分析了 MyBatis 中的别名实现。