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

