前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >都说了能不动就别动,非要去调整,出生产事故了吧 → 补充

都说了能不动就别动,非要去调整,出生产事故了吧 → 补充

作者头像
青石路
发布2024-02-27 09:05:33
1340
发布2024-02-27 09:05:33
举报
文章被收录于专栏:开发技术开发技术

瑕疵回顾

都说了能不动就别动,非要去调整,出生产事故了吧 中有一个地方讲的有瑕疵,不知道你们发现了没有

  框住的第一句是没问题的,但第二句是不够严谨的,我自罚三耳光!

  为什么这么说,我们细看下当时的异常堆栈信息

  发现了什么?

  在 Mybatis 与 JDBC 之间有 hikari

hikari 是什么,一个性能极高的数据库连接池,它是可以有自己的想法的!

  思维再扩散一点,格局再打开一点,是不是就是:在 Mybatis 与 JDBC 之间有 数据库连接池

  哪些说不用 数据库连接池 的小伙伴,你最好想清楚了再说

  用关系型数据库,而不用数据库连接池的项目多吗?仔细回忆回忆

  回到框住的第二句,严谨的说法应该是:而是交由下游组件

  至于下游组件是 Hikari ,还是 Druid ,亦或是其他的,是不是都囊括进来了?是不是就严谨了?

druid SQLFeatureNotSupportedException

mybatis-plus/issues/1114 中提到了一个异常: java.sql.SQLFeatureNotSupportedException

  基于 druid 1.1.16 触发的异常

  我们调整下代码

  1、引入 druid 依赖

  2、修改数据源类型(默认的 hikari 可以不用配置 type )

  数据库数据源就已经切成 druid 了

  我们将 mysql-connector-java 版本调回到最初的 5.1.26 , Mybatis-Plus 仍使用 3.1.1

  运行 com.qsl.OrderTest#orderListAllTest ,此时的异常是什么,还是 Conversion not supported for type java.time.LocalDateTime 吗?

代码语言:javascript
复制
org.springframework.dao.InvalidDataAccessApiUsageException: Error attempting to get column 'pay_time' from result set.  Cause: java.sql.SQLFeatureNotSupportedException
; null; nested exception is java.sql.SQLFeatureNotSupportedException

    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:73)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
    at com.sun.proxy.$Proxy54.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)
    at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForMany(MybatisMapperMethod.java:158)
    at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:76)
    at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:62)
    at com.sun.proxy.$Proxy60.selectList(Unknown Source)
    at com.qsl.OrderTest.orderListAllTest(OrderTest.java:33)
    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.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: java.sql.SQLFeatureNotSupportedException
    at com.alibaba.druid.pool.DruidPooledResultSet.getObject(DruidPooledResultSet.java:1771)
    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.$Proxy73.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)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.applyAutomaticMappings(DefaultResultSetHandler.java:521)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue(DefaultResultSetHandler.java:402)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValuesForSimpleResultMap(DefaultResultSetHandler.java:354)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValues(DefaultResultSetHandler.java:328)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSet(DefaultResultSetHandler.java:301)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:194)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
    at com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor.doQuery(MybatisSimpleExecutor.java:67)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:83)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
    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.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
    ... 39 more

  和 hikari 对比下

  可以看到,差别还是比较大的

  从异常堆栈信息,我们得知,调用栈是: Mybatis -> 数据库连接池 -> mysql-connector-java

  当数据库连接池是 druid 1.1.16 时,在调用栈的第二环( druid )就异常了

  而当数据库连接池是 hikari 3.4.5 时,在调用栈的第三环才异常,而在第二环( hikari )并未异常

  那就来看看 druid 为何会异常

  为何异常

  相信看了 都说了能不动就别动,非要去调整,出生产事故了吧 的小伙伴,能够很快定位到关键代码

  对,你想的没错,就是从异常堆栈中找关键位置

  我们就从 LocalDateTimeTypeHandler.java:38 开始,来看看异常是怎么产生的

  打个断点,然后再 debug 下

  按 F7 ,进入 org.apache.ibatis.logging.jdbc.ResultSetLogger#invoke

  红框框住的代码,大家能看懂吗?我给大家拆分下

