Mybatis内置ZonedDateTimeTypeHandler处理DATE字段报类型转换异常
异常产生原因
- 数据库
DATE类型本身仅存储年月日信息,不包含时分秒、时区属性,而java.time.ZonedDateTime是携带时区偏移量的完整日期时间类型,二者语义上存在信息差,无法直接对等转换。 - Mybatis内置的
ZonedDateTimeTypeHandler底层直接调用JDBC的resultSet.getObject(列索引, ZonedDateTime.class)方法取值,该方法要求数据库字段类型必须兼容JDBC标准的TIMESTAMP_WITH_TIMEZONE类型。当前使用的HSQLDB驱动检测到字段是DATE类型时,会直接抛出类型不兼容的转换错误,也就是日志中看到的incompatible data type in conversion异常。 - 即使更换其他数据库驱动,直接用内置的ZonedDateTime处理器映射DATE字段也会存在问题:JDBC驱动不会为无时间、无时区的DATE值自动补全时分秒、时区信息,转换结果不可控。
解决方案
可根据实际业务场景选择以下任意一种方案:
方案1:自定义类型处理器(推荐,无需修改现有字段/实体类型)
自定义适配DATE到ZonedDateTime转换的TypeHandler,手动补全时间、时区信息:
import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.MappedTypes; import java.sql.*; import java.time.ZoneId; import java.time.ZonedDateTime; @MappedTypes(ZonedDateTime.class) @MappedJdbcTypes(JdbcType.DATE) public class DateToZonedDateTimeTypeHandler extends BaseTypeHandler<ZonedDateTime> { // 按需指定业务使用的时区,例如固定东八区可写为ZoneId.of("Asia/Shanghai") private static final ZoneId APP_ZONE = ZoneId.systemDefault(); @Override public void setNonNullParameter(PreparedStatement ps, int i, ZonedDateTime parameter, JdbcType jdbcType) throws SQLException { // 写入数据库时取日期部分转成java.sql.Date存储 ps.setDate(i, Date.valueOf(parameter.toLocalDate())); } @Override public ZonedDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException { Date sqlDate = rs.getDate(columnName); return sqlDate == null ? null : sqlDate.toLocalDate().atStartOfDay(APP_ZONE); } @Override public ZonedDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException { Date sqlDate = rs.getDate(columnIndex); return sqlDate == null ? null : sqlDate.toLocalDate().atStartOfDay(APP_ZONE); } @Override public ZonedDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { Date sqlDate = cs.getDate(columnIndex); return sqlDate == null ? null : sqlDate.toLocalDate().atStartOfDay(APP_ZONE); } }
注册处理器的方式:
- SpringBoot整合Mybatis场景下,给该类加上
@Component注解即可自动注册;非SpringBoot场景可在Mybatis配置文件的typeHandlers节点下添加该类的全限定名完成全局注册。 - 如果仅需要对单个字段生效,可在对应resultMap的字段映射中,通过
typeHandler属性指定该类的全限定名。
方案2:调整类型匹配(语义最匹配,无额外配置)
如果业务场景下该字段只需要存储年月日,不需要时间、时区信息,直接把实体类对应字段的类型从ZonedDateTime修改为java.time.LocalDate即可。Mybatis内置的LocalDateTypeHandler原生支持数据库DATE类型和LocalDate的双向转换,不需要任何额外配置,也不会出现类型不兼容问题。
方案3:修改数据库字段类型(适合需要完整时区时间的场景)
如果业务上确实需要存储带时区的完整时间,把数据库对应字段的类型从DATE修改为TIMESTAMP WITH TIME ZONE(不同数据库语法略有差异,例如PostgreSQL对应类型为timestamptz,MySQL可使用TIMESTAMP配合服务端时区配置),这种场景下Mybatis内置的ZonedDateTimeTypeHandler可以直接正常工作,不需要自定义处理器。
内容的提问来源于stack exchange,提问作者lance-java
相关产品推荐
相关产品推荐

