MyBatis的类型处理器TypeHandler与自定义实现

简介: MyBatis的类型处理器TypeHandler与自定义实现

通过前面的分析 ,我们可以看到MyBatis在进行参数设置与结果取值的时候,都应用到了各种TypeHandler。这里我们回顾一下设置参数与获取结果的代码。

为参数赋值如下图所示:

从结果取值之DefaultResultSetHandler.getPropertyMappingValue方法如下所示

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

【1】 类型处理器(typeHandlers)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。


提示 从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API) 。


类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERIC 或 SMALLINT
IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERIC 或 BIGINT
FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERIC 或 DECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] 数据库兼容的字节流类型
BlobTypeHandler byte[] BLOB, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER 或未指定类型
EnumTypeHandler Enumeration Type VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandler Enumeration Type 任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandler java.lang.String SQLXML
InstantTypeHandler java.time.Instant TIMESTAMP
LocalDateTimeTypeHandler java.time.LocalDateTime TIMESTAMP
LocalDateTypeHandler java.time.LocalDate DATE
LocalTimeTypeHandler java.time.LocalTime TIME
OffsetDateTimeTypeHandler java.time.OffsetDateTime TIMESTAMP
OffsetTimeTypeHandler java.time.OffsetTime TIME
ZonedDateTimeTypeHandler java.time.ZonedDateTime TIMESTAMP
YearTypeHandler java.time.Year INTEGER
MonthTypeHandler java.time.Month INTEGER
YearMonthTypeHandler java.time.YearMonth VARCHAR 或 LONGVARCHAR
JapaneseDateTypeHandler java.time.chrono.JapaneseDate DATE

你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。


具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。


实例代码如下:

// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {
  @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);
  }
}
<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler" />
</typeHandlers>
<!-- typeHandler指定JavaType jdbcType -->
<typeHandler handler="org.mybatis.example.ExampleTypeHandler" 
javaType="java.lang.String" jdbcType="VARCHAR" />

使用上述的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器。


要注意 MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。


通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:


在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType="String");

在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略。


可以通过两种方式来指定关联的 JDBC 类型:


在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR");

在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。

当在 ResultMap 中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类型], jdbcType=null 的组合来选择一个类型处理器。


这意味着使用 @MappedJdbcTypes 注解可以限制类型处理器的作用范围,并且可以确保,除非显式地设置,否则类型处理器在 ResultMap 中将不会生效。 如果希望能在 ResultMap 中隐式地使用类型处理器,那么设置 @MappedJdbcTypes 注解的 includeNullJdbcType=true 即可。


然而从 Mybatis 3.4.0 开始,如果某个 Java 类型只有一个注册的类型处理器,即使没有设置 includeNullJdbcType=true,那么这个类型处理器也会是 ResultMap 使用 Java 类型时的默认处理器。


最后,可以让 MyBatis 帮你查找类型处理器:

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>

注意在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型。

你可以创建能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一个接受该类的 class 作为参数的构造器,这样 MyBatis 会在构造一个类型处理器实例的时候传入一个具体的类。

//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {
  private Class<E> type;
  public GenericTypeHandler(Class<E> type) {
    if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
    this.type = type;
  }
  //...

EnumTypeHandler 和 EnumOrdinalTypeHandler 都是泛型类型处理器,我们将会在接下来的部分详细探讨。


日期和时间的处理


JDK1.8以前一直是个头疼的问题。我们通常使用JSR310规范领导者Stephen Colebourne创建的Joda-Time来操作。1.8已经实现全部的JSR310规范了。日期时间处理上,我们可以使用MyBatis基于JSR310(Date and Time API)编写的各种日期时间类型处理器。MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的。

【2】实现TypeHandler的两种方式

① 继承BaseTypeHandler抽象类


BaseTypeHanler extends TypeReference<T> implements TypeHandler<T> ,我们只需要实现BaseTypeHanler 四个抽象方法即可。

如下所示,是mybatis提供的BigInteger类型转换。

public class BigIntegerTypeHandler extends BaseTypeHandler<BigInteger> {
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, BigInteger parameter, JdbcType jdbcType) throws SQLException {
    ps.setBigDecimal(i, new BigDecimal(parameter));
  }
  @Override
  public BigInteger getNullableResult(ResultSet rs, String columnName) throws SQLException {
    BigDecimal bigDecimal = rs.getBigDecimal(columnName);
    return bigDecimal == null ? null : bigDecimal.toBigInteger();
  }
  @Override
  public BigInteger getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    BigDecimal bigDecimal = rs.getBigDecimal(columnIndex);
    return bigDecimal == null ? null : bigDecimal.toBigInteger();
  }
  @Override
  public BigInteger getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    BigDecimal bigDecimal = cs.getBigDecimal(columnIndex);
    return bigDecimal == null ? null : bigDecimal.toBigInteger();
  }
}