method 信息如下

rs 对象

method.invoke(rs, params); 作用是不是明显了?

  就是反射调用 DruidPooledResultSet 的方法: getObject(String columnLabel, Class<T> type)

  跟进去看下该方法的具体实现

  哦豁,直接抛出异常 SQLFeatureNotSupportedException

  原因是不是找到了: druid 1.1.16 不支持根据JAVA类型获取列值

  如何修复

  1、降低 Mybatis 版本

  将 Mybatis 版本降低至 3.5.0 或以下

  因为项目中用的是 Mybatis-Plus ,我们将其降至 3.1.0

  运行 com.qsl.OrderTest#orderListAllTest ,没异常,结果也正确

  此时各个组件的版本: Mybatis-Plus 3.1.0 (即 Mybatis 3.5.0 ), druid 1.1.16 , mysql-connector-java 5.1.26

  为何将 Mybatis 的版本调整至 3.5.0 就可以了?

  这其实跟 Mybatis 3.5.1 对 LocalDateTimeTypeHandler.java 的调整有关

Mybatis 3.5.0 依赖下游组件的 getTimestamp(String columnLabel) 或 getTimestamp(int columnIndex)

  而 Mybatis 3.5.1 依赖下游组件的 getObject(int columnIndex, Class<T> type) 或 getObject(String columnLabel, Class<T> type)

  2、升级 druid 版本

  升级到哪个版本,这个需要看 druid 从哪个版本开始支持 LocalDateTime

  根据官方说明,从 1.1.18 开始支持

  我们来看下 1.1.18 相较于上一个版本( 1.1.16 ,没有 1.1.17 ),对 DruidPooledResultSet 调整了什么

  我们将 Mybatis-Plus 改回成 3.1.1 ( Mybatis 3.5.1 ),然后将 druid 升级到 1.1.18

  再执行 com.qsl.OrderTest#orderListAllTest ,你会发现还是有异常!!!

  但是先别慌,该异常

  是不是很眼熟?

  不是在 都说了能不动就别动,非要去调整,出生产事故了吧 已经解决过了吗?

  那怎么修?

  有小伙伴会说:这个我会,将 mysql-connector-java 升级到 5.1.37

  就不能一步到位升级到 5.1.42 ?, 5.1.37 有 NullPointerException 呀!!!

  此时各个组件的版本: Mybatis-Plus 3.1.1 (即 Mybatis 3.5.1 ), druid 1.1.18 , mysql-connector-java 5.1.42

Hikari 为何没问题

  此刻相信大家会有一个问题:为何 hikari 没有 druid 的那个问题( SQLFeatureNotSupportedException )

  我们来分析下,hikari 版本是 3.4.5

  它的 HikariProxyResultSet 有实现 getObject(int columnIndex, Class<T> type) 和 getObject(String columnLabel, Class<T> type)

  具体实现交给了下游,也就是交给了 mysql-connector-java

  那它是一开始就是这么实现的,还是在 3.4.5 或之前的某个版本调整成这样的了?

  因为 HikariProxyResultSet 是动态生成的,没有现成的源代码,我也想帮你们分析,可我暂时做不到呀

  等我了解了 HikariCP 动态代理实现机制,我再来给你们分析,暂时算我欠你们的!

  你们要实在是觉的不爽,来打我呀

总结

  遇到异常不要害怕,异常堆栈是很有用的信息

  遇到开源组件的问题, github 上搜它的相关 issue ,往往能事半功倍

  还是那句话:能不动就不要动,改好没绩效,改出问题要背锅,吃力不讨好,又不是不能跑

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-02-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 瑕疵回顾
  • druid SQLFeatureNotSupportedException
    •   为何异常
      •   如何修复
      • Hikari 为何没问题
      • 总结
      相关产品与服务
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档