zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Windows下,SpringBoot JDBC无法连接的问题

2023-03-07 09:39:40 时间

问题症状

在Win7和Win10下启动时均会出现下面的错误,但是在OSX和Linux下没问题

com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 18,982 milliseconds ago.  The last packet sent successfully to the server was 18,979 milliseconds ago.
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.mysql.jdbc.Util.handleNewInstance(Util.java:403)
	at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990)
	...
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(SocketInputStream.java:210)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
	at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
	at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
	at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2966)
	at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3427)
	... 166 common frames omitted

配置文件

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
            aservice:
                driverClassName: com.mysql.jdbc.Driver
                url: jdbc:mysql://192.168.1.2:3306/aservice?useUnicode=true&characterEncoding=utf8
                username: dbuser
                password: dbuser
            bservice:
                ...
            ...
            validationQuery: SELECT 1 FROM DUAL
            testWhileIdle: true

对应的各组件版本为
spring-boot-dependencies:2.1.17.RELEASE
druid-spring-boot-starter:1.1.17
mysql-connector-java:auto --> 8.0.21

检查过程

排除网络及MySQL服务器账号问题:用客户端可以正常连接,同样的代码在OSX和Ubuntu20.04下均可正常连接。如果通过公网opnvpn连接到内网,在windows下也可以正常连接
排除mysql-connector-java版本不兼容问题:切换到8.0.16和5.1.46后问题依旧
排除mysql-connector-java自身问题:仅使用spring-jdbc + mysql-connector-java 可以正常连接

在SpringBoot:2.2.4.RELEASE + hikari的项目B上,在同样的内网环境连接此数据库,无法连接。因此问题范围可以缩小到SpringBoot上。
这个项目里出现的错误不太一样,是

201030 14:09:11275     main W xxer.hikari.pool.PoolBase#481 HikariPool-1 - Default transaction isolation level detection failed (No operations allowed after connection closed.).
201030 14:09:11279     main E er.hikari.pool.HikariPool#493 HikariPool-1 - Error thrown while acquiring connection from data source
java.sql.SQLNonTransientConnectionException: No operations allowed after connection closed.
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:110)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:73)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:73)
	at com.mysql.cj.jdbc.ConnectionImpl.setTransactionIsolation(ConnectionImpl.java:2279)
        ...
	at com.rockbb.banyan.commons.impl.CommonsBoot.main(CommonsBoot.java:14)
Caused by: com.mysql.cj.exceptions.ConnectionIsClosedException: No operations allowed after connection closed.
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
	at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105)
	at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:151)
	at com.mysql.cj.NativeSession.checkClosed(NativeSession.java:1274)
	at com.mysql.cj.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:575)
	at com.mysql.cj.jdbc.ConnectionImpl.setTransactionIsolation(ConnectionImpl.java:2226)
	... 70 common frames omitted

在项目B上,通过debug发现连接关闭这个状态是通过NativeSession.forceClose()设置的,调用栈是
com.mysql.cj.jdbc.ConnectionImpl.abortINternal()
com.mysql.cj.jdbc.ConnectionImpl.isValid()
com.zaxxer.hikari.pool.PoolBase.checkValidationSupport()
在这个方法里,判断连接是否有效有两种方法,

if (this.isUseJdbc4Validation) {
    connection.isValid(1);
} else {
    this.executeSql(connection, this.config.getConnectionTestQuery(), false);
}

出错时使用的是前一种,这里timeout是固定的1秒,在这1秒内无返回就出错了。因为超时不能修改,所以要改用第二种,可以看到这个对象初始化时这个变量的赋值为

this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;

所以只需要配置中有connection-test-query: "SELECT 1"这一项就可以了,在hikari的配置中加上这项,程序就可以正常运行了。

回到项目A,项目A使用的是DruidDatasource, 没有上面的这个参数项,配置这个不起作用。
对项目中出现异常的部分加断点debug,可以发现异常的来源是com.mysql.cj.protocol.ReadAheadInputStream.fill(), 实际的错误来源是socket read timeout,这个类里有一个doDebug变量可以控制是否输出socket读写的信息,再继续跟踪得到这个选项是traceProtocol=true, 要配置到dbUrl上。
打开这个信息后发现,实际上连接出错是在和服务器多次交互后才发生的。
继续跟踪,连接的关闭是在这个方法后发生的com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker.isValidConnection(...),这个方法里通过ping和sql查询两种方式检查连接有效性,并且查询的timeout也是1秒,所以先修改timeout试试,在druid的配置上加上

validationQueryTimeout: 30

后,debug中可以发现确实ping阻塞在那里直到超时也无返回,所以这个不能解决问题,再尝试换成另一种方式检查有效性。这个开关字段是usePingMethod,经过检查,这个参数不能通过application.yml带入,要加在java启动时的命令行,加上-Ddruid.mysql.usePingMethod=false这个参数启动后,就会通过SELECT 1去检查连接有效性,这个检测就没问题。因为超时时间也使用于这个检测,所以最终解决方案是

  • 启动命令行中加上 -Ddruid.mysql.usePingMethod=false 参数
  • application.yml的Druid配置中加上 validationQueryTimeout: 30 和 validationQuery: SELECT 1这两项