先说下解决方法: 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的转换就会抛异常:

  1. com.microsoft.sqlserver.jdbc.SQLServerException: The conversion to class java.time.LocalDateTime is unsupported.
  2. at com.microsoft.sqlserver.jdbc.SQLServerResultSet.getObject(SQLServerResultSet.java:2249)
  3. at com.microsoft.sqlserver.jdbc.SQLServerResultSet.getObject(SQLServerResultSet.java:2267)
  4. at com.zaxxer.hikari.pool.HikariProxyResultSet.getObject(HikariProxyResultSet.java)
  5. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  6. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  7. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  8. at java.lang.reflect.Method.invoke(Method.java:498)
  9. at org.apache.ibatis.logging.jdbc.ResultSetLogger.invoke(ResultSetLogger.java:69)
  10. at com.sun.proxy.$Proxy300.getObject(Unknown Source)
  11. at org.apache.ibatis.type.LocalDateTimeTypeHandler.getNullableResult(LocalDateTimeTypeHandler.java:38)
  12. at org.apache.ibatis.type.LocalDateTimeTypeHandler.getNullableResult(LocalDateTimeTypeHandler.java:28)
  13. at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:81)

原因在于Mybatis3.5.1出现了一个不向后兼容的更新mybatis-3.5.1更新日志

  1. There is a backward incompatible change.
  2. Because of the fix for #1478 , LocalDateTypeHandler, LocalTimeTypeHandler and LocalDateTimeTypeHandler now require a JDBC driver that supports JDBC 4.2 API.
  3. Also, these type handlers no longer work with Druid. See #1516

大体意思就是Local*TypeHandler需要一个支持JDBC 4.2的JDBC驱动
Mybatis3.5.0 LocalDateTimeTypeHandler.class部分源码:

  1. @Override
  2. public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
  3. Timestamp timestamp = rs.getTimestamp(columnIndex);
  4. return getLocalDateTime(timestamp);
  5. }
  6. @Override
  7. public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
  8. Timestamp timestamp = cs.getTimestamp(columnIndex);
  9. return getLocalDateTime(timestamp);
  10. }
  11. private static LocalDateTime getLocalDateTime(Timestamp timestamp) {
  12. if (timestamp != null) {
  13. return timestamp.toLocalDateTime();
  14. }
  15. return null;
  16. }

通过源码可知,Mybatis3.5.0及以前应该是将LocalDatetime转换为Timestamp然后再调用驱动来获取数据;
Mybatis3.5.1 LocalDateTimeTypeHandler.class部分源码:

  1. @Override
  2. public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
  3. return rs.getObject(columnName, LocalDateTime.class);
  4. }
  5. @Override
  6. public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
  7. return rs.getObject(columnIndex, LocalDateTime.class);
  8. }
  9. @Override
  10. public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
  11. return cs.getObject(columnIndex, LocalDateTime.class);
  12. }

这里是直接用驱动的getObject方法来获取对应数据;
找到mssql驱动的实现方法,源码如下:

  1. public <T> T getObject(int index,
  2. Class<T> type) throws SQLException {
  3. loggerExternal.entering(getClassNameLogging(), "getObject", index);
  4. checkClosed();
  5. Object returnValue;
  6. if (type == String.class) {
  7. returnValue = getString(index);
  8. }
  9. else if (type == Byte.class) {
  10. byte byteValue = getByte(index);
  11. returnValue = wasNull() ? null : byteValue;
  12. }
  13. else if (type == Short.class) {
  14. short shortValue = getShort(index);
  15. returnValue = wasNull() ? null : shortValue;
  16. }
  17. else if (type == Integer.class) {
  18. int intValue = getInt(index);
  19. returnValue = wasNull() ? null : intValue;
  20. }
  21. else if (type == Long.class) {
  22. long longValue = getLong(index);
  23. returnValue = wasNull() ? null : longValue;
  24. }
  25. else if (type == BigDecimal.class) {
  26. returnValue = getBigDecimal(index);
  27. }
  28. else if (type == Boolean.class) {
  29. boolean booleanValue = getBoolean(index);
  30. returnValue = wasNull() ? null : booleanValue;
  31. }
  32. else if (type == java.sql.Date.class) {
  33. returnValue = getDate(index);
  34. }
  35. else if (type == java.sql.Time.class) {
  36. returnValue = getTime(index);
  37. }
  38. else if (type == java.sql.Timestamp.class) {
  39. returnValue = getTimestamp(index);
  40. }
  41. else if (type == microsoft.sql.DateTimeOffset.class) {
  42. returnValue = getDateTimeOffset(index);
  43. }
  44. else if (type == UUID.class) {
  45. // read binary, avoid string allocation and parsing
  46. byte[] guid = getBytes(index);
  47. returnValue = guid != null ? Util.readGUIDtoUUID(guid) : null;
  48. }
  49. else if (type == SQLXML.class) {
  50. returnValue = getSQLXML(index);
  51. }
  52. else if (type == Blob.class) {
  53. returnValue = getBlob(index);
  54. }
  55. else if (type == Clob.class) {
  56. returnValue = getClob(index);
  57. }
  58. else if (type == NClob.class) {
  59. returnValue = getNClob(index);
  60. }
  61. else if (type == byte[].class) {
  62. returnValue = getBytes(index);
  63. }
  64. else if (type == Float.class) {
  65. float floatValue = getFloat(index);
  66. returnValue = wasNull() ? null : floatValue;
  67. }
  68. else if (type == Double.class) {
  69. double doubleValue = getDouble(index);
  70. returnValue = wasNull() ? null : doubleValue;
  71. }
  72. else {
  73. // if the type is not supported the specification says the should
  74. // a SQLException instead of SQLFeatureNotSupportedException
  75. MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionTo"));
  76. Object[] msgArgs = {type};
  77. throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET, null);
  78. }
  79. loggerExternal.exiting(getClassNameLogging(), "getObject", index);
  80. return type.cast(returnValue);
  81. }

发现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


image.png

解决方法

该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的解决过程;