简体   繁体   中英

Mybatis not handling ZonedDateTime

I have columns in a database table of type DATE which I'd like mybatis to convert to java.time.ZonedDateTime but it seems that the built in ZonedDateTimeHandler is not working. I get the following exception

Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'UPDATED_DATE' from result set.  Cause: java.sql.SQLSyntaxErrorException: incompatible data type in conversion
    at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:87)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createUsingConstructor(DefaultResultSetHandler.java:711)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createByConstructorSignature(DefaultResultSetHandler.java:694)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createResultObject(DefaultResultSetHandler.java:658)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createResultObject(DefaultResultSetHandler.java:631)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue(DefaultResultSetHandler.java:398)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValuesForSimpleResultMap(DefaultResultSetHandler.java:355)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValues(DefaultResultSetHandler.java:329)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSet(DefaultResultSetHandler.java:302)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:195)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
    at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:325)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:89)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)
    ... 36 common frames omitted
Caused by: java.sql.SQLSyntaxErrorException: incompatible data type in conversion
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCResultSet.getObject(Unknown Source)
    at org.hsqldb.jdbc.JDBCResultSet.getObject(Unknown Source)
    at com.zaxxer.hikari.pool.HikariProxyResultSet.getObject(HikariProxyResultSet.java)
    at org.apache.ibatis.type.ZonedDateTimeTypeHandler.getNullableResult(ZonedDateTimeTypeHandler.java:38)
    at org.apache.ibatis.type.ZonedDateTimeTypeHandler.getNullableResult(ZonedDateTimeTypeHandler.java:28)
    at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:85)
    ... 53 common frames omitted
Caused by: org.hsqldb.HsqlException: incompatible data type in conversion
    at org.hsqldb.error.Error.error(Unknown Source)
    at org.hsqldb.error.Error.error(Unknown Source)
    ... 60 common frames omitted

It turned out that the default ZonedDateTimeTypeHandler assumes that the jdbc driver knows how to handle ZonedDateTime which is NOT the case with hsqldb or oracle

public class ZonedDateTimeTypeHandler extends BaseTypeHandler<ZonedDateTime> {
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, ZonedDateTime parameter, JdbcType jdbcType) throws SQLException {
    ps.setObject(i, parameter);
  }

  @Override
  public ZonedDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getObject(columnName, ZonedDateTime.class);
  }
  ...
}

I fixed this with a custom handler that uses setTimestamp(...) and getTimestamp(...) under the hood

@AllArgsConstructor
public class ZonedDateTimeTypeHandler extends BaseTypeHandler<ZonedDateTime> {
    private final ZoneId zoneId;

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, ZonedDateTime parameter, JdbcType jdbcType) throws SQLException {
        ps.setTimestamp(i, Timestamp.from(parameter.toInstant()));
    }

    @Override
    public ZonedDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp ts = rs.getTimestamp(columnName);
        return ts == null ? null : ts.toInstant().atZone(zoneId);
    }

    @Override
    public ZonedDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Timestamp ts = rs.getTimestamp(columnIndex);
        return ts == null ? null : ts.toInstant().atZone(zoneId);
    }

    @Override
    public ZonedDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Timestamp ts = cs.getTimestamp(columnIndex);
        return ts == null ? null : ts.toInstant().atZone(zoneId);
    }
}

Which I registered using the following code

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) {
        ZoneId zoneId = ZoneId.of("UTC");
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("default", transactionFactory, dataSource);
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(environment);
        configuration.getTypeHandlerRegistry().register(ZonedDateTime.class, new ZonedDateTimeTypeHandler(zoneId));
        configuration.addMapper(MyMapper.class);
        return new SqlSessionFactoryBuilder().build(configuration);
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM