资讯专栏INFORMATION COLUMN

【Mybatis系列】从源码角度理解Mybatis的数据转换器TypeHandler

Edison / 779人阅读

摘要:无论是在预处理语句中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成类型。这个抽象类实现了接口,这个接口主要定义了类型转换的几种操作。至于这个抽象类继承的,主要是提供了获取这个具体是哪个类型。

TypeHandlers

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

下面是常见的一些对应类型:

以BigDecimalTypeHandler看一下,它主要完成了哪些工作。

这个类的第一个方法是对预处理语句(PreparedStatement)设置参数,之后的三个函数都是从ResultSet或者用于执行存储过程的CallableStatement语句中获取BigDecimal类型的数值,用于向BigDecimal类型的Java字段赋值。
BigDecimalTypeHandler继承的BaseTypeHandler是个泛型类,其他的TypeHandler也是通过继承这个抽象类,实现其中的抽象方法,实现类型转换的工作。

这个抽象类实现了TypeHandler接口,这个接口主要定义了类型转换的几种操作。

至于这个抽象类继承的TypeReference,主要是提供了获取这个T具体是哪个类型。在判断使用使用哪个TypeHandler时有用,后文会看到。

如何使用

大致介绍了TypeHandler的作用,及其相关类,我们来看看如何使用它。
今天遇到的主要是从SqlServer中取数据,遇到很多列都是Numeric(10,2)类型,指的是字段是数字型,长度为10,小数为两位。Mybatis默认的BigDecimalTypeHandler取到后,都默认变成4位小数,不够的补了0。而上层的要求是,拿到的和数字相关的数据都要2位小数。

有两种做法,一种是在所有给上层赋值的时候,都人工对BigDeciam的数据做如下操作。

setScale(2, BigDecimal.ROUND_HALF_UP)

因为这是一个全局性的要求,所有相关的地方,都需要有这个代码,虽然可以写一个工具类,各个地方调用,但就对原本间接的代码造成了侵入。既然这样,为什么不试试TypeHandler。

我的做法是继承BigDecimalTypeHandler,覆盖原来的取值方法,对取到的数值做范围限定。

加上@MappedJdbcTypes注解是为了表明这个类是用于映射JdbcType的NUMERIC类型,这会覆盖默认的用于转换Java BegDecimal和Jdbc NUMERIC的BigDecimal,在后面源码中可略窥一二。

开发完这个转换类后,你需要在Mybatis的配置文件中声明这个TypeHandler,这样Mybatis才知道你自己声明了一个TypeHandler。


    

这样TypeHandler就起作用了。下面是前后效果。

源码层面

首先Mybatis有一个默认的TypeHandler实现,这些TypeHandler是如何被Mybatis识别的呢。
答案是TypeHandlerRegistry。在Mybatis初始化配置的时候,TypeHandlerRegistry会把JdbcType和Java类型对应的映射关系注册进该类内部的Map中。


JDBC_TYPE_HANDLER_MAP中记录的是JdbcType和TypeHandler对应的关系。

TYPE_HANDLER_MAP中记录的是Java类型和对应的所有JdbcType以及其对应TypeHandler的映射关系关系。

UNKNOWN_TYPE_HANDLER是在执行BaseTypeHandler的抽象方法时,去先解析出来该用什么TypeHandler,目前还没用到,先不研究。
ALL_TYPE_HANDLERS_MAP中记录的是所有TypeHandler的Class和其实例之间的映射关系。

我们以系统默认注册的三个作为例子,看看整个执行的流程

1 register(String.class, new StringTypeHandler());
2 register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
3 register(JdbcType.NCHAR, new NStringTypeHandler());

第一个是告诉 String类型的转换,要用StringTypeHandler。
直接进的这个函数,因为我们的TypeHandler上并没有打注解,因此直接进入箭头标记的逻辑。

然后继续注册,只不过jdbcType是null。

后续的代码比较简单,会先从Type_HANLER_MAP中看是否有已经存在的 Map> map,没有的话,新建,并且放入TYPE_HANDLER_MAP中,在Map> map放入此次的jdbcType和它对应的Handler。最后在ALL_TYPE_HANDLERS_MAP放入handler的类和实例。

第二个,传入了jdbcType是NCHAR,和第一个类似,但直接就进入了最后一步的注册环节,没有去判断传入什么样的jdbcType类型,因为已经指定了。

第三个是绑定了 jdbcType和Handler之间的对应关系。

OK,前面是系统默认注册进去的,那我们看一下我们在如何使用章节中添加进去的SubBigDecimalTypeHandler是如何被注册进去的呢。

Mybatis在应用中启动时,会根据XML文件初始化配置,负责解析XML生成配置类的就是XMLConfigBuilder,通过调用其中的parseConfiguration方法填充配置类。

