- TypeHandler介绍
- TypeHandler的实际运用
- TypeHandler作用原理
TypeHandler介绍
在预处理语句(PreparedStatement)中设置一个参数时,或者从结果集中取出一个值时, 都会用TypeHandler将获取的值以合适的方式设置到PreparedStatement,或者转换成Java类型。
可以重写TypeHandler或创建自己的TypeHandler来处理不支持的或非标准的类型。 具体做法为:实现org.apache.ibatis.type.TypeHandler接口, 或继承一个很便利的类org.apache.ibatis.type.BaseTypeHandler。
TypeHandler的实际运用
一般在项目中设计表结构的时候对于一些只有有限个值的字段,都会用数字类型来表示。例如表示考试的状态字段exam_status表类型会定义为tinyint,并且使用 1:未开始;2:待考试;3:考试中;4:已完成 来表示考试的不同状态。
那么对于类似这样的字段,如果我们在代码中直接使用Integer来表示的话,这个可读性和可维护性还是挺差的,所以一般都会定义一个枚举来表示,像这样:
public
那么问题来了,如何才能使得数据库的数字类型的exam_status转换成ExamStatusEnum类呢?
方案一:每次使用手工转换,麻烦啰嗦,不可取
方案二:配置Mybatis,使得能够自动完成这个转换工作
为了使用方案二,来看下解决方案:
首先,这些枚举类需实现一个共同的接口,也就是EnumBase接口,里面有一个关键的静态方法codeOf,实现数字转换成特定枚举的功能,然后编写TypeHandler:
public
我大概对EnumBaseTypeHandler类的几个方法做下说明:
- void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) 该方法为设置参数使用的转换方法,也就是转换参数到SQL
- E getNullableResult(ResultSet rs, String columnName) 该方法是拿到结果集后根据列名称处理结果,也就是将数据库的字段值转换为Java 的属性值
- E getNullableResult(ResultSet rs, int columnIndex) 该方法是拿到结果集后根据列序号处理结果,也就是将数据库的字段值转换为Java 的属性值
- E getNullableResult(CallableStatement cs, int columnIndex) 该方法是针对存储过程转换结果
最后,为了避免需要在Mybatis的xml配置文件为每个这样的枚举声明,类似这样:
这里就不再使用xml文件来做配置,所有配置信息使用Java代码来直接设置,这也是为了能够动态注册TypeHandler所必须要做的,主要关键就是这个Java配置:
@Bean
完成这些工作后就能让Mybatis自动将数据库表字段数字转换成Java Bean 的特定枚举类型字段。
TypeHandler作用原理
Mybatis有一个注册工厂为TypeHandlerRegistry的类,里面初始化了常见类型的转换器,例如我们看下String类型:
register
register 方法最终会调用到TypeHandlerRegistry的这个方法:
private
TYPE_HANDLER_MAP的定义是这样的:
private
里面就存放了Java类型对应的TypeHandler。
Mybatis执行转换时,是根据这个方法来获取TypeHandler的:
private
从这里我们可以看出获取TypeHandler的顺序是:
- 根据Java字段类型+jdbc类型
- 根据Java字段类型
- 根据jdbc类型
我们的自定义枚举类型能起作用就是依据这里:
EnumBaseTypeHandler
下面我们看下Mybatis如何使用TypeHandler实现参数设置到sql中和转换SQL查询结果到Java类的,以下面例子来讲解:
@Service
mapper文件是这样的:
<mapper
跟踪Mybatis解析这个mapper xml文件的流程,可以看到最终所有parameter入参的Java参数都是使用这个方法来关联TypeHandler的,其中每条SQL的parameter入参都会解析成一个ParameterMapping对象:
private
但这时候关联的都是UnknownTypeHandler,因为xml中字段并没指定JavaType和jdbcType属性,那么Java类型就会默认是Object类型(没指定parameterType属性,所以没法知道实际类型,只有等到运行时才能得到。因此只有指定parameterType属性,这里Mybatis才能相应的获取到实际Java类型),jdbc类型默认是null,所以关联到了UnknownTypeHandler
而resultMap的Java参数则是使用这个方法来关联TypeHandler的,其中resultMap元素会解析成一个ResultMapping对象:
private
此时Mybatis根据type="org.javamaster.b2c.core.entity.MicrowebsiteExam"就会相应的获取到了实际Java类型并和具体的TypeHandler关联起来,例如examType属性就会和EnumBaseTypeHandler关联起来。
在执行这行代码时:
List
对于SQL参数的设置,跟踪源码执行流程,可以看到(只留下关键代码):
public
我们看下UnknownTypeHandler的setParameter方法,参数如果不是null,最终是调用了自身的setNonNullParameter方法:
public
所以获取真正的TypeHandler并设置SQL参数是这个方法完成的,这个resolveTypeHandler最终是调用到了我前面提到的TypeHandlerRegistry.getTypeHandler(javaType, jdbcType)方法。
对于转换SQL查询结果到Java类,跟踪源码执行流程,这个流程就复杂了很多,可以看到(只留下关键代码),是由DefaultResultSetHandler 来处理的:
public
好,整个Mybatis的执行流程就分析完了,有兴趣的同学可以自己去实践下。
源码github地址:
jufeng98/java-mastergithub.com