MyBatis自定义类型处理器 TypeHandler
在項目開發中經常會遇到一個問題:
當我們在javabean中自定義了枚舉類型或者其它某個類型,但是在數據庫中存儲時往往需要轉換成數據庫對應的類型,并且在從數據庫中取出來時也需要將數據庫類型轉換為javabean中的對應類型。比如:javabean中字段類型為Date,數據庫中存儲的是varchar類型;javabean中字段類型是Enum,數據庫中存儲的是String或者Integer。
因為有大量類似數據的轉換,手動轉換類型進行存儲和查詢已經過于麻煩。MyBatis為我們提供了解決辦法:TypeHandler類型處理器。
類型處理器 TypeHandler
MyBatis 中的 TypeHandler 類型處理器用于 JavaType 與 JdbcType 之間的轉換,用于 PreparedStatement 設置參數值和從 ResultSet 或 CallableStatement 中取出一個值。MyBatis 內置了大部分基本類型的類型處理器,所以對于基本類型可以直接處理,當我們需要處理其他類型的時候就需要自定義類型處理器。
MyBatis 內置的 TypeHandler
在 MyBatis 的 TypeHandlerRegistry 類型中,可以看到內置的類型處理器。內置處理器比較多,這里整理常見的一些。
BooleanTypeHandler:用于 java 類型 boolean,jdbc 類型 bit、boolean
ByteTypeHandler:用于 java 類型 byte,jdbc 類型 TINYINT
ShortTypeHandler:用于 java 類型 short,jdbc 類型 SMALLINT
IntegerTypeHandler:用于 INTEGER 類型
LongTypeHandler:用于 long 類型
FloatTypeHandler:用于 FLOAT 類型
DoubleTypeHandler:用于 double 類型
StringTypeHandler:用于 java 類型 string,jdbc 類型 CHAR、VARCHAR
ArrayTypeHandler:用于 jdbc 類型 ARRAY
BigDecimalTypeHandler:用于 java 類型 BigDecimal,jdbc 類型 REAL、DECIMAL、NUMERIC
DateTypeHandler:用于 java 類型 Date,jdbc 類型 TIMESTAMP
DateOnlyTypeHandler:用于 java 類型 Date,jdbc 類型 DATE
TimeOnlyTypeHandler:用于 java 類型 Date,jdbc 類型 TIME
對于常見的 Enum 類型,內置了 EnumTypeHandler 進行 Enum 名稱的轉換和 EnumOrdinalTypeHandler 進行 Enum 序數的轉換。這兩個類型處理器沒有在 TypeHandlerRegistry 中注冊,如果需要使用必須手動配置。
自定義 TypeHandler
自定義類型處理器是通過實現 org.apache.ibatis.type.TypeHandler 接口實現的。這個接口定義了類型處理器的基本功能,接口定義如下所示。
其中 setParameter 方法用于把 java 對象設置到 PreparedStatement 的參數中,getResult 方法用于從 ResultSet(根據列名或者索引位置獲取) 或 CallableStatement(根據存儲過程獲取) 中取出數據轉換為 java 對象。
實際開發中,我們可以繼承 org.apache.ibatis.type.BaseTypeHandler 類型來實現自定義類型處理器。這個類型是抽象類型,實現了 TypeHandler 的方法進行通用流程的封裝,做了異常處理,并定義了幾個類似的抽象方法,如下所示。繼承 BaseTypeHandler 類型可以極大地降低開發難度。
類型轉換器還可以通過注解配置 java 類型和 jdbc 類型:
@MappedTypes:注解配置 java 類型
@MappedJdbcTypes:注解配置 jdbc 類型
自定義枚舉類型處理器示例:
自定義一個枚舉基類
import org.springframework.util.ReflectionUtils; import java.lang.reflect.Field;/*** @author: liumengbing* @date: 2019/05/20 15:37**/ public interface BaseEnum {String DEFAULT_VALUE_NAME = "value";String DEFAULT_LABEL_NAME = "label";default Integer getValue() {Field field = ReflectionUtils.findField(this.getClass(), DEFAULT_VALUE_NAME);if (field == null)return null;try {field.setAccessible(true);return Integer.parseInt(field.get(this).toString());} catch (IllegalAccessException e) {throw new RuntimeException(e);}}default String getLabel() {Field field = ReflectionUtils.findField(this.getClass(), DEFAULT_LABEL_NAME);if (field == null)return null;try {field.setAccessible(true);return field.get(this).toString();} catch (IllegalAccessException e) {throw new RuntimeException(e);}}static <T extends Enum<T>> T valueOfEnum(Class<T> enumClass, Integer value) {if (value == null)throw new IllegalArgumentException("DisplayedEnum value should not be null");if (enumClass.isAssignableFrom(com.zfkr.qianyue.common.enums.BaseEnum.class))throw new IllegalArgumentException("illegal DisplayedEnum type");T[] enums = enumClass.getEnumConstants();for (T t: enums) {com.zfkr.qianyue.common.enums.BaseEnum displayedEnum = (com.zfkr.qianyue.common.enums.BaseEnum)t;if (displayedEnum.getValue().equals(value))return (T) displayedEnum;}throw new IllegalArgumentException("cannot parse integer: " + value + " to " + enumClass.getName());}static <T> T valueOfEnum1(T[] enums, Integer value) {for (T t: enums) {com.zfkr.qianyue.common.enums.BaseEnum displayedEnum = (com.zfkr.qianyue.common.enums.BaseEnum)t;if (displayedEnum.getValue().equals(value))return (T) displayedEnum;}throw new IllegalArgumentException("cannot parse integer: " + value + " to " );} }定義一個枚舉類Enum1
/*** @author: liumengbing* @date: 2019/05/20 15:34**/ public enum Enum1 implements BaseEnum{UNAUDITED("未審核",0),AUDIT("待審核",1),AUDITED("已審核",2);private String label;private Integer value;Enum1(String label,Integer value){this.label = label;this.value = value;}public Integer getValue() {return value;}public String getLabel() {return label;}public static Enum1 getByValue(Integer value){for(Enum1 enum1 : values()){if (enum1.getValue() == value) {return enum1;}}return null;}}自定義枚舉類型處理器
/*** @author: liumengbing* @date: 2019/05/20 15:34**/ @MappedTypes(value = { Enum1.class, Enum2.class}) public class EnumTypeHandler extends BaseTypeHandler<BaseEnum> {private Class<BaseEnum> type;public EnumTypeHandler() {}public EnumTypeHandler(Class<BaseEnum> type) {if (type == null) throw new IllegalArgumentException("Type argument cannot be null");this.type = type;}@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, BaseEnum parameter, JdbcType jdbcType) throws SQLException {ps.setInt(i, parameter.getValue());}@Overridepublic BaseEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {return convert(rs.getInt(columnName));}@Overridepublic BaseEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return convert(rs.getInt(columnIndex));}@Overridepublic BaseEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return convert(cs.getInt(columnIndex));}private BaseEnum convert(int status) {BaseEnum[] objs = type.getEnumConstants();for (BaseEnum em : objs) {if (em.getValue() == status) {return em;}}return null;} }Javabean,Dao層,mapper層處理忽略,將自定義的類型處理器配置到程序中即可。
把TypeHandler配置到程序中有三種方法:
1.在Mapper.xml中聲明
<result column="enum1" jdbcType="INTEGER" property="enum1" typeHandler="com.xxx.handler.EnumTypeHandler"/>2.在mybatis配置文件中設置
<typeHandlers><typeHandler handler="com.xxx.handler.EnumTypeHandler"/></typeHandlers>3.在springboot的yml配置文件中設置類型處理器所在的包名
mybatis:type-handlers-package: com.xxx.handler源碼分析
MyBatis 啟動后首先把 typeHandler 注冊進去。首先嘗試讀取 MappedTypes 注解,如果有這個注解定義了 java 類型,則把這個類型處理器注冊到相應的 java 類型的處理器中。如果沒有使用注解,但是繼承了 TypeReference 類型,比如前面提到的 BaseTypeHandler,則通過 TypeReference 的接口獲取原始類型注冊到相應的 java 類型的處理器中。如果實在是獲取不到 java 類型,則按照無類型處理。
public <T> void register(TypeHandler<T> typeHandler) {boolean mappedTypeFound = false;MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);if (mappedTypes != null) {for (Class<?> handledType : mappedTypes.value()) {register(handledType, typeHandler);mappedTypeFound = true;}}// @since 3.1.0 - try to auto-discover the mapped typeif (!mappedTypeFound && typeHandler instanceof TypeReference) {try {TypeReference<T> typeReference = (TypeReference<T>) typeHandler;register(typeReference.getRawType(), typeHandler);mappedTypeFound = true;} catch (Throwable t) {// maybe users define the TypeReference with a different type and are not assignable, so just ignore it}}if (!mappedTypeFound) {register((Class<T>) null, typeHandler);}}MyBatis 在預處理語句設置參數時調用 TypeHandler 進行 java 對象到 jdbc 的 PreparedStatement 參數值的轉換。以下為其中一個調用片段。
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) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } catch (SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); }MyBatis 查詢數據庫完成后,調用 TypeHandler 的方法讀取數據轉換成 java 對象。以下為其中一個調用片段。
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)throws SQLException {if (propertyMapping.getNestedQueryId() != null) {return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);} else if (propertyMapping.getResultSet() != null) {addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?return DEFERED;} else {final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);return typeHandler.getResult(rs, column);} }參考資料:
mybatis中幾種typeHandler的定義使用
MyBatis 類型處理器 TypeHandler
總結
以上是生活随笔為你收集整理的MyBatis自定义类型处理器 TypeHandler的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jdk1.8新特性(五)——Stream
- 下一篇: 由防重复点击引发的幂等性问题思考