mybatis TypeHandler原理分析与思考

#Mybatis #泛型

TypeHandler使用

  1. 在自定义TypeHandler上注解@MappedTypes(value = {A.class, B.class})
  2. 配置typeHandlersPackage. 包下的所有TypeHandler都会被自动注册.
  3. Mapper.xml中声明<result column="enum1" jdbcType="INTEGER" typeHandler="xxx"/>
  4. mybatis配置文件
    <typeHandlers>
        <typeHandler handler="com.xxx.handler.EnumTypeHandler"/>
    </typeHandlers>
    

TypeHandler工作原理

  • 自动注册JavaType的所有默认TypeHandler. 见TypeHandlerRegistry
    • 虽然Enum不是JavaType, 但是会自动设置默认的EnumTypeHandler. 在根据javaType获取TypeHandler的时候, 会判断class是否是enum, 如果是, 则会生成该enum专用的EnumTypeHandler
  • TypeHandler注册时,需要寻找对应的JavaType.
    • 如果已经指定JavaType, 则直接可以注册
    • 如果没有指定, 则尝试通过@MappedTypes注解, 找到JavaType
    • 如果依然找不到JavaType, 则判断该TypeHandler是否继承TypeReference<T>,通过getSuperclassTypeParameter方法,可以得到泛型的JavaType.
    • 如果依然找不到JavaType, 则JavaType为null,进行注册.
  • preparedStatement设值时, 调用来字段对应的HandlerType,进行转换.
  • 查询数据库结果后, 调用字段对应的HandlerType.setResult.

mybatis中对于Enum的类型处理

使用者什么都不用做, 自动支持.

mybatis提供了EnumTypeHandler和EnumOrdinalTypeHandler.

  • EnumTypeHandler: 调用Enum.name()方法, 将枚举转换为String.
  • EnumOrdinalTypeHandler: 调用Enum.ordinal()方法, 将枚举转换为int.

当需要转换枚举类型时, 优先使用缓存中的TypeHandler, 如果没有找到, 则实时生成专用的EnumTypeHandler.

MybatisPlusEnumTypeHandler

MybatisPlus通过MybaitsEnumTypeHandler增强对enum的支持,允许指定转换时使用的字段.

使用

  • 配置mybatis-plus.defaultEnumTypeHandler, 默认是org.apache.ibatis.type.EnumTypeHandler, 可以不配置.
  • 如果想要在初始化时, 创建TypeHandler, 有两种方式
    1. 配置mybatis-plus.type-enums-package. 自动为该包下的所有enum,注册MybatisPlusEnumTypeHandler.
    2. 在model中为枚举字段注解@TableField(typeHandler = xxx), 为该枚举,注册指定的TypeHandler.
  • 如果想要在使用时, 实时创建Typehandler. 枚举实现IEnum接口,或者为想要序列化的字段设置@EnumValue注解.

所以,使用者只需要为枚举上需要转换的字添加@EnumValue即可, 无需做其他.

原理

启动项目时, 通过typeEnumsPackage配置的包名, 加载指定包下的所有枚举类型, 并将枚举类型注册到mybatis中.

  1. 扫描包下的所有枚举, 且枚举必须继承 IEnum(MybatisPlus提供的枚举接口).
  2. 根据枚举, 生成MybatisEnumTypeHandler实例, 并注册到mybatis中. 每个enum生成一个独有的MybatisEnumTypeHandler实例.

源码见MybatisSqlSessionFactoryBean.buildSqlSessionFactory()

mybatisPlus中枚举处理的另一种方式

指定enum(或任意javaType)使用jacksonTypeHandler, 使用json对enum序列化. 该方案简单而通用,可以对任意javaType序列化. 缺陷是 json序列化的格式不一定能满足需求.

延伸

问题:如何实现泛型TypeHandler? 答案是无法实现,原因如下:

  1. 由于泛型擦除,所以需要为泛型类的所有子类,创建专用的TypeHandler. mybatis的EnumTypeHandler 和mybatis-plus的MybatisEnumTypeHandler是同样的原理, 只是创建的方式不同.
  • mybatis是在getTypeHandler时, 为enum实时创建专用TypeHandler.
  • mybatis-plus增加了: 初始化时, 通过type-enums-package,为包下的所有Enum创建专用TypeHandler.
  1. 由于mybatis-plus提供了@tableFiled字段,所以在model的字段上, 指定泛型TypeHandler, 这是否可行呢? 该方法不可用。Java的获取泛型参数,只能通过父类的泛型参数来获得。即只有在编译期明确指出的泛型参数,才可以获取到。 见[[java中继承与泛型]]