alibaba / druid

阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池
https://github.com/alibaba/druid/wiki
Apache License 2.0
27.97k stars 8.58k forks source link

在使用mybatis cursor 和 druid 的时候,druid 关闭连接会重新设置fetchsize为0,导致java.sql.SQLException: No operations allowed after statement closed #3641

Open qzeng2490 opened 4 years ago

qzeng2490 commented 4 years ago

druid-spring-boot-starter 版本是1.1.21 spring-boot-starter-parent 版本2.2.1.RELEASE mybatis-spring-boot-starter 版本2.1.1 mysql-connector-java 版本8.0.18

主要的代码如下

@Mapper
public interface TestTableMapper {
    @Options(fetchSize = Integer.MIN_VALUE,resultSetType = ResultSetType.FORWARD_ONLY)
    @Select({
            "<script> " +
                "SELECT * " +
                "FROM test_table limit 1000" +
            "</script>"
    })
    Cursor<TestTable> getEntities();
}
@GetMapping(path = "/test")
    @Transactional
    public void test()  {
        Cursor<TestTable> entities = testTableMapper.getEntities();
        Iterator<TestTable> iterator =  entities.iterator();
        while (iterator.hasNext()) {
            iterator.next();
        }
    }
配置如下
spring:
  profiles:
    active: dev
  application:
    name: demo
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource #表明使用Druid连接池
    driverClassName: com.mysql.cj.jdbc.Driver    #mysql驱动
    url:  jdbc:mysql://localhost:3306/db_course?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true
    username: root
    password: root
    druid:
      initial-size: 5  # 初始化时建立物理连接的个数
      max-active: 30   # 最大连接池数量
      min-idle: 5      # 最小连接池数量
      max-wait: 60000  # 获取连接时最大等待时间,单位毫秒
      time-between-eviction-runs-millis: 60000   # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      min-evictable-idle-time-millis: 300000  # 连接保持空闲而不被驱逐的最小时间
      validation-query: SELECT 1 FROM DUAL    # 用来检测连接是否有效的sql,要求是一个查询语句
      test-while-idle: true  # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-on-borrow: false  # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
      test-on-return: false  # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
      pool-prepared-statements: true  # 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
      max-pool-prepared-statement-per-connection-size: 50   # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
      filters: stat,wall      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500   # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      use-global-data-source-stat: true  # 合并多个DruidDataSource的监控数据

mybatis:
  configuration:
    #配置项:开启下划线到驼峰的自动转换. 作用:将数据库字段根据驼峰规则自动注入到对象属性。
    map-underscore-to-camel-case: true

报错如下

2019-12-21 20:40:51.371 ERROR 41877 --- [nio-9899-exec-3] com.alibaba.druid.util.JdbcUtils         : close statement error

