1. 程式人生 > >mybatis TypeHandler詳解

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的轉換了.