Mybatis之類型處理器

前言

上文Mybatis之XML如何映射到方法中講到了類型處理器,分別用在兩個地方設置參數到數據庫和從結果集中取出數據,根據不一樣的數據類型從類型註冊器裏面獲取具體的類型處理器,分別進行處理;本文將重點介紹一下類型處理器,註冊器,如何處理數據以及如何擴展。java

類型處理器

類型處理器Mybatis提供了接口TypeHandler,而且Mybatis實現了主流數據庫支持類型的處理器實現,具體有哪些類型以下所示:git

處理器 java類型 數據庫類型
BooleanTypeHandler Boolean,boolean BOOLEAN,BIT
ByteTypeHandler Byte,byte TINYINT
ShortTypeHandler Short,short SMALLINT
IntegerTypeHandler Integer,int INTEGER
LongTypeHandler Long,long BIGINT
FloatTypeHandler Float,float FLOAT
DoubleTypeHandler Double,double DOUBLE
StringTypeHandler String CHAR,VARCHAR
ClobTypeHandler String CLOB,LONGVARCHAR
NStringTypeHandler String NVARCHAR,NCHAR
NClobTypeHandler String NCLOB
ArrayTypeHandler Object ARRAY
BigDecimalTypeHandler BigDecimal REAL,DECIMAL,NUMERIC
BlobTypeHandler byte[] BLOB,LONGVARBINARY
UnknownTypeHandler Object OTHER或者未指定類型
DateTypeHandler Date TIMESTAMP
DateOnlyTypeHandler Date DATE
TimeOnlyTypeHandler Date TIME
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
EnumTypeHandler 枚舉類 VARCHAR或兼容的字符串類型

上面全部的類型處理器均可以在TypeHandlerRegistry註冊器中找到,再來看一下TypeHandler提供的接口方法:github

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

四個方法分別表示:setParameter用來設置寫入數據庫的參數,getResult用來處理結果集和存儲過程,能夠經過字段名稱和下標來處理;以上處理器並無直接實現TypeHandler,而是繼承於公共的BaseTypeHandler,以StringTypeHandler爲例只須要實現很簡單的操做:sql

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

分別表示往PreparedStatement中設置字符串參數,從ResultSet獲取指定字段名稱或者下標的字符串數據,以及從存儲過程當中獲取指定下標字符串數據。數據庫

處理器註冊器

全部處理器在啓動的時候都註冊到了TypeHandlerRegistry中,須要根據javaType或者jdbcType獲取處理器時直接在註冊器中獲取便可,註冊器中保存了相關的對應關係以下所示:apache

private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP;
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP;
  private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
  private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP ;
  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;

以上定義的幾個變量的含義大體以下:
JDBC_TYPE_HANDLER_MAP:jdbcType對應的類型處理器Map;
TYPE_HANDLER_MAP:一個javaType對應多個jdbcType類型,這也好理解好比String類型能夠對象數據庫裏面的char,varchar,clob等;而後每一個jdbcType對應各自的類型處理器;
UNKNOWN_TYPE_HANDLER:在沒有配置jdbcType的狀況下使用默認的UnknownTypeHandler處理器,此類會更新客戶端參數來自動匹配相應的處理器;
ALL_TYPE_HANDLERS_MAP:全部的類型處理器類名稱對應類型處理器;
NULL_TYPE_HANDLER_MAP:空Map,用來作斷定是否爲空的;
defaultEnumTypeHandler:枚舉處理器;segmentfault

有對應關係,這樣在設置參數和處理結果集的時候能夠直接根據javaType或者jdbcType獲取對應的類型處理器,根據具體的處理器處理相關參數。mybatis

處理數據

用到類型處理器主要在兩個地方分別是設置參數的時候和處理結果集的時候,下面分別看一下兩種場景;app

1.設置參數

在文章Mybatis之XML如何映射到方法中咱們將到了BoundSql,ParameterMapping以及處理參數的DefaultParameterHandler,看一段裏面的核心代碼:ide