java.sql.SQLException: No operations allowed after statement closed.
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:73) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:82) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.mysql.cj.jdbc.StatementImpl.setFetchSize(StatementImpl.java:1848) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.alibaba.druid.filter.FilterChainImpl.statement_setFetchSize(FilterChainImpl.java:3068) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.filter.FilterAdapter.statement_setFetchSize(FilterAdapter.java:2655) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.filter.FilterChainImpl.statement_setFetchSize(FilterChainImpl.java:3065) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.filter.FilterAdapter.statement_setFetchSize(FilterAdapter.java:2655) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.filter.FilterChainImpl.statement_setFetchSize(FilterChainImpl.java:3065) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.proxy.jdbc.StatementProxyImpl.setFetchSize(StatementProxyImpl.java:447) ~[druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.pool.DruidPooledPreparedStatement.close(DruidPooledPreparedStatement.java:193) ~[druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.util.JdbcUtils.close(JdbcUtils.java:105) ~[druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.pool.DruidConnectionHolder.reset(DruidConnectionHolder.java:319) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.pool.DruidDataSource.recycle(DruidDataSource.java:1894) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.pool.DruidPooledConnection.recycle(DruidPooledConnection.java:324) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl.java:5049) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.filter.FilterAdapter.dataSource_releaseConnection(FilterAdapter.java:2750) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl.java:5045) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.filter.stat.StatFilter.dataSource_releaseConnection(StatFilter.java:665) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl.java:5045) [druid-1.1.21.jar:1.1.21]
    at com.alibaba.druid.pool.DruidPooledConnection.close(DruidPooledConnection.java:267) [druid-1.1.21.jar:1.1.21]
    at org.springframework.jdbc.datasource.DataSourceUtils.doCloseConnection(DataSourceUtils.java:402) [spring-jdbc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:389) [spring-jdbc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.jdbc.datasource.DataSourceUtils.releaseConnection(DataSourceUtils.java:356) [spring-jdbc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager.java:389) [spring-jdbc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager.java:1005) [spring-tx-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:791) [spring-tx-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:712) [spring-tx-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:631) [spring-tx-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385) [spring-tx-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99) [spring-tx-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747) [spring-aop-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) [spring-aop-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at com.example.demo.AuthController$$EnhancerBySpringCGLIB$$f642ddd9.test2(<generated>) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) [spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) [spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) [spring-webmvc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) [spring-webmvc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) [spring-webmvc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) [spring-webmvc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) [spring-webmvc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) [spring-webmvc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) [spring-webmvc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) [spring-webmvc-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) [tomcat-embed-websocket-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:124) [druid-1.1.21.jar:1.1.21]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_201]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_201]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_201]
Caused by: com.mysql.cj.exceptions.StatementIsClosedException: No operations allowed after statement closed.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_201]
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_201]
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_201]
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_201]
    at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:85) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.mysql.cj.jdbc.StatementImpl.checkClosed(StatementImpl.java:340) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    at com.mysql.cj.jdbc.StatementImpl.setFetchSize(StatementImpl.java:1841) ~[mysql-connector-java-8.0.18.jar:8.0.18]
    ... 84 common frames omitted
cfgxy commented 4 years ago

我也遇到了这个问题;

主要原因是 iterator.hasNext() 在到达最后一条时,会主动关闭 Cursor,然后连锁反应最终导致关闭查询对应的PreparedStatement;

然而 mybatis 的 session.close() 的时候,最终会调到 com.alibaba.druid.pool.DruidConnectionHolder.reset 对跟踪的statement进行重置操作,如果Mapper方法重新定义了fetchSize属性,并且与defaultFetchSize不同,则会调用 setFetchSize 重置为默认值;而此时的Statement已经被hasNext触发关闭了,所以会报这个错。

检查了下源码,Mapper 方法中定义的 maxFieldSize / maxRows / queryTimeout / fetchDirection / fetchSize 如果与默认值不同,在使用 Druid 和 Cursor时都会触发这个问题。

虽然Issues提在这里,但我认为这是mybatis的问题。Cursor应该交给用户主动关闭,而不是隐藏在 hasNext 里

Seriels commented 3 years ago

我现在也遇到了这个问题 现在 是 cursor 正常 执行完毕 hasNext 把游标关闭 了 但是 在我调用 finally 的 sqlSession#close()时候就会触发这个问题 。。。。。。

harawata commented 3 years ago

This probably occurs only when pool-prepared-statements is enabled. MyBatis' Cursor implementation uses [java.sql.Statement#closeOnCompletion](https://docs.oracle.com/en/java/javase/16/docs/api/java.sql/java/sql/Statement.html#closeOnCompletion()) (since 3.5.0) which implicitly closes a Statement when all ResultSets associated with it are closed.

It seems that Druid logs the exception, but does not actually throw it , so it should not be a problem if you ignore the log output. If you don't want to see the stack trace, you may have to disable pool-prepared-statements for now. [EDIT] It might be a problem if the closed statement is not removed from the pool. Then you may have to disable pool-prepared-statements when using MyBatis' Cursor.

