mybatis TypeHandler详解

来源:互联网 发布:itc智能网络广播 编辑:程序博客网 时间:2024/06/11 22:28

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的转换了.