都說了能不動就別動,非要去調整,出生產事故了吧 → 補充

開心一刻

  今天去超市買飲料

  老闆說 5 元,我聽成了“會員”

  我說沒有

  老闆跟我對視了 10 秒

  然後,老闆說:沒有你買啥?

  我說:沒有就不讓買?

  當時的老闆:

 瑕疵回顧

  都說了能不動就別動,非要去調整,出生產事故了吧 中有一個地方講的有瑕疵,不知道你們發現了沒有

  框住的第一句是沒問題的,但第二句是不夠嚴謹的,我自罰三耳光!

  爲什麼這麼說,我們細看下當時的異常堆棧信息

  發現了什麼?

  在 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 嗎?

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
View Code

  和 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 ,往往能事半功倍

  還是那句話:能不動就不要動,改好沒績效,改出問題要背鍋,喫力不討好,又不是不能跑

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