② 实现TypeHandler接口

TypeHandler接口如下所示,这里的T表示参数类型。

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


自定义实现实例如下:

public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {
   //定义当前数据如何保存到数据库中
  @Override
  public void setParameter(PreparedStatement ps, int i, EmpStatus parameter,
      JdbcType jdbcType) throws SQLException {
    System.out.println("要保存的状态码:"+parameter.getCode());
    ps.setString(i, parameter.getCode().toString());
  }
  @Override
  public EmpStatus getResult(ResultSet rs, String columnName)
      throws SQLException {
    //需要根据从数据库中拿到的枚举的状态码返回一个枚举对象
    int code = rs.getInt(columnName);
    System.out.println("从数据库中获取的状态码:"+code);
    EmpStatus status = EmpStatus.getEmpStatusByCode(code);
    return status;
  }
  @Override
  public EmpStatus getResult(ResultSet rs, int columnIndex)
      throws SQLException {
    int code = rs.getInt(columnIndex);
    System.out.println("从数据库中获取的状态码:"+code);
    EmpStatus status = EmpStatus.getEmpStatusByCode(code);
    return status;
  }
  @Override
  public EmpStatus getResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    int code = cs.getInt(columnIndex);
    System.out.println("从数据库中获取的状态码:"+code);
    EmpStatus status = EmpStatus.getEmpStatusByCode(code);
    return status;
  }
}

③ 配置TypeHandler

第一种,在全局配置文件配置

<typeHandlers>
  <typeHandler handler="com.mybatis.typehandler.MyEnumEmpStatusTypeHandler"  
  javaType="com.mybatis.bean.EmpStatus"/>
</typeHandlers>

第二种,结果映射时配置具体属性

<resultMap type="com.mybatis.bean.Employee" id="MyEmp">
  <id column="id" property="id"/>
  <result column="empStatus" property="empStatus" typeHandler="XXXXX"/>
</resultMap>


第三种,保存数据时具体参数配置

<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
  insert into tbl_employee(last_name,email,gender,empStatus) 
  values
  (#{lastName},#{email},#{gender},#{empStatus,typeHandler=xxxx})
</insert>


需要注意的是,如果在具体属性配置,请务必保证查询和保存配置的类型转换器一致。




目录
相关文章
|
2月前
|
SQL Java 数据库连接
|
5月前
|
SQL Java 数据库连接
mybatis常见分页技术和自定义分页原理实战
mybatis常见分页技术和自定义分页原理实战
|
5月前
|
SQL Java 数据库连接
Mybatis源码分析系列之第三篇:Mybatis的操作类型对象
Mybatis源码分析系列之第三篇:Mybatis的操作类型对象
|
4月前
|
SQL Java 数据库连接
Mybatis之核心配置文件详解、默认类型别名、Mybatis获取参数值的两种方式
【1月更文挑战第3天】 一、核心配置文件详解 二、默认的类型别名 三、MyBatis的增删改查 四、MyBatis获取参数值的两种方式 1、单个字面量类型的参数 2、多个字面量类型的参数 3、map集合类型的参数 4、实体类类型的参数 5、使用@Param标识参数
62 2
Mybatis之核心配置文件详解、默认类型别名、Mybatis获取参数值的两种方式
|
4月前
|
SQL Oracle 关系型数据库
整合Mybatis-Plus高级,Oracle 主键Sequence,Sql 注入器实现自定义全局操作
整合Mybatis-Plus高级,Oracle 主键Sequence,Sql 注入器实现自定义全局操作
92 0
|
4月前
|
SQL 数据库
在mybatis-plus怎么使用自定义的sql语句
在mybatis-plus怎么使用自定义的sql语句
67 0
|
24天前
|
存储 关系型数据库 MySQL
【mybatis-plus】Springboot+AOP+自定义注解实现多数据源操作(数据源信息存在数据库)
【mybatis-plus】Springboot+AOP+自定义注解实现多数据源操作(数据源信息存在数据库)
|
24天前
|
Java 关系型数据库 MySQL
【mybatis-plus】自定义多数据源,动态切换数据源事务失效问题
【mybatis-plus】自定义多数据源,动态切换数据源事务失效问题
【mybatis-plus】自定义多数据源,动态切换数据源事务失效问题
|
2月前
|
SQL Java 数据库连接
Mybatis查询的时候BigDecimal类型的值查询失效的解决办法
Mybatis查询的时候BigDecimal类型的值查询失效的解决办法
|
4月前
|
Java 关系型数据库 MySQL
结合springboot+mybatis-plus+lombok,自定义Page封装类
结合springboot+mybatis-plus+lombok,自定义Page封装类
54 0