开心一刻

今天去超市买饮料

老板说 5 元,我听成了“会员”

我说没有

老板跟我对视了 10 秒

然后,老板说:没有你买啥?

我说:没有就不让买?

当时的老板:

瑕疵回顾

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

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

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

发现了什么?


Mybatis

JDBC
之间有
hikari

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

思维再扩散一点,格局再打开一点,是不是就是:在
Mybatis

JDBC
之间有
数据库连接池

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

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

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

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

druid SQLFeatureNotSupportedException

mybatis-plus/issues/6666664
中提到了一个异常:
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
,往往能事半功倍

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

标签: none

添加新评论