A long term solution would be to improve Druid to support closeOnCompletion() properly. The following test should pass (with no stack trace in the log), basically.

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.Assert;
import com.alibaba.druid.pool.DruidDataSource;
import junit.framework.TestCase;

public class CloseOnCompletionTest extends TestCase {
    public void test_closeOnCompletion_druid() throws Exception {
        try (DruidDataSource dataSource = new DruidDataSource()) {
            dataSource
                    .setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
            dataSource.setUrl(
                    "jdbc:derby:memory:closeoncompletiondruid;create=true");
            dataSource.setUsername("APP");
            dataSource.setPassword("APP");
            dataSource.setPoolPreparedStatements(true);
            dataSource.init();

            try (Connection connection = dataSource.getConnection()) {
                try (Statement statement = connection.createStatement()) {
                    statement.execute(
                            "create table test (id int, name varchar(10))");
                    statement.execute(
                            "insert into test (id, name) values (1, 'Name1')");
                    statement.execute(
                            "insert into test (id, name) values (2, 'Name2')");
                }
                PreparedStatement statement = connection
                        .prepareStatement("select name from test order by id");
                statement.setFetchSize(5);
                statement.closeOnCompletion();
                ResultSet resultSet = statement.executeQuery();
                Assert.assertTrue(resultSet.next());
                Assert.assertEquals("Name1", resultSet.getString("name"));
                Assert.assertTrue(resultSet.next());
                Assert.assertEquals("Name2", resultSet.getString("name"));
                Assert.assertFalse(resultSet.next());
                resultSet.close();
                Assert.assertTrue(statement.isClosed());
            }
        }
    }
}
ioandy commented 3 years ago

我在使用MyBatisCursorItemReader时也遇到了这个问题,请教各位大佬,都是怎么解决的?

light0x00 commented 2 years ago

原因是druid尝试关闭一个已经被关闭的 Statement,不过这其实只是一条debug日志,不会影响什么(当然,确实不太好看) image

Nick-The-Uncharted commented 2 years ago

遇到了这个问题+1,只能通过spring.datasource.druid.poolPreparedStatements=false规避

linghu666 commented 2 years ago

spring.datasource.druid.poolPreparedStatements=false spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=0 或者两个都删除

1184915153 commented 2 years ago

同遇到,求解决,改数据源配置不是长久之道,全局关闭PreparedStatement 不是很好。

1184915153 commented 2 years ago

手动开数据源连接又显得不是很优雅。

liudongmiao commented 2 years ago

同遇到,求解决,改数据源配置不是长久之道,全局关闭PreparedStatement 不是很好。

可以让 加一个 Mybatis Interceptor,然后在 StatementHandler 中对 prepare 方法进行处理返回一个改写的 Statement,然后该 Statement 返回 ResultSet 时,返回一个 ResultSet 代理对象,当关闭这个 ResultSet 时,也关闭 Statement。

绕一圈...还是直接弃用 druid 吧,2000+ issues 呢,而这已经是 3 年前的问题了。

另外,1.2.10 里,通过调整级别 https://github.com/alibaba/druid/commit/500f8ac2932e58533eb42617e761e7e43b3df952 ,已经是 debug 不是 error 了。眼不见心净。

1184915153 commented 2 years ago

感谢你的回复,确实主要是看着报error有点不好,虽然不影响功能,我还是升下版本吧。

liudongmiao commented 2 years ago

感谢你的回复,确实主要是看着报error有点不好,虽然不影响功能,我还是升下版本吧。

这个日志,只有底层使用 SLF4J 才有。而且,即使只是 debug 日志,也就是见不到,但其实已经是一个异常。

而最近发现的另外一个,简直无法理解:https://github.com/alibaba/druid/blob/1.2.12/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java#L1495-L1517

已经 valid,然后还要进一步判断。也就是说,连接明明能用,但是还是要断掉。这个我个人理解,纯 alibaba 私货行为:https://github.com/alibaba/druid/search?q=usePingMethod

我们暂时直接停用 ping 检查:-Ddruid.mysql.usePingMethod=false