自定义typeHandler背景

比如保存到数据库时,有以下需求:

1.有个枚举类型的值,想要保存到数据库为字符串或整数

2.Date类型存入数据库为毫秒数

3.对象中的集合(List)属性保存数据库为{xxx,xxx,xxx}的格式,读取出来自动转车List

传统的读取操作也能达到这个功能,但必须自己代码实现逻辑,工作量较大,这样就可以使用Mybatis下的自定义typeHandler的功能。

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

自定义typeHandler实现

1.实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型。比如:

// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
//@MappedTypes({String.class}) //泛型有此注释功能
public class ExampleTypeHandler extends BaseTypeHandler<String> { //如果不用泛型,可以加@MappedTypes注释来匹配需要被拦截的Java类型

  @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);
  }
}

2.注册handler

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

3.配置Mapper.xml

 ①在resultMap标签中加入typeHandler

<resultMap id="testResultMap" type="com.lhl.mybatis.beans.Test">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="nums" jdbcType="INTEGER" property="nums" />
    <result column="name" jdbcType="VARCHAR" property="name" typeHandler="com.lhl.mybatis.typehandler.CustomTypeHandler"/>
  </resultMap>

②在查询结果集中指定自定义resultMap

<select id="selectById" parameterType="java.lang.Integer" resultMap="testResultMap">
    SELECT * FROM test WHERE id=#{id}
  </select>

查询结果时,如果调用String类型的数据,就会到自定义typeHandler的get方法中处理自定义逻辑

如果想要在插入时加入自定义typeHandler,如何处理呢?以下两种方式等效

①在插入时配置jdbcType, 测试时只需要jdbcType配置就可以触发自定义typeHandler

<insert id="insert" parameterType="com.lhl.mybatis.beans.Test" useGeneratedKeys="true" keyProperty="id">
    insert into test(id, nums, name)
    values (#{id,jdbcType=INTEGER},#{nums,jdbcType=INTEGER},#{name,jdbcType=VARCHAR,javaType=java.lang.String})
  </insert>

②在插入时指定typeHandler,注意不要引号

<insert id="insert" parameterType="com.lhl.mybatis.beans.Test" useGeneratedKeys="true" keyProperty="id">
    insert into test(id, nums, name)
    values (#{id,jdbcType=INTEGER},#{nums,jdbcType=INTEGER},#{name,typeHandler=com.lhl.mybatis.typehandler.CustomTypeHandler})
  </insert>

============================================================================

最后引用官网说明:

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

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

  • 在类型处理器的配置元素(typeHandler element)上增加一个 javaType 属性(比如:javaType="String");
  • 在类型处理器的类上(TypeHandler class)增加一个 @MappedTypes 注解来指定与其关联的 Java 类型列表。 如果在 javaType

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

  • 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR");
  • 在类型处理器的类上(TypeHandler class)增加一个 @MappedJdbcTypes 注解来指定与其关联的 JDBC 类型列表。 如果在 jdbcType

当决定在ResultMap中使用某一TypeHandler时,此时java类型是已知的(从结果类型中获得),但是JDBC类型是未知的。 因此Mybatis使用javaType=[TheJavaType], jdbcType=null的组合来选择一个TypeHandler。 这意味着使用@MappedJdbcTypes注解可以限制TypeHandler的范围,同时除非显式的设置,否则TypeHandler在ResultMap中将是无效的。 如果希望在ResultMap中使用TypeHandler,那么设置@MappedJdbcTypes注解的includeNullJdbcType=true即可。 然而从Mybatis 3.4.0开始,如果只有一个注册的TypeHandler来处理Java类型,那么它将是ResultMap使用Java类型时的默认值(即使没有includeNullJdbcType=true)。