MyBatis 类型处理器 TypeHandler

类型处理器 TypeHandler

MyBatis 中的 TypeHandler 类型处理器用于 JavaType 与 JdbcType 之间的转换,用 PreparedStatement 设置参数值,从 ResultSet 或 CallableStatement 中取出一个值。MyBatis 内置了大部分基本类型的类型处理器,所以对于基本类型可以直接处理,当我们需要处理其他类型的时候就需要自定义类型处理器。

下面源码是Integer类型的转换。一个setxxx方法,表示向PreparedStatement里面设置值。三个getxxx方法,一个是根据列名获取值,一个是根据列索引位置获取值,最后一个是存储过程。

public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
 
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setInt(i, parameter);
  }
 
  @Override
  public Integer getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getInt(columnName);
  }
 
  @Override
  public Integer getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getInt(columnIndex);
  }
 
  @Override
  public Integer getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getInt(columnIndex);
  }
}

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 接口实现的。这个接口定义了类型处理器的基本功能,接口定义如下所示。

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 方法用于把 java 对象设置到 PreparedStatement 的参数中,getResult 方法用于从 ResultSet 或 CallableStatement 中取出数据转换为 java 对象。

实际开发中,我们可以继承 org.apache.ibatis.type.BaseTypeHandler 类型来实现自定义类型处理器。这个类型是抽象类型,实现了 TypeHandler 的方法进行通用流程的封装,做了异常处理,并定义了几个类似的抽象方法,如下所示。继承 BaseTypeHandler 类型可以极大地降低开发难度。

public abstract void setNonNullParameter(PreparedStatement ps, int i, 
T parameter, JdbcType jdbcType) throws SQLException;

public abstract T getNullableResult(ResultSet rs, String columnName) 
throws SQLException;

public abstract T getNullableResult(ResultSet rs, int columnIndex) 
throws SQLException;

public abstract T getNullableResult(CallableStatement cs, int columnIndex) 
throws SQLException;

类型转换器还可以通过注解配置 java 类型和 jdbc 类型: 

@MappedTypes:注解配置 java 类型 

@MappedJdbcTypes:注解配置 jdbc 类型

例:Blob转String的类型处理器

public class TypeHandlerBlob2String extends BaseTypeHandler<String> {
  
    // 指定字符集
    private static final String DEFAULT_CHARSET = "utf-8";

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
            throws SQLException {
        ByteArrayInputStream bis;
        try {
            // 把String转化成byte流
            bis = new ByteArrayInputStream(parameter.getBytes(DEFAULT_CHARSET));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Blob Encoding Error!");
        }
        ps.setBinaryStream(i, bis, parameter.length());
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Blob blob = rs.getBlob(columnName);
        byte[] returnValue = null;
        String result = null;
        if (null != blob) {
            returnValue = blob.getBytes(1, (int) blob.length());
        }
        try {
            if (null != returnValue) {
                // 把byte转化成string
                result = new String(returnValue, DEFAULT_CHARSET);
            }
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Blob Encoding Error!");
        }
        return result;
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Blob blob = cs.getBlob(columnIndex);
        byte[] returnValue = null;
        String result = null;
        if (null != blob) {
            returnValue = blob.getBytes(1, (int) blob.length());
        }
        try {
            if (null != returnValue) {
                result = new String(returnValue, DEFAULT_CHARSET);
            }
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Blob Encoding Error!");
        }
        return result;
    }

   

    @Override
    public String getNullableResult(ResultSet rs, int columnName) throws SQLException {
        String result = null;
        Blob blob = rs.getBlob(columnName);
        byte[] returnValue = null;
        if (null != blob) {
            returnValue = blob.getBytes(1, (int) blob.length());
        }
        try {
            // 把byte转化成string
            if (null != returnValue) {
                result = new String(returnValue, DEFAULT_CHARSET);
            }
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Blob Encoding Error!");
        }
        return result;

    }
}

源码分析

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 type
    if (!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);
    }
}

TypeHandler配置到程序

把TypeHandler配置到程序中,有下面三种方法:

1.在Mapper.xml中声明

<result column="gender" jdbcType="VARCHAR" property="gender" typeHandler="com.xhx.springboot.convert.GenderEnumTypeHandler"/>


2.在mybatis配置文件中设置  

    <typeHandlers>
        <typeHandler handler="com.xhx.springboot.convert.GenderEnumTypeHandler"/>
    </typeHandlers>


3.在spring配置文件中设置,springboot项目如下

mybatis:
  type-handlers-package: com.xhx.springboot.convert

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值