2018-11-08 · Develop

MyBatis 丢失时间精度-自定义类型转换器

在使用 Mybatis 的过程中除了遇到 Generator 只能生成 insert 接口 的问题,还遇到了时间格式转换丢失"时分秒"精度的问题。

情况是这样的,数据库系统使用的是 Oracle 11g
数据库中有个世家字段设置为 JdbcType.DATE 类型也就是 java.sql.Date ,而实体类使用的 java.util.Date
在查询操作的时候取出来的时分秒都变成了 00:00:00 丢失了精度。

分析

我们知道 JdbcType 与 JavaType 之间进行转换都是使用的 typeHandler 进行的,通过 Debug 发现转换走的是 DateOnlyTypeHandler 这个类的如下方法

  @Override
  public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
    java.sql.Date sqlDate = rs.getDate(columnName);
    if (sqlDate != null) {
      return new Date(sqlDate.getTime());
    }
    return null;
  }

在第一步 java.sql.Date sqlDate = rs.getDate(columnName); 获取出来就已经丢失了时分秒。所以我们拿到的结果就不对。

MyBatis 允许我们进行自定义的处理,需要实现 TypeHandler 接口或者是继承 BaseTypeHandler 类。

下面是 TypeHandler 的接口源码

// T是泛型,专指javaType
public interface TypeHandler<T> {
    // 通过PreparedStatement 对象进行设置SQL参数 
    // i是参数在SQL的下标
    // parameter是参数
    // jdbcType是数据库类型
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
 
    // 从JDBC结果集中获取数据进行转换,要么使用列名(columnName)要么使用下标(columnIndex)获取数据库的数据
    T getResult(ResultSet var1, String var2) throws SQLException;
 
    T getResult(ResultSet var1, int var2) throws SQLException;
    // 存储过程专用的
    T getResult(CallableStatement var1, int var2) throws SQLException;
}

下面是 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;

解决方案

有了上面的分析我门就来解决这个问题, 首先仿照 DateOnlyTypeHandler 来自定义一个 TypeHandler

@MappedTypes(Date.class)
@MappedJdbcTypes(JdbcType.DATE)
public class DateOnlyTypeHandler extends BaseTypeHandler<Date> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
        ps.setDate(i, new java.sql.Date(parameter.getTime()));
    }

    @Override
    public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // java.sql.Date sqlDate = rs.getDate(columnName);
        java.sql.Timestamp sqlDate = rs.getTimestamp(columnName);
        if (sqlDate != null) {
            return new Date(sqlDate.getTime());
        }
        return null;
    }

    @Override
    public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        // java.sql.Date sqlDate = rs.getDate(columnIndex);
        java.sql.Timestamp sqlDate = rs.getTimestamp(columnIndex);
        if (sqlDate != null) {
            return new Date(sqlDate.getTime());
        }
        return null;
    }

    @Override
    public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        // java.sql.Date sqlDate = cs.getDate(columnIndex);
        java.sql.Timestamp sqlDate = cs.getTimestamp(columnIndex);
        if (sqlDate != null) {
            return new Date(sqlDate.getTime());
        }
        return null;
    }
}
  1. @MappedJdbcTypes 定义的是 JdbcType 类型,这里的类型不可自己随意定义,必须要是枚举类 org.apache.ibatis.type.JdbcType 所枚举的数据类型。
  2. @MappedTypes 定义的是 JavaType 的数据类型,描述了哪些 Java 类型可被拦截。
  3. 在我们启用了我们自定义的这个TypeHandler之后,数据的读写都会被这个类所过滤
  4. 在 setNonNullParameter 方法中,我们重新定义要写往数据库的数据。
  5. 在另外三个方法中我们将从数据库读出的数据类型进行转换。

然后在 SpringBoot 中设置 mybatis 使用我们的 TypeHandler 包就可以了。

mybatis.type-handlers-package=com.zuojl.lomall.common.handler

参考文档
关于mybatis中typeHandler的两个案例
MyBatis配置typeHandler类型转换器 (自定义类型转换器)