mybatis TypeHandler詳解

1.TypeHandler概念
TypeHandler,類型轉換器,在mybatis中用於實現java類型和JDBC類型的相互轉換.mybatis使用prepareStatement來進行參數設置的時候,需要通過typeHandler將傳入的java參數設置成合適的jdbc類型參數,這個過程實際上是通過調用PrepareStatement不同的set方法實現的;在獲取結果返回之後,也需要將返回的結果轉換成我們需要的java類型,這時候是通過調用ResultSet對象不同類型的get方法時間的;所以不同類型的typeHandler其實就是調用PrepareStatement和ResultSet的不同方法來進行類型的轉換,有些時候會在調用PrepareStatement和ResultSet的相關方法之前,可以對傳入的參數進行一定的處理.
當我們沒有指定typeHandler的時候mybatis會根據傳入參數的類型和返回值的類型調用默認的typeHandler進行處理.對於一個typeHandler需要配置java類型(javaType)和JDBC類型(jdbcType),typeHandler的作用就是實現這兩種類型的轉換,在傳入的參數爲指定的Java類型時,將其轉換爲指定的JDBC類型,當返回值爲指定JDBC類型時將其轉換爲配置的Java類型.

2.mybatis默認定義的TypeHandler
mybatis默認定義了一批TypeHandler,正常情況下這些TypeHandler就可以滿足我們的使用了.mybatis通過TypeHandlerRegister來管理TypeHandler
所以在這個類裏面可以看到所有定義好的typeHandler,下面是源碼.

private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
  private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
  private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = new HashMap<JdbcType, TypeHandler<?>>();

  public TypeHandlerRegistry() {
    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());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(Reader.class, new ClobReaderTypeHandler());
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, UNKNOWN_TYPE_HANDLER);
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    // mybatis-typehandlers-jsr310
    try {
      // since 1.0.0
      register("java.time.Instant", "org.apache.ibatis.type.InstantTypeHandler");
      register("java.time.LocalDateTime", "org.apache.ibatis.type.LocalDateTimeTypeHandler");
      register("java.time.LocalDate", "org.apache.ibatis.type.LocalDateTypeHandler");
      register("java.time.LocalTime", "org.apache.ibatis.type.LocalTimeTypeHandler");
      register("java.time.OffsetDateTime", "org.apache.ibatis.type.OffsetDateTimeTypeHandler");
      register("java.time.OffsetTime", "org.apache.ibatis.type.OffsetTimeTypeHandler");
      register("java.time.ZonedDateTime", "org.apache.ibatis.type.ZonedDateTimeTypeHandler");
      // since 1.0.1
      register("java.time.Month", "org.apache.ibatis.type.MonthTypeHandler");
      register("java.time.Year", "org.apache.ibatis.type.YearTypeHandler");
      // since 1.0.2
      register("java.time.YearMonth", "org.apache.ibatis.type.YearMonthTypeHandler");
      register("java.time.chrono.JapaneseDate", "org.apache.ibatis.type.JapaneseDateTypeHandler");

    } catch (ClassNotFoundException e) {
      // no JSR-310 handlers
    }

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }

可以看到對於一個Java類型是可以有多個JDBC類型相對應的,所以會存在多個TypeHandler,在這種情況下需要根據傳入參數的javaType以及其在數據庫中對應JdbcType一起來選定一個TypeHandler進行處理(mybatis如何知道每個字段對應的數據庫字段類型的?).下面來看一個具體的TypeHandler的實現:實現Java中Date類型和jdbc中JdbcType.Time類型轉換的TimeOnlyTypeHandler.JDBC中的Time類型只記錄時分秒,所以如果我們傳入一個代表2017-11-21 11:55:59的Date對象,那麼數據庫中存儲的時間是11:55:59.下面看下具體的源碼實現:

package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.util.Date;

/**
 * @author Clinton Begin
 */
public class TimeOnlyTypeHandler extends BaseTypeHandler<Date> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setTime(i, new Time(parameter.getTime()));
  }

  @Override
  public Date getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    java.sql.Time sqlTime = rs.getTime(columnName);
    if (sqlTime != null) {
      return new Date(sqlTime.getTime());
    }
    return null;
  }

  @Override
  public Date getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    java.sql.Time sqlTime = rs.getTime(columnIndex);
    if (sqlTime != null) {
      return new Date(sqlTime.getTime());
    }
    return null;
  }

  @Override
  public Date getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    java.sql.Time sqlTime = cs.getTime(columnIndex);
    if (sqlTime != null) {
      return new Date(sqlTime.getTime());
    }
    return null;
  }
}

3.使用自定義TypeHandler
雖然大部分時候mybatis提供的typeHandler已經夠用了,但總有些情況下需要我們自己定義TypeHandler.下面通過一個實例來研究如何自己定義和使用TypeHandler.
現在的場景是:首先是建立了一個person表存儲個人信息,這個表裏有一欄hobbys是記錄個人愛好的,愛好包括足球,排球,游泳之類的,在Java的Person對象中對應的是一個String類型的list,而在數據庫中是以逗號分隔的字符串表示的,心事如”足球,排球,游泳”,所以現在需要定義一個TypeHandler實現在插入數據和查詢數據時string和list類型的相互轉換.自定義一個TypeHandler需要繼承TypeHandler T接口,T是傳入的java類型,並需要使用@MappedTypes定義需要被攔截的java類型@MappedJdbcTypes配置jdbc類型,這裏的JdbcType必須org.apache.ibatis.type.JdbcType中的枚舉類型,然後還需要實現TypeHandler接口中一系列的get set方法,具體實現的代碼如下:

package com.sankuai.lkl.typeHandler;

import com.sun.deploy.util.StringUtils;
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;
import java.util.Arrays;
import java.util.List;


@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({List.class})
public class ListTypeHandler implements TypeHandler<List<String>> {

    @Override
    public void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
        String hobbys = StringUtils.join(parameter, ",");
        try {
            ps.setString(i, hobbys);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String hobbys = cs.getString(columnIndex);
        return Arrays.asList(hobbys.split(","));
    }

    @Override
    public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
        return Arrays.asList(rs.getString(columnIndex).split(","));
    }

    @Override
    public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
        return Arrays.asList(rs.getString(columnName).split(","));
    }
}

自定義完成之後就需要在mybatis-config.xml文件中配置以註冊到mybatis中

<typeHandlers>
        <typeHandler jdbcType="VARCHAR" javaType="list"
                     handler="com.sankuai.lkl.typeHandler.ListTypeHandler"/>
</typeHandlers>

但註冊完成之後也仍然不能起作用,因爲還需要標識那些參數和返回的結果是需要使用這個TypeHandler進行處理的;具體來說,在插入數據和對返回結果進行處理的時候,可以對參數配置javaType和jdbcType或直接配置typeHandler屬性來進行標識
下面是插入數據時標識用指定TypeHandler進行處理

<insert id="insertPerson" parameterType="person">
        INSERT INTO person (id,name,sex,hobbys,data_time) values(#{id},#{name},#{sex},#{hobbys,typeHandler=com.sankuai.lkl.typeHandler.ListTypeHandler},#{date})
</insert>

下面是標識對返回的結果用指定TypeHandler進行處理

<resultMap id="personMap" type="person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="sex" column="sex"/>
        <result property="hobbys" column="hobbys" typeHandler="com.sankuai.lkl.typeHandler.ListTypeHandler"/>
        <result property="date" column="data_time"/>
    </resultMap>

這樣進行配置之後就可以使用自定義的TypeHandler處理javaType和JdbcType的轉換了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章