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