查看原文
其他

玩转Mybatis中的类型转换器TypeHandler

码农小胖哥 码农小胖哥 2019-09-12


      抛开使用场景谈技术都是耍流氓。


1.场景

日常java开发中经常有这种需求,用0或者1这些代码(不局限于数字)来表示某种状态。比如用0表示女性,用1来表示男性。而且写入数据库可能是一个标识,从数据库读取又还原为具体的说明。而且一般情况下为了更好理解或者消除魔法值,通常的处理方案是定义一个枚举,有些枚举是这样定义的:



那么通常很多人会这么入库(java伪代码)



读取的时候要么同样按照上面的再反向处理一次或者使用数据库sql语法case when来直接写入DTO



这种处理方式看起来不是很优雅。而且多了很多的判断和处理逻辑,和我们的业务并不是非常相关。所以我们可以选择更好的处理方式。


2.Mybatis中的TypeHandler

如果你ORM框架用的是Mybatis。那么将很容易通TypeHandler<T>接口解决这个问题。这是Mybatis提供的一个类型转换接口,将数据库的jdbc类型和java中的类型进行相互转换。接下来我们将一步一步地来研究这个接口。


2.1 TypeHandler


源码分析:

 

  •  setParameter 方法 通过 传入的T类型写你自己的逻辑,选择调用 PreparedStatement 对象的某个set方法将数据写入数据库。此方法用来写库。

  •  getResult(ResultSet rs, String columnName) 通过字段名来读库并转换为T类型。 

  •  getResult(ResultSet rs, int columnIndex) 通过字段索引来读库并转换为T类型。

  •  getResult(CallableStatement cs, int columnIndex) 调用存储过程来获取结果并转换为T类型。


2.2 EnumOrdinalTypeHandler

我们发现TypeHandler有一个实现类EnumOrdinalTypeHandler。字面意思是可以通过枚举的序号来处理类型。

 


我们先不考虑setNull的情况。通过此方法我们发现确实存入的是枚举的顺序值(顺序从0开始),拿上面的例子来说 如果是GenderType.FEMALE是0,如果是GenderType.MALE是1,但是当GenderType.UNKNOWN时存入的是2。取的时候也是自然反向处理为具体的GenderType枚举。


2.3 EnumTypeHandler

我们还发现有另外一个枚举类型处理器。它的set方法是这样的:


我们不考虑jdbcType问题发现都是将Enum.name()的值写入数据库。拿上面的例子来说 如果是GenderType.FEMALE是FEMALE,如果是GenderType.MALE是MALE,但是当GenderType.UNKNOWN时存入的是UNKNOWN。读库是通过Enum.valueOf(Class<T> enumType,String name)来进行反转操作。


2.4 自定义TypeHandler

如果说我们的枚举类型或者说我们使用其他方式来处理类别转换怎么办?当然Mybatis不会帮你干这么具体的事情。需要你自己来实现了。我们还拿枚举作为例子,然后模仿上面的两种TypeHandler。还是拿开始的例子来说通常我个人比较喜欢这么定义枚举



通过继承BaseTypeHandler实现该抽象类的3个钩子方法就行了:

 


TypeHandler 实现写好了,那么如何让其发挥作用呢?我们接着往下走。 


2.5 TypeHandler的核心要点

TypeHandler作用是javaType和jdbcType相互转换。所以在声明一个TypeHandler的时候一定要明确该TypeHandler处理的这两种类型。这是必须要明确的原则。

MyBatis不会通过窥探数据库元信息来决定使用哪种JDBC类型,所以你必须在参数和结果映射中指明何种类型的字段,使其能够绑定到正确的类型处理器上。MyBatis直到语句被执行时才清楚数据类型。

通过上述例子中的@MappedJdbcTypes和@MappedTypes来进行绑定类型转换关系,也可以通过xml的typeHandler元素中的jdbcType或者javaType来指定。如果同时指定,xml的优先级要高。注意有可能你会覆盖内置的TypeHandler。所以自定义时一定要去了解Mybatis提供的一些默认处理器。避免对其他业务的影响。

使用自定义TypeHandler很重要的一个原则就是一定要声明JavaType和JdbcType。上面这些虽然比较生涩但是对于使用好TypeHandler非常重要。接下来我们来讲讲具体的配置。


2.6 免注册TypeHandler

我们这里只讲最常用xml中的配置,你可以举一反三:


  •  一种在resultMap元素中声明一般用来查询。一定要注意2.5中的一些原则。

 


  • 然后是在插入、更新语句中使用。它们都是相同的,这里只举一个插入例子。



如果注册了别名上面的都可以使用别名来减少冗长的全限定名。上面的好处就是不用在TypeHandlerRegistry中进行注册。


2.7 注册TypeHandler

 在配置中声明注册TypeHandler,然后Mybatis根据两种类型会自动匹配。所以这里还是要强调2.5中的核心要点。

 

  • 如果你是xml配置需要在Configuration配置文件中的<typeHandlers>标签中进行声明式注册

 


  • javaConfig 方式 ,第一你可以通过SqlSessionFactory对象取到Configuration对象将typeHandler注册进去。如果你使用mybatis-spring组件,可以在SqlSessionFactoryBean的setTypeHandlersPackage方法中配置typeHandler的集中包路径,那么框架将会自动扫描并注册他们。springboot中对应的配置属性是mybatis.typeHandlersPackage。如果你注册了TypeHandler。在Mapper.xml中只需要声明jdbcType和javaType,无需再声明具体的typeHandler。Mybatis会自动通过jdbcType、javaType映射到具体注册的TypeHandler上去 。就像下面的例子:

   


3.总结

今天我们学习了mybatis开发中如何通过使用类型处理器进行类型的转换处理,如何处理枚举,如何自定义处理器并使用它。其实不仅仅局限于枚举。留给你自己去发现。相关的代码在我的码云仓库中:

https://gitee.com/felord/mybatis-test.git



   点击下方可查看其他干货▼        

一文理清Mybatis中resultType与resultMap之间的关系和使用场景

老司机教你通过Spring来读取文件的各种姿势

Java入门必看的Java 8 Stream API 使用指南

快速学会Springboot中编写Mock单元测试

学会Spring Mvc 跨域你只需要看完这一篇


扫描二维码

   关注我

码农小胖哥

我就知道你在看!
Modified on

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存