箭头表示处,就是解析typehandlers节点,我们看看他具体做了些什么。

因为我们不是对整个package进行注册,所以进入else分支,因为只表明了一个最简单的Handler,所以要获取的字段都为null,由此我们也可以看出,在编写XML时,我们也是可以直接指定映射关系的,因为获取不到javaType和jdbcType,后面应该是会根据这个类再解析一波。跟注册相关的又回到了TypeHandlerRegistry这个类里面,职责还是很清晰的。

在这个方法里面,首先会获取有没有打MappedType这个注解,这个注解是表明这个类对应处理的JavaType是啥。我们这边没有找到,因此继续往下走。

从Mybatis3.1.0开始,会自动解析这个类对应的Java类型,还记得之前我们继承的BigDecimalTypeHandler中我们的基类BaseTypeHandler继承了TypeReference么?

这个类的构造函数会获取泛型中具体的类型是什么,细节代码可以私下看一下。
获取到了具体的Java类型,我们就继续往下传。

因为我们的subBigDecimalTypeHandler是打了MappedJdbcType注解的,因此之后的步骤和register(String.class, JdbcType.NCHAR, new NStringTypeHandler())是一致的,可以回看上文。

到这里,TypeHandler的注册部分已经完成了。

在之前的关于映射的文章中,我们提过,Mybatis完成映射后,会选择合适的TypeHandler处理器,完成对Java业务对象的赋值,我们首先找到入口在哪里。

完成赋值的就是在1,2处,我们这边用的是自动映射,因此进1看看,具体关于TypeHandler的处理,不会有太大的差异。
在之前的createAutomaticMappings,找到列名后,会找出对应的字段,首先会判断是否有对应的TypeHandler。

因为你知道了JDBC的类型,也通过反射知道了Java的类型。

这边就首先去TYPE_HANDLER_MAP中找已经存在的JDBC-TypeHandler的映射,如果有的话直接取,没有的话,就默认取null所对应的那个类型。
因为我们知道jdbc的类型是NUMERIC,而且之前注册的SubBigDecimalTypeHandler对应的JDBC类型是NUMERIC。

因此就取了更匹配的SubBigDecimalTypeHandler。
之后就是调用getResult方法,完成值的获取即可。

总结

本文主要介绍了

什么是TypeHandler。

如何使用TypeHandler。

从系统默认的以及自定义的TypeHandler的注册和获取的角度,从源码层面分析了整个过程。

希望得到您的点赞,打赏支持,谢谢。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70141.html

相关文章

  • Mybatis系列源码角度理解Mybatis$和#作用

    摘要:原因就是传入的和原有的单引号,正好组成了,而后面恒等于,所以等于对这个库执行了查所有的操作。类比的执行流程和原有的我们使用的方法就是。可以理解为就是用来解析定制的符号的语句。后续的流程,就和正常的流程一致了。 前言 在JDBC中,主要使用的是两种语句,一种是支持参数化和预编译的PrepareStatement,能够支持原生的Sql,也支持设置占位符的方式,参数化输入的参数,防止Sql注...

    yanwei 评论0 收藏0
  • 源码学习设计模式之模板方法

    摘要:模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。在软件工程中,它是一种软件设计模式,和模板没有关连。模板方法充分运用了多态与继承。去建设银行支付去招商银行支付实现模板方法的细节,我们来看使用逻辑。 Photo by Tomáš Malík on Unsplash 什么是模板方法模式?摘录 wiki 的介绍。 模板方法模式定义了一个算法的步骤,并允许子类别为...

    yankeys 评论0 收藏0
  • 使用MyBatis简化枚举类值存储和读取

    摘要:内置的枚举处理器为了处理上述遇到的问题,内置了两种,分别是和。将使用枚举实例的值序数值,从开始来和枚举类之间做转换。比如有记录显式为全局指定在查询时,类变量将自动赋值为,添加记录时同理,数据库值将存储为其枚举类实例序号。 场景描述 我们在实际场景中经常会遇到需要将枚举值存储到数据库中,或是将从数据库中查询到的值对应到枚举类上的情况。 比如表process大致定义如下: -- -----...

    Bryan 评论0 收藏0
  • Mybatis系列源码角度理解Mybatis字段映射-驼峰式命名

    摘要:主要有三种方案驼峰式命名开关,或者不开,数据库列和字段名全一致。开启开配置项后,在匹配时,能够根据数据库列名找到对应对应的驼峰式命名后的字段。经过若干次中途崩溃,我终于写完了驼峰式命名开关下,我们是如何完成数据库列和字段名的映射的。 在上篇博客-[[JDBC] 处理ResultSet,构建Java对象](https://my.oschina.net/kailun...中提到,我们需要分...

    qiangdada 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<