TypeHandler 即类型处理器,作用是将 Java 数据类型参数转成数据库的数据类型,或取出数据库数据转成 Java 数据类型。
MyBatis 为 TypeHandler 提供了系统定义,也支持用户自定义,系统定义就可以实现大部分功能了。如果用户自定义 TypeHandler ,则需要小心谨慎。例如自定义 TypeHandler  实现枚举转换。
 
MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,或者从结果集(ResultSet)中取出一个值时,都会用注册了的 typeHandler。
TypeHandler  常用的配置为 Java 类型(javaType),JDBC 类型(jdbcType)。
系统定义的typeHandler 在源码 org.apache.ibatis.type.TypeHandlerRegistry 类的构造方法中,可以看到默认注册的 typeHandler。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public  final  class  TypeHandlerRegistry  {  private  final  Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap = new  EnumMap <>(JdbcType.class);   private  final  Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new  ConcurrentHashMap <>();   private  final  TypeHandler<Object> unknownTypeHandler;   private  final  Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new  HashMap <>();   private  static  final  Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();   private  Class<? extends  TypeHandler > defaultEnumTypeHandler = EnumTypeHandler.class;      public  TypeHandlerRegistry ()  {     this (new  Configuration ());   }      public  TypeHandlerRegistry (Configuration configuration)  {     this .unknownTypeHandler = new  UnknownTypeHandler (configuration);     register(Boolean.class, new  BooleanTypeHandler ());     register(boolean .class, new  BooleanTypeHandler ());     register(JdbcType.BOOLEAN, new  BooleanTypeHandler ());     register(JdbcType.BIT, new  BooleanTypeHandler ());     register(Byte.class, new  ByteTypeHandler ());     register(byte .class, new  ByteTypeHandler ());     register(JdbcType.TINYINT, new  ByteTypeHandler ());   	   } 
 
以 StringTypeHandler 为例,了解 typeHandler 的实现逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public  class  StringTypeHandler  extends  BaseTypeHandler <String> {  @Override    public  void  setNonNullParameter (PreparedStatement ps, int  i, String parameter, JdbcType jdbcType)        throws  SQLException {     ps.setString(i, parameter);   }   @Override    public  String getNullableResult (ResultSet rs, String columnName)        throws  SQLException {     return  rs.getString(columnName);   }   @Override    public  String getNullableResult (ResultSet rs, int  columnIndex)        throws  SQLException {     return  rs.getString(columnIndex);   }   @Override    public  String getNullableResult (CallableStatement cs, int  columnIndex)        throws  SQLException {     return  cs.getString(columnIndex);   } } 
 
StringTypeHandler 是一个最常用的 typeHandler,处理 String 类型。
StringTypeHandler  继承了 BaseTypeHandler,而 BaseTypeHandler 实现了 TypeHandler 接口,TypeHandler  接口定义了4个抽象方法,所以实现类需要实现这四个方法。
1 2 3 4 5 6 7 8 9 10 11 public  interface  TypeHandler <T> {  void  setParameter (PreparedStatement ps, int  i, T parameter, JdbcType jdbcType)  throws  SQLException;   T getResult (ResultSet rs, String columnName)  throws  SQLException;   T getResult (ResultSet rs, int  columnIndex)  throws  SQLException;   T getResult (CallableStatement cs, int  columnIndex)  throws  SQLException; } 
 
BaseTypeHandler  实现了 setParameter 方法。
setParameter  是 PreparedStatement 对象设置参数,它允许我们自己自定义转换规则 
getResult  是对 ResultSet 结果集的转换处理,分为用列名(columnName),或者使用列下标(columnIndex)来获取结果数据。还包括使用 CallableStatement(存储过程)获取结果及数据的方法。 
 
自定义typeHandler MyBatis 系统定义的 typeHandler 已经能够处理大部分的场景了了;而自定义 typeHandler 可以处理一些特殊的类型,如字典项的枚举。
自定义 typeHandler:必须实现接口 org.apache.ibatis.type.TypeHandler,也可继承 MyBatis 已经提供的 org.apache.ibatis.type.BaseTypeHandler 抽象类来实现,BaseTypeHandler  实现了 TypeHandler 接口。
自定义 typeHandler 类上使用注解来配置指定 JdbcType 和 JavaType。
@MappedTypes:定义的是 JavaType 类型,可以指定哪些 Java 类型被拦截。 
@MappedJdbcTypes:定义的是 JdbcType 类型,它需要满足枚举类 org.apache.ibatis.type.JdbcType 所列的枚举类型。 
 
MyBatis 默认情况下是不会启用自定义的 typeHandler 进行转换结果的,需要标识和指定,比如在字段映射的 ResultMap 中配置 JdbcType 和 JavaType,或直接使用 typeHandler 属性指定。配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?xml version="1.0"  encoding="UTF-8" ?> <!DOCTYPE mapper  PUBLIC  "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper  namespace ="com.gxitsky.mapper.ActorMapper" >     <resultMap  id ="actorMap"  type ="actor" >          <id  column ="actor_id"  property ="actorId"  javaType ="long"  jdbcType ="BIGINT" />          <result  column ="first_name"  property ="firstName"  javaType ="string"  jdbcType ="VARCHAR" />          <result  column ="last_name"  property ="last_name"  typeHandler ="com.gxitsky.config.MyStringTypeHandler" />      </resultMap >      <select  id ="queryById"  parameterType ="long"  resultType ="actor" >          SELECT * FROM actor WHERE actor_id = #{actor_id}     </select >      <select  id ="queryById"  resultMap ="actorMap" >          SELECT * FROM actor     </select >      <select  id ="findActor"  resultMap ="actorMap" >          SELECT * FROM actor WHERE first_name LIKE concat('%',             #{firstName javaType=string jdbcType=VARCHAR typeHadler=com.gxitsky.config.MyStringTypeHandler} ,             '%');     </select >  </mapper > 
 
在配置文件里面配置,结果集中字段指定的 JdbcType  和 JavaType 与定义的 typeHandler 一致,MyBatis 才能知道使用自定义的类型转换器进行转换。
在配置 typeHandler 时也可以进行包配置,MyBatis 就会扫描包中的 typeHander,就不用一个一个配置,减少配置工作量。
1 2 3 4 <typeHandlers >     <typeHandler  handler ="com.gxitsky.config.mybatis.typehandler.MyStringTypeHandler"  javaType ="string"  jdbcType ="VARCHAR" />      <package  name ="com.gxitsky.config.mybatis.typehandler" />  </typeHandlers > 
 
 
映射集中的字段直接指定 typeHandler 属性,就不需要在配置文件中定义了。
 
在参数中指定 typeHandler ,MyBatis 就会用对应的 typeHandler 进行转换,这样也不需要在配置里面定义。
 
 
枚举类型typeHandler MyBatis 内部提供了两个转换枚举类型的 typeHandler:
org.apache.ibatis.type.EnumTypeHandler:使用枚举字符串名称作为参数传递。 
org.apache.ibatis.type.EnumOrdinalTypeHandler:使用整数下标作为参数传递,MyBatis 默认的枚举类型处理器。 
 
如果枚举和数据库字典项保持一致(例如,性别枚举,数据库字段保存男性的是 MALE , 枚举也是 MALE ,指的是 Enum.name  方法的值,不是指枚举的一个属性),则可直接拿来使用。
所以从这里可知这两个枚举并不太适用 ,因为枚举通常会定义两个属性甚至多个属性。例如,code ,name ;入库保存 code , 输出 name  用于显示。而不是简单的使用枚举元素的 name  或 元素下标 。
EnumTypeHandler EnumTypeHandler 是使用枚举名单处理 Java 枚举类型。EnumTypeHandler 对应的是一个字符串。
EnumTypeHandler 通过 Enum.name 方法将其转化为字符串,通过 Enum.valueOf 将字符串转化为枚举。
EnumTypeHandler  源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package  org.apache.ibatis.type;import  java.sql.CallableStatement;import  java.sql.PreparedStatement;import  java.sql.ResultSet;import  java.sql.SQLException;public  class  EnumTypeHandler <E extends  Enum <E>> extends  BaseTypeHandler <E> {  private  final  Class<E> type;   public  EnumTypeHandler (Class<E> type)  {     if  (type == null ) {       throw  new  IllegalArgumentException ("Type argument cannot be null" );     }     this .type = type;   }   @Override    public  void  setNonNullParameter (PreparedStatement ps, int  i, E parameter, JdbcType jdbcType)  throws  SQLException {     if  (jdbcType == null ) {              ps.setString(i, parameter.name());     } else  {       ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);      }   }   @Override    public  E getNullableResult (ResultSet rs, String columnName)  throws  SQLException {     String  s  =  rs.getString(columnName);          return  s == null  ? null  : Enum.valueOf(type, s);   }   @Override    public  E getNullableResult (ResultSet rs, int  columnIndex)  throws  SQLException {     String  s  =  rs.getString(columnIndex);          return  s == null  ? null  : Enum.valueOf(type, s);   }   @Override    public  E getNullableResult (CallableStatement cs, int  columnIndex)  throws  SQLException {          String  s  =  cs.getString(columnIndex);     return  s == null  ? null  : Enum.valueOf(type, s);   } } 
 
