近日在使用Mybatis的时候出现了这么一个奇怪的报错:SQLFeatureNotSupportedException: getObject with type
现场还原:
Mybatis: 3.5.2+
JDBC: shardingsphere-jdbc 4.0.1-
Error attempting to get column 'update_time' from result set. Cause: java.sql.SQLFeatureNotSupportedException: getObject with type
; getObject with type; nested exception is java.sql.SQLFeatureNotSupportedException: getObject with type
org.springframework.dao.InvalidDataAccessApiUsageException: Error attempting to get column 'update_time' from result set. Cause: java.sql.SQLFeatureNotSupportedException: getObject with type
; getObject with type; nested exception is java.sql.SQLFeatureNotSupportedException: getObject with type
at org.springframework.jdbc.support.SQLExceptionSubclassTranslator.doTranslate(SQLExceptionSubclassTranslator.java:96)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:74)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
at com.sun.proxy.$Proxy193.selectOne(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:159)
...
Caused by: java.sql.SQLFeatureNotSupportedException: getObject with type
at org.apache.shardingsphere.shardingjdbc.jdbc.unsupported.AbstractUnsupportedOperationResultSet.getObject(AbstractUnsupportedOperationResultSet.java:223)
at org.apache.ibatis.type.LocalDateTimeTypeHandler.getNullableResult(LocalDateTimeTypeHandler.java:38)
at org.apache.ibatis.type.LocalDateTimeTypeHandler.getNullableResult(LocalDateTimeTypeHandler.java:28)
at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:81)
让我们来找一下原因,定位到错误提示的最后一句,发现问题出在Mybatis的LocalDateTimeTypeHandler这个类型转化器上,代码如下:
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
return (LocalDateTime)rs.getObject(columnName, LocalDateTime.class);
}}
Mybatis的TypeHandelr试图调用ResultSet.getObject()获取结果,但是JDBC却说我们不支持这玩意儿?
我们去Mybatis那边看一下,发现了一些端倪。下方是Mybatis的更新报告。
(LocalDateTimeTypeHandler要求JDBC支持4.2 API,是不是我们的JDBC版本低了)
JDBC对应的API(Since 1.8)
再去看一眼我们的JDBC(shardingsphere),好家伙,这玩意儿居然才JDK7,LocalDateTime是JDK8才出来的日期类型,现在问题找到了,我们的Mybatis版本太高了,或者说JDBC的版本太低了。
解决方案:
- 降低Mybatis版本至3.5.1及以下(不推荐)
- 升级JDBC版本(shardingsphere)(
可是这已经是最新版本了,此路不通)
近日,SharingSphere发布了4.1.0版本,终于升级到了JDK8,这个BUG也就不复存在了。
- 改写LocalDateTimeTypeHandler(既然JDBC不支持(LocalDateTime)rs.getObject(),那我们就不用好了,用自定义的转换器去替换Mybatis的转换器)。如下:
@Component
//定义转换器支持的JAVA类型
@MappedTypes(LocalDateTime.class)
//定义转换器支持的数据库类型
@MappedJdbcTypes(value = JdbcType.TIMESTAMP, includeNullJdbcType = true)
public class CustomLocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATETIME);
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)
throws SQLException {
if (parameter != null) {
ps.setString(i, dateTimeFormatter.format(parameter));
}
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
String target = rs.getString(columnName);
if (StringUtil.isEmpty(target)) {
return null;
}
return LocalDateTime.parse(target, dateTimeFormatter);
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String target = rs.getString(columnIndex);
if (StringUtil.isEmpty(target)) {
return null;
}
return LocalDateTime.parse(target, dateTimeFormatter);
}
@Override
public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String target = cs.getString(columnIndex);
if (StringUtil.isEmpty(target)) {
return null;
}
return LocalDateTime.parse(target, dateTimeFormatter);
}
以上代码并不完全,LocalDateTypeHandler也需要改写,但是高度雷同,就不贴出了。