TypeHandler typeHandler = parameterMapping.getTypeHandler();
 JdbcType jdbcType = parameterMapping.getJdbcType();
 if (value == null && jdbcType == null) {
     jdbcType = configuration.getJdbcTypeForNull();
 }
 try {
     typeHandler.setParameter(ps, i + 1, value, jdbcType);
 } catch (TypeException e) {
     ...省略...
 }

直接從ParameterMapping中獲取具體的類型處理器,這裏面的類型處理器是在解析xxMapper.xml根據設置的類型來決定的,下面以一個實例來講明一下:

<select id="selectBlogMap" parameterType="hashmap" resultType="blog">
        select id,title from blog where id = #{id} and author=#{author,javaType=string,jdbcType=VARCHAR}
    </select>

如上實例有個參數須要設置分別是id和author,由於id沒有指定具體的javaType和jdbcType因此解析的時候id的類型處理器是UnknownTypeHandler,而author指定了具體的類型因此能夠解析的時候指定StringTypeHandler類型處理器;那id是未知的類型處理器如何處理Long類型哪,能夠看到UnknownTypeHandler內部有一個resolveTypeHandler方法:

private TypeHandler<? extends Object> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
    TypeHandler<? extends Object> handler;
    if (parameter == null) {
      handler = OBJECT_TYPE_HANDLER;
    } else {
      handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);
      // check if handler is null (issue #270)
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = OBJECT_TYPE_HANDLER;
      }
    }
    return handler;
  }

能夠看到沒有配置類型的狀況下,直接根據傳遞過來的參數類型從類型註冊器中獲取具體的處理器,好比這裏的id是Long類型,因此最後獲取的實際處理器是LongTypeHandler;

2.處理結果集

結果集咱們能夠配置resultType和resultMap,在映射結果集的時候首先是須要經過對象工廠建立對象的,這個在上文Mybatis之對象工廠中詳細介紹了,建立完對象以後須要往屬性中設置值,至於如何獲取值就是經過TypeHandler來實現的;如何獲取對應的TypeHandler,使用resultType和resultMap有不一樣的方式:
1.resultType
根據指定的類型,結果集中的屬性映射到類中的屬性,而後獲取類中屬性的類型,能夠參考部分代碼:

final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
if (property != null && metaObject.hasSetter(property)) {
      if (resultMap.getMappedProperties().contains(property)) {
        continue;
      }
      final Class<?> propertyType = metaObject.getSetterType(property);
      if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
           final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
           autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
      } 
      ......
}

首先經過結果集中的屬性獲取到類對象中的屬性名稱,而後獲取屬性被定義的類型,經過此類型到註冊器TypeHandlerRegistry中獲取相應的處理器,最後經過調用處理器中的getResult方法獲取對應的值,把值經過反射的方式賦值給經過對象工廠建立的對象。

2.resultMap
resultMap能夠直接在xml中指定相關的類型,好比:

<resultMap id="blogResultMap" type="blog" autoMapping="true">
        <result property="id" column="id" javaType="long" jdbcType="BIGINT"/>
        <result property="title" column="title" javaType="string" jdbcType="VARCHAR"/>
    </resultMap>

直接給屬性指定javaType和jdbcType,這樣在解析xml的時候直接能夠分配相關的類型處理器;固然也能夠不指定,若是不指定的話處理方式相似resultType的方式,也須要根據resultMap中指定的type類型,獲取相關屬性的類型,而後在獲取TypeHandler。

擴展處理器

擴展處理器有兩種狀況分別是:已有的類型處理器好比StringTypeHandler,咱們建立一個新的去覆蓋它;另外就是建立一個以前沒有處理類型的處理器,好比常見的枚舉類型處理器;下面分別來擴展實現。

1.覆蓋擴展

實現本身的MyStringTypeHandler,繼承於BaseTypeHandler,以下所示:

