The conversion to class java.time.LocalDateTime is unsupported.

mssql-jdbc 對於java8的LocalDate,LocalTime,LocalDatetime的轉換異常

先說下解決方法: 1. 降級Mybatis至3.5.0及以下; 2. 或升級mssql-jdbc驅動至7.1.0及以上
原文地址:https://blog.csdn.net/qq_24505485/article/details/103540439

解決過程

之前實體類使用的是Timestamp 來存儲時間,沒有錯,後來使用了LocalDatetime來存儲也沒錯,再之後我把Mybatis從3.5.0升級到了3.5.1之後,(mssql-jdbc的版本是com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8)LocalDatetime的轉換就會拋異常:

com.microsoft.sqlserver.jdbc.SQLServerException: The conversion to class java.time.LocalDateTime is unsupported.
at com.microsoft.sqlserver.jdbc.SQLServerResultSet.getObject(SQLServerResultSet.java:2249)
at com.microsoft.sqlserver.jdbc.SQLServerResultSet.getObject(SQLServerResultSet.java:2267)
at com.zaxxer.hikari.pool.HikariProxyResultSet.getObject(HikariProxyResultSet.java)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.logging.jdbc.ResultSetLogger.invoke(ResultSetLogger.java:69)
at com.sun.proxy.$Proxy300.getObject(Unknown Source)
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)

原因在於Mybatis3.5.1出現了一個不向後兼容的更新mybatis-3.5.1更新日誌

There is a backward incompatible change.

  • Because of the fix for #1478 , LocalDateTypeHandler, LocalTimeTypeHandler and LocalDateTimeTypeHandler now require a JDBC driver that supports JDBC 4.2 API.
    Also, these type handlers no longer work with Druid. See #1516

大體意思就是Local***TypeHandler需要一個支持JDBC 4.2的JDBC驅動

Mybatis3.5.0 LocalDateTimeTypeHandler.class部分源碼:

  @Override
  public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    Timestamp timestamp = rs.getTimestamp(columnIndex);
    return getLocalDateTime(timestamp);
  }

  @Override
  public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    Timestamp timestamp = cs.getTimestamp(columnIndex);
    return getLocalDateTime(timestamp);
  }

  private static LocalDateTime getLocalDateTime(Timestamp timestamp) {
    if (timestamp != null) {
      return timestamp.toLocalDateTime();
    }
    return null;
  }

通過源碼可知,Mybatis3.5.0及以前應該是將LocalDatetime轉換爲Timestamp然後再調用驅動來獲取數據;
Mybatis3.5.1 LocalDateTimeTypeHandler.class部分源碼:

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

  @Override
  public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getObject(columnIndex, LocalDateTime.class);
  }

  @Override
  public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getObject(columnIndex, LocalDateTime.class);
  }

這裏是直接用驅動的getObject方法來獲取對應數據;
找到mssql驅動的實現方法,源碼如下:

    public <T> T getObject(int index,
            Class<T> type) throws SQLException {
        loggerExternal.entering(getClassNameLogging(), "getObject", index);
        checkClosed();
        Object returnValue;
        if (type == String.class) {
            returnValue = getString(index);
        }
        else if (type == Byte.class) {
            byte byteValue = getByte(index);
            returnValue = wasNull() ? null : byteValue;
        }
        else if (type == Short.class) {
            short shortValue = getShort(index);
            returnValue = wasNull() ? null : shortValue;
        }
        else if (type == Integer.class) {
            int intValue = getInt(index);
            returnValue = wasNull() ? null : intValue;
        }
        else if (type == Long.class) {
            long longValue = getLong(index);
            returnValue = wasNull() ? null : longValue;
        }
        else if (type == BigDecimal.class) {
            returnValue = getBigDecimal(index);
        }
        else if (type == Boolean.class) {
            boolean booleanValue = getBoolean(index);
            returnValue = wasNull() ? null : booleanValue;
        }
        else if (type == java.sql.Date.class) {
            returnValue = getDate(index);
        }
        else if (type == java.sql.Time.class) {
            returnValue = getTime(index);
        }
        else if (type == java.sql.Timestamp.class) {
            returnValue = getTimestamp(index);
        }
        else if (type == microsoft.sql.DateTimeOffset.class) {
            returnValue = getDateTimeOffset(index);
        }
        else if (type == UUID.class) {
            // read binary, avoid string allocation and parsing
            byte[] guid = getBytes(index);
            returnValue = guid != null ? Util.readGUIDtoUUID(guid) : null;
        }
        else if (type == SQLXML.class) {
            returnValue = getSQLXML(index);
        }
        else if (type == Blob.class) {
            returnValue = getBlob(index);
        }
        else if (type == Clob.class) {
            returnValue = getClob(index);
        }
        else if (type == NClob.class) {
            returnValue = getNClob(index);
        }
        else if (type == byte[].class) {
            returnValue = getBytes(index);
        }
        else if (type == Float.class) {
            float floatValue = getFloat(index);
            returnValue = wasNull() ? null : floatValue;
        }
        else if (type == Double.class) {
            double doubleValue = getDouble(index);
            returnValue = wasNull() ? null : doubleValue;
        }
        else {
            // if the type is not supported the specification says the should
            // a SQLException instead of SQLFeatureNotSupportedException
            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionTo"));
            Object[] msgArgs = {type};
            throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET, null);
        }
        loggerExternal.exiting(getClassNameLogging(), "getObject", index);
        return type.cast(returnValue);
    }

發現mssql-jdbc-6.4.0.jre8.jar並不支持LocalDatetime的轉換,所以會拋出The conversion to class java.time.LocalDateTime is unsupported.
但是我去mssql-jdbc官方網站看到的是mssql-jdbc:6.4.0.jre8是全面支持JDBC 4.2的

Microsoft JDBC Driver 6.4 for SQL Server 完全符合 JDBC 規範 4.1 和 4.2。 根據 Java 版本兼容性命名 6.4 包中的 jar。 例如,6.4 包中的 mssql-jdbc-6.4.0.jre8.jar 文件必須與 Java 8 配合使用。

JDBC 4.2 新增了對 JSR 310(Date and Time API,JSR 310爲Java提供了一個新的、改進的日期和時間API)的支持;也就是說JDBC 4.2 可以直接將Java8的LocalDatetime直接和數據庫數據對應上;

所以說應該是他們的bug了。
由於mssql-jdbc是在開源在github上的,所以可以到相關repo找找有沒有人提過該bug,以及在哪個版本被修復了。
直接檢索jdbc 4.2 發現第二條issue就是和LocalDatetime相關的#749
修復後發佈的版本號:[7.1.0] Preview Release
msql-jdbc-4.2bug

解決方法

該bug在7.1.0版本中被修復,所以至此有2種方式能解決問題

  1. 降級Mybatis至3.5.0及以下
  2. 或升級mssql-jdbc驅動至7.1.0及以上
    mssql-jdbc的驅動可以在mvnrepository查找:https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc

我是那種必定選擇升級的人😄;

解決問題的思路

由於最初是因爲升級Mybatis版本而引起的,所以在看了異常信息之後就去找Mybatis的更新日誌了,通過更新日誌才一步步發現這個原來是mssql-jdbc驅動的bug;
還有本來第一反應是百度搜索相關報錯,結果沒找到,所以這裏詳細記錄一下這次bug的解決過程;

原文地址:https://blog.csdn.net/qq_24505485/article/details/103540439

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章