Mybatis源码分析(四)--TypeHandler的解析
学习前的疑问
- TypeHandler的主要功能是什么?
- TypeHandler如何配置?
- Mybatis是如何使用TypeHandler?(参数设置,结果映射)
- 实现通用枚举转换器的可行方案是什么?
TypeHandler的主要功能是什么?
TypeHandler
是一个接口,那么其所拥有什么功能最简单的方法是看接口方法与注释(这里mybatis注释相当少),那么看下列方法.
1.void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
该方法为设置参数使用的转换方法,所需要的参数基本都给你传过来了,因此很好理解.
2.T getResult(ResultSet rs, String columnName) throws SQLException;
该方法是拿到结果集后根据列名称处理结果
3.T getResult(ResultSet rs, int columnIndex) throws SQLException
该方法是拿到结果集后根据列序号处理结果
4.T getResult(CallableStatement cs, int columnIndex) throws SQLException;
该方法是针对存储过程转换结果.
那么TypeHandler
的作用就可以简单的理解为:
- 转换参数到sql中
- 转换查询结果到Java类中
TypeHandler如何配置?
1.系统默认转换器
TypeHandler有一个注册工厂为TypeHandlerRegistry
类,该类中默认初始化了常用的转换器,其成员变量中有如下两个Map,可以看到JDBC_TYPE_HANDLER_MAP
该map是针对jdbc转换到Java类的转换,为一对一结构,TYPE_HANDLER_MAP
该map是针对Java类到JDBC类型的转换,为一对多结构.
1 | private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); |
以String类的转换器注册为例分析下
1 | register(String.class, new StringTypeHandler()); |
那么对应的JDBC_TYPE_HANDLER_MAP内存里面为
TYPE_HANDLER_MAP内存里面接口如下图,注意在其TypeHandler
中有一个key为null的转换器,其对应的注册方法自然为 register(String.class, new StringTypeHandler());
,那么也就是说当没指定jdbc类型时对于String.class类的转换均使用该转换器作为默认的TypeHandler
.
2.mybatis.type-handlers-package转换器
该指令是配置一个转换器所在的包,然后扫描该包下的TypeHandler
的实现类,自动注册为转换器,详情可以看org.apache.ibatis.type.TypeHandlerRegistry#register(java.lang.String)
方法
由于Java存在泛型擦除机制,那么该Handler针对的JavaType该方法从TypeHandler
实现类是拿不到的,因此其需要配合MappedTypes
注解,看如下实现方法,针对TypeHandler
去主动获取其上的MappedTypes
注解,使用注解中的JavaType作为该TypeHandler
的转换主体,如果获取不到则使用null,因此需要额外注意.
1 | public void register(Class<?> typeHandlerClass) { |
3.Mapper中定义的TypeHandler
首先我定义一个自定义的TypeHandler
,该Handler只针对我所定义的枚举类处理,当然只能处理UserIdentifyType
枚举类型,后面会实现一个通用的枚举转换器.
1 | public class UserIdentifyTypeHandler extends BaseTypeHandler<UserIdentifyType> { |
然后在mapper.xml文件中也是可以定义TypeHandler
的,如下形式
1 | <select id="findByOpenIdAnyType" resultType="com.itoolshub.user.repository.domain.UserAuth"> |
那么这个TypeHandler
是什么时候初始化的呢?
这里涉及到ParameterMapping
这个类,该类是Mybatis存储参数映射的地方,其内部有方法org.apache.ibatis.builder.BaseBuilder#resolveTypeHandler(java.lang.Class<?>, java.lang.Class<? extends org.apache.ibatis.type.TypeHandler<?>>)
,该方法会获取到对应的TypeHandler
,然后从typeHandlerRegistry
中获取,获取不到则使用反射生成一个.生成后并没有加入到typeHandlerRegistry中,也就是该TypeHandler并非单例,多少个sqlStament中如果使用了该转换器那么就会实例化几个该转换器,因此正确的使用方法是把该TypeHandler
注册到typeHandlerRegistry
中,然后在xml中使用.那么针对上述sql的ParameterMapping
如下.
另外由于我没有在xml中指定JavaType,那么其默认为Object,也就是参数设置是不能动态获取参数类型的.
参数如何使用TypeHandler设置到sql中?
上面说到对于每一个sqlSatment都会解析为一个ParameterMapping的Map集合,在该ParameterMapping
中TypeHandler已经确定好了,那么设置参数就只需要简单的调用下typeHandler.setParameter(ps, i + 1, value, jdbcType);
方法,具体可以参考org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
方法中对其的做法.
这里有一个很重要的点就是这里的TypeHandler
的选择没有和我传入的参数类型绑定,举个例子我把上述参数去掉typehandler变成identity_type = #{type}
,那么得到的则是一个UnknownTypeHandler
.
UnknownTypeHandler并不Unknow
UnknownTypeHandler
的实现中能获取到具体输入参数的类型,然后调用org.apache.ibatis.type.UnknownTypeHandler#resolveTypeHandler(java.lang.Object, org.apache.ibatis.type.JdbcType)
方法从TypeHandlerRegistry
中获取到真正的转换器,这里的获取是根据输入参数的具体类型的class名称.获取不到则使用ObjectTypeHandler
作为转换器.
结果如何使用TypeHandler设置到结果集中?
相比参数设置结果的取出转换要复杂很多,方法org.apache.ibatis.executor.resultset.ResultSetWrapper#getTypeHandler
中定义了一系列的获取TypeHandler
的策略,总结如下顺序
- 根据返回参数类型+jdbc类型
- 根据返回参数类型
- 根据jdbc类型
具体就不展开讨论了.
制作通用的枚举类处理器
依照上述分析,如果想让枚举类的处理和基本类型一样的不需要显示的在mapper.xml上指定一些属性,几乎是不可能的一件事情,不过可以大大简化其使用方式,首先分析下对于枚举类两处的处理.
- 参数设置时,mapper.xml中的sql字段什么都不指定直接#{value},那么最终会使用该value的class名称去获取到对应的typeHandler.
- 结果映射时,由上述优先级顺序可以得知对于枚举类会使用方式2根据返回参数类型,也就是class名称获取对应的typeHandler.
那么通用转换器的实现思路很简单了,要做的就是把对应枚举类的名称->typehandler初始化时注入到TypeHandlerRegistry
中.首先定义一个枚举类所使用的接口,然后编写通用处理,这里能实现还一个原因就是Class对象有type.getEnumConstants()
方法可以获取到其所有枚举对象,也就是可以把数字映射为指定结果了,需要注意的是这里把每个枚举类都注入到TypeHandlerRegistry
使用的是@MappedTypes
注解,该注解生效是需要配置mybatis.type-handlers-package
以包的形式扫,否则不生效.
下面的代码是copy自github,本文算是对其原理分析了一遍.https://github.com/mybatis/mybatis-3/issues/42
EnumHasValue
1 | public interface EnumHasValue { |
EnumValueTypeHandler
1 |
|
备注
以上内容基于 mybatis-spring-boot-starter
:1.3.0,其mybatis
版本3.4.4
参考文章: http://www.mybatis.org/mybatis-3/zh/configuration.html#typeHandlers
- 版权声明: 感谢您的阅读,本文由屈定's Blog版权所有。如若转载,请注明出处。
- 文章标题: Mybatis源码分析(四)--TypeHandler的解析
- 文章链接: https://mrdear.cn/posts/framework-mybatis-type-handler.html