@MappedTypes({ String.class })
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MyStringTypeHandler extends BaseTypeHandler<String> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
            throws SQLException {
        System.out.println("MyStringTypeHandler->setNonNullParameter");
        ps.setString(i, parameter);
    }
;
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        System.out.println("MyStringTypeHandler->getNullableResult");
        return rs.getString(columnName);
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        System.out.println("MyStringTypeHandler->getNullableResult");
        return rs.getString(columnIndex);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        System.out.println("MyStringTypeHandler->getNullableResult");
        return cs.getString(columnIndex);
    }
}

須要指定註解MappedTypes和MappedJdbcTypes,分別表示javaType和jdbcType;而後實現BaseTypeHandler中四個抽象方法便可;固然也能夠不用註解的方式,能夠在configuration中進行類型的配置:

<typeHandlers>
        <typeHandler javaType="string" jdbcType="VARCHAR" handler="com.mybatis.myTypeHandler.MyStringTypeHandler" />
    </typeHandlers>

指定了javaType="string"以及jdbcType="VARCHAR" 的狀況下使用MyStringTypeHandler進行處理;這樣配完以後可能有一些問題,好比參數在沒有指定jdbcType的狀況下,仍是會用默認的String類型,這是爲何了那,由於在TypeHandlerRegistry中初始化了String處理器:

register(String.class, new StringTypeHandler());

沒有指定jdbcType的狀況下會初始化null->StringTypeHandler;因此要解決這個問題,咱們還能夠繼續添加一行以下所示:

<typeHandlers>
        <typeHandler javaType="string" handler="com.mybatis.myTypeHandler.MyStringTypeHandler" />
    </typeHandlers>

2.枚舉處理器擴展

Mybatis提供了兩個默認的枚舉類型處理器分別是:EnumTypeHandler和EnumOrdinalTypeHandler;分別表示用枚舉字符串名稱做爲參數,另外一個是使用整數下標做爲參數傳遞;使用也很簡單,使用以下配置:

<typeHandler javaType="com.mybatis.vo.AuthorEnum" handler="org.apache.ibatis.type.EnumTypeHandler" />

若是默認的兩種方式不能知足,那能夠自定義本身的枚舉類型處理器,以下經過處理器經過id來獲取枚舉,代碼以下:

public class AuthorEnumTypeHandler implements TypeHandler<AuthorEnum> {

    @Override
    public void setParameter(PreparedStatement ps, int i, AuthorEnum parameter, JdbcType jdbcType) throws SQLException {
        System.out.println("AuthorEnumTypeHandler->setParameter");
        ps.setInt(i, parameter.getId());
    }

    @Override
    public AuthorEnum getResult(ResultSet rs, String columnName) throws SQLException {
        int id = Integer.valueOf(rs.getString(columnName));
        System.out.println("AuthorEnumTypeHandler->getResult");
        return AuthorEnum.getAuthor(id);
    }

    @Override
    public AuthorEnum getResult(ResultSet rs, int columnIndex) throws SQLException {
        int id = rs.getInt(columnIndex);
        System.out.println("AuthorEnumTypeHandler->getResult");
        return AuthorEnum.getAuthor(id);
    }

    @Override
    public AuthorEnum getResult(CallableStatement cs, int columnIndex) throws SQLException {
        int id = cs.getInt(columnIndex);
        System.out.println("AuthorEnumTypeHandler->getResult");
        return AuthorEnum.getAuthor(id);
    }
}

總結

本文首先介紹了Mybatis默認有哪些類型處理器,以及每種處理器對應的java類型和jdbc類型;而後介紹了註冊器是如何把保存javaType,jdbcType以及TypeHandler對應關係的;接下來介紹TypeHandler主要在兩個地方分別是設置參數的時候和處理結果集;最後經過覆蓋的方式和新建的方式來擴展處理器,並給出了實例。

完整代碼

Github

相關文章
相關標籤/搜索