验证枚举的 ``Enum.name()方法和Enum.valueOf()` 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  enum  SexEnum  {    MALE(1 , "男" ),     FEMAIL(2 , "女" ),     ;     private  int  code;     private  String name;     SexEnum(int  code, String name) {         this .code = code;         this .name = name;     }     public  static  void  main (String[] args)  {         String  name  =  SexEnum.MALE.name();         System.out.println(name);         SexEnum  male  =  Enum.valueOf(SexEnum.class, "MALE" );         System.out.println(male);      } } 
 
EnumOrdinalTypeHandler 枚举类型是一个数组结构,枚举元素也是数组元素,是有下标的,下标依元素所在位置先后顺序,从 0 开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package  org.apache.ibatis.type;import  java.sql.CallableStatement;import  java.sql.PreparedStatement;import  java.sql.ResultSet;import  java.sql.SQLException;public  class  EnumOrdinalTypeHandler <E extends  Enum <E>> extends  BaseTypeHandler <E> {  private  final  Class<E> type;   private  final  E[] enums;   public  EnumOrdinalTypeHandler (Class<E> type)  {     if  (type == null ) {       throw  new  IllegalArgumentException ("Type argument cannot be null" );     }          this .type = type;           this .enums = type.getEnumConstants();     if  (this .enums == null ) {       throw  new  IllegalArgumentException (type.getSimpleName() + " does not represent an enum type." );     }   }   @Override    public  void  setNonNullParameter (PreparedStatement ps, int  i, E parameter, JdbcType jdbcType)  throws  SQLException {          ps.setInt(i, parameter.ordinal());   }   @Override    public  E getNullableResult (ResultSet rs, String columnName)  throws  SQLException {     int  ordinal  =  rs.getInt(columnName);     if  (ordinal == 0  && rs.wasNull()) {       return  null ;     }          return  toOrdinalEnum(ordinal);   }   @Override    public  E getNullableResult (ResultSet rs, int  columnIndex)  throws  SQLException {     int  ordinal  =  rs.getInt(columnIndex);     if  (ordinal == 0  && rs.wasNull()) {       return  null ;     }     return  toOrdinalEnum(ordinal);   }   @Override    public  E getNullableResult (CallableStatement cs, int  columnIndex)  throws  SQLException {     int  ordinal  =  cs.getInt(columnIndex);     if  (ordinal == 0  && cs.wasNull()) {       return  null ;     }     return  toOrdinalEnum(ordinal);   }   private  E toOrdinalEnum (int  ordinal)  {     try  {       return  enums[ordinal];     } catch  (Exception ex) {       throw  new  IllegalArgumentException ("Cannot convert "  + ordinal + " to "  + type.getSimpleName() + " by ordinal value." , ex);     }   } } 
 
验证枚举元素的下标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  enum  SexEnum  {    FEMALE(2 , "女" ),     MALE(1 , "男" ),     ;     private  int  code;     private  String name;     SexEnum(int  code, String name) {         this .code = code;         this .name = name;     }     public  static  void  main (String[] args)  {         int  index  =  FEMALE.ordinal();         Class<SexEnum> sexEnumClass = SexEnum.class;         SexEnum[] enumConstants = sexEnumClass.getEnumConstants();         SexEnum  enumConstant  =  enumConstants[index];         System.out.println(enumConstant);     } } 
 
输出结果:
 
自定义枚举TypeHandler 大多数情况下,MyBatis默认的枚举类型处理类使用枚举名 称或下标 并不适用,则需要自定义枚举TypeHandler。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package  com.gxitsky.config;import  com.gxitsky.enums.SexEnum;import  org.apache.ibatis.type.JdbcType;import  org.apache.ibatis.type.MappedJdbcTypes;import  org.apache.ibatis.type.MappedTypes;import  org.apache.ibatis.type.TypeHandler;import  java.sql.CallableStatement;import  java.sql.PreparedStatement;import  java.sql.ResultSet;import  java.sql.SQLException;@MappedTypes(value = {Integer.class}) @MappedJdbcTypes(value = JdbcType.INTEGER) public  class  SexEnumTypeHandler  implements  TypeHandler <SexEnum> {    @Override      public  void  setParameter (PreparedStatement ps, int  i, SexEnum parameter, JdbcType jdbcType)  throws  SQLException {         ps.setInt(i, parameter.getCode());     }     @Override      public  SexEnum getResult (ResultSet rs, String columnName)  throws  SQLException {         int  code  =  rs.getInt(columnName);         return  SexEnum.getByCode(code);     }     @Override      public  SexEnum getResult (ResultSet rs, int  columnIndex)  throws  SQLException {         int  code  =  rs.getInt(columnIndex);         return  SexEnum.getByCode(code);     }     @Override      public  SexEnum getResult (CallableStatement cs, int  columnIndex)  throws  SQLException {         int  code  =  cs.getInt(columnIndex);         return  SexEnum.getByCode(code);     } } 
 
把映射结果集中的 sex 字段 typeHandler 改为 SexEnumTypeHandler。