You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot3升级后SqlRowSet读取datetimeoffset类型报错求助

问题描述

我已将应用升级至SpringBoot 3(Hibernate 6),目前Java Instant类型在SQL Server中以datetimeoffset类型存储。我有多个通过SqlRowSet返回最后修改时间和创建时间的查询,当前问题是内部调用的checkColType方法会使用java.sql.Types类,而该类没有datetimeoffset(SQL类型码为-155)对应的字段,导致该方法抛出异常。

是否有解决方案?或者我是否需要将Java Instant对应的类型回退为旧的datetime类型?

相关代码如下:

public class RowSetMetaDataImpl implements RowSetMetaData,  Serializable {
private void checkColType(int SQLType) throws SQLException {
    try {
        Class<?> c = java.sql.Types.class;
        Field[] publicFields = c.getFields();
        int fieldValue = 0;
        for (int i = 0; i < publicFields.length; i++) {
            fieldValue = publicFields[i].getInt(c);
            if (fieldValue == SQLType) {
                return;
             }
        }
    } catch (Exception e) {
        throw new SQLException("Invalid SQL type for column");
    }
    throw new SQLException("Invalid SQL type for column");
}
}

即使在com.microsoft.sqlserver.jdbc.SSType中,DATETIMEOFFSET映射到microsoft.sql.Types.DATETIMEOFFSET,但该类型在java.sql.Types中不存在,且RowSetMetaDataImpl仅使用java.sql.Types。

代码用途:我们团队编写了多种临时原生SQL查询,将结果集写入Excel。通过SqlRowSet获取列元数据,即使无数据也会写入表头;有数据时则同时写入数据。我们无法为众多客户重写这些查询,数据写入方法如下:

public Path exportDefaultView(List<QueryExcelSheetConfig> queryExcelSheetConfigs) throws Exception {
    Workbook wb = new Workbook();
    Path temp = Files.createTempFile("temp", ".xlsx");
    try (InputStream is = Files.newInputStream(temp)) {
        wb.getWorksheets().removeAt(0);
        for (QueryExcelSheetConfig queryConfig : queryExcelSheetConfigs) {
            String sheetName = queryConfig.getSheetName().replaceAll("[^a-zA-Z0-9._]+", "_");
            Worksheet ws = wb.getWorksheets().add(sheetName);
            SqlRowSet sqlRowSet = namedParameterJdbcTemplate.queryForRowSet("dynamic query from db",
                    queryConfig.getArguments());
            SqlRowSetMetaData metaData = sqlRowSet.getMetaData();
            Cells cells = ws.getCells();
            IntStream.range(0, metaData.getColumnCount()).forEach(colNum -> {
                Cell cell = cells.get(0, colNum);
                cell.setValue(metaData.getColumnName(colNum + 1));
            });
            while (sqlRowSet.next()) {
                IntStream.range(0, metaData.getColumnCount()).forEach(colNum -> {
                    Cell cell = cells.get(sqlRowSet.getRow(), colNum);
                    setCellValue(cell, colNum + 1, sqlRowSet, metaData);
                });
            }
            wb.getWorksheets().get(sheetName).autoFitColumns();
        }
        wb.save(temp.toString());
    } finally {
        wb.dispose();
    }
    return temp;
}
解决方案

1. 修改RowSetMetaDataImpl的类型检查逻辑

直接在checkColType方法中加入对datetimeoffset类型的判断,无需修改大量业务代码:

private void checkColType(int SQLType) throws SQLException {
    try {
        // 优先判断datetimeoffset类型(类型码-155,或直接引用microsoft.sql.Types.DATETIMEOFFSET常量)
        if (SQLType == -155 || SQLType == microsoft.sql.Types.DATETIMEOFFSET) {
            return;
        }
        Class<?> c = java.sql.Types.class;
        Field[] publicFields = c.getFields();
        int fieldValue = 0;
        for (int i = 0; i < publicFields.length; i++) {
            fieldValue = publicFields[i].getInt(c);
            if (fieldValue == SQLType) {
                return;
             }
        }
    } catch (Exception e) {
        throw new SQLException("Invalid SQL type for column");
    }
    throw new SQLException("Invalid SQL type for column");
}

如果无法直接修改该类(比如属于第三方库),可以通过字节码增强工具(如ASM)在运行时修改方法逻辑。

2. 自定义SqlRowSet实现,绕过类型检查

利用Spring的扩展能力,自定义SqlRowSet实现:

  • 编写自定义RowSetMetaData实现,重写checkColType方法并加入datetimeoffset支持;
  • 通过JdbcTemplate的query方法,使用自定义RowSetExtractor返回自己实现的SqlRowSet,替换默认实现。

3. 配置JDBC驱动的类型映射

调整SQL Server JDBC驱动配置,将datetimeoffset映射到java.sql.Types中兼容的类型(如TIMESTAMP_WITH_TIMEZONE,类型码2013)。可在JDBC URL中添加参数:

jdbc:sqlserver://localhost:1433;databaseName=your_db;sendTimeAsDatetime=false;datetimeoffsetType=TIMESTAMP_WITH_TIMEZONE

注意需确认JDBC驱动版本支持该参数,不同版本参数名称可能有差异。

不建议回退到datetime类型

datetime类型不包含时区信息,而Java Instant是带UTC时区的时间戳,回退会丢失时区数据,可能引发时间显示错误、业务逻辑异常等问题,不符合Spring Boot 3+Hibernate 6的时间类型最佳实践。

内容的提问来源于stack exchange,提问作者aakash singh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 04:25:57