本文修订于:2022年6月11日

MyBatis typeHandlers 类型处理器

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

类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERIC 或 SHORT INTEGER
IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERIC 或 LONG INTEGER
FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERIC 或 DECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
ByteArrayTypeHandler byte[] 数据库兼容的字节流类型
BlobTypeHandler byte[] BLOB, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER 或未指定类型 
EnumTypeHandler Enumeration Type  VARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引)
EnumOrdinalTypeHandler Enumeration Type 任何兼容的 NUMERIC 或 DOUBLE 类型,存储枚举的索引(而不是名称)。 

自定义类型处理器

可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。具体的做法为实现org.apache.ibatis.type.TypeHandler接口或继承org.apache.ibatis.type.BaseTypeHandler类,然后可以选择性地将它映射到一个JDBC类型。比如:

 //ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR) 
public class ExampleTypeHandler extends BaseTypeHandler<String>
{

  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException
  { 
    ps.setString(i,parameter);
  }

  public String getNullableResult(ResultSet rs, String columnName) throws SQLException
  { 
    return rs.getString(columnName);
  }

  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException
  { 
    return rs.getString(columnIndex);
  }

  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException
  { 
    return cs.getString(columnIndex);
  }
}

并且还需要在配置文件里面加上:

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

使用这个的类型处理器将会覆盖已经存在的处理Java的String类型属性和VARCHAR参数及结果的类型处理器。要注意MyBatis不会窥探数据库元信息来决定使用哪种类型,所以必须在参数和结果映射中指明是VARCHAR类型字段,以使其能绑定到正确的类型处理器上。这是因为MyBatis直到语句被执行才清楚数据类型。

通过类型处理器的泛型,MyBatis可以得知该类型处理器的Java类型,不过这种行为可以通过两种方法改变:

  • 在类型处理器的配置元素(typeHandler element)上增加一个javaType属性。比如,javaType="String"
  • 在类型处理器的类上(TypeHandler class)增加一个@MappedTypes注解来指定与其关联的Java类型列表。如果在javaType属性中也设置了,则注解方式将被忽略。

可以通过两种方式来指定被关联的JDBC类型:

  • 在类型处理器的配置元素上增加一个javaType属性。比如:javaType="VARCHAR"
  • 在类型处理器的类上(TypeHandler class)增加一个@MappedJdbcTypes注解来指定与其关联的JDBC类型列表。如果在javaType属性中也同时指定,则注解方式将被忽略。

最后,还可以让MyBatis查找类型处理器:

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>

注意在使用自动检索(autodiscovery)功能的时候,只能通过注解的方式来指定JDBC类型。

你能创建一个泛型类型处理器,它可以处理多于一个类。为达到此目的,需要增加一个接收该类作为参数的构造器,这样在构造一个类型处理器的时候MyBatis就会传入一个具体的类。

//GenericTypeHandler.java 
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E>
{ 
  private Class<E> type; 
  public GenericTypeHandler(Class<E> type)
  {
    if (type == null) 
        throw new IllegalArgumentException("Type argument cannot be null");
    this.type = type;
  }
  ...
}

处理枚举类型

EnumTypeHandler和EnumOrdinalTypeHandler都是泛型处理器(generic TypeHandlers),接下来的部分详细探讨。

若想映射枚举类型Enum,则需要从EnumTypeHandler或者EnumOrdinalTypeHandler中选一个来使用。比如说我们想存储近似值时用到的舍入模式。默认情况下,MyBatis会利用EnumTypeHandler来把Enum值转换成对应的名字。注意EnumTypeHandler在某种意义上来说是比较特别的,其他的处理器只针对某个特定的类,而它不同,它会处理任意继承了Enum的类。不过,我们可能不想存储名字,相反我们的DBA会坚持使用整型值代码。

在配置文件中把EnumOrdinalTypeHandler加到typeHandlers中即可,这样每个RoundingMode将通过他们的序数值来映射成对应的整型。

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>

但是怎么样能将同样的Enum既映射成字符串又映射成整型呢?

自动映射器(auto-mapper)会自动选用EnumOrdinalTypeHandler来处理,所以如果我们想用普通的EnumTypeHandler,就非要为那些SQL语句显示地设置要用到的类型处理器不可。

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
    <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="funkyNumber" property="funkyNumber"/>
        <result column="roundingMode" property="roundingMode"/>
    </resultMap>

    <select id="getUser" resultMap="usermap">
        select * from users
    </select>
    <insert id="insert"> 
        insert into users (id, name, funkyNumber, roundingMode) values
            ( #{id}, #{name}, #{funkyNumber},
        #{roundingMode})
    </insert>
        
    <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="funkyNumber" property="funkyNumber"/>
        <result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
    </resultMap>
    <select id="getUser2" resultMap="usermap2">
        select * from users2
    </select>
    <insert id="insert2"> 
        insert into users2 (id, name, funkyNumber, roundingMode) values(
            #{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=
        org.apache.ibatis.type.EnumTypeHandler})
    </insert>
</mapper>

注意,这里的select语句强制使用resultMap来代替resultType。

标签: none

已有 2 条评论

  1. Tliu Tliu

    你为何如此优秀

  2. 小兵 小兵

    能发下群主的微信吗群添加不进去了

添加新评论