问题描述:自己负责的一个公司项目采用了JNDI数据源连接的方式部署在tomcat中,在tomcat中配置了数据源连接相关的属性,最初配置如下:<Resource name="jdbc/myDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="123456" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC"/>
同时项目也整合了hibernate框架,对hibernate相关属性进行了配置。
tomcat启动运行,数据库连接正常使用,并没有出现任何问题,持续了几个月之久。
然而就在项目运行空闲期,中间大概间隔了几天没有任何请求调用,测试组需要验证一些资源的可用度,于是对该项目接口进行了调用,发现出现服务异常的情况,经过一番查证,发现数据源连接出现问题,无法释放。日志如下:
org.springframework.dao.DataAccessResourceFailureException:could not extract ResultSet; nested exception isorg.hibernate.exception.JDBCConnectionException: could not extract ResultSet
atorg.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:143)
atorg.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:344)
atorg.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:309)
atorg.springframework.orm.hibernate5.HibernateTemplate.get(HibernateTemplate.java:419)
atorg.springframework.orm.hibernate5.HibernateTemplate.get(HibernateTemplate.java:412)
atcom.iflytek.zszb.dao.hbn.OmrPhotoDao.find(OmrPhotoDao.java:16)
atcom.iflytek.zszb.service.ASPProcessService.start(ASPProcessService.java:176)
atcom.iflytek.zszb.service.ASPProcessService.process(ASPProcessService.java:161)
atcom.iflytek.zszb.controller.ASPProcessController.entry(ASPProcessController.java:99)
atsun.reflect.GeneratedMethodAccessor43.invoke(Unknown Source)
atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
atjava.lang.reflect.Method.invoke(Method.java:606)
atorg.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
atorg.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
atorg.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
atorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832)
atorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743)
atorg.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
atorg.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961)
atorg.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
atorg.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
atorg.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
atjavax.servlet.http.HttpServlet.service(HttpServlet.java:650)
atorg.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
atjavax.servlet.http.HttpServlet.service(HttpServlet.java:731)
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
atorg.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
atorg.springframework.orm.hibernate5.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:151)
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
atorg.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
atorg.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
atorg.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
atorg.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
atorg.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
atorg.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
atorg.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
atorg.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
atorg.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:436)
atorg.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1078)
atorg.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)
atorg.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2517)
atorg.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2506)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
atorg.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
atjava.lang.Thread.run(Thread.java:745)
Caused by:org.hibernate.exception.JDBCConnectionException: could not extract ResultSet
atorg.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:115)
atorg.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
atorg.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
atorg.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
atorg.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:79)
atorg.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.getResultSet(AbstractLoadPlanBasedLoader.java:434)
atorg.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeQueryStatement(AbstractLoadPlanBasedLoader.java:186)
atorg.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:121)
atorg.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)
atorg.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167)
atorg.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3991)
atorg.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)
atorg.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)
atorg.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
atorg.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278)
atorg.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121)
atorg.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)
atorg.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1142)
atorg.hibernate.internal.SessionImpl.access$2600(SessionImpl.java:167)
atorg.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.doLoad(SessionImpl.java:2762)
atorg.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.load(SessionImpl.java:2741)
atorg.hibernate.internal.SessionImpl.get(SessionImpl.java:978)
atorg.springframework.orm.hibernate5.HibernateTemplate$1.doInHibernate(HibernateTemplate.java:426)
atorg.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:341)
...52 more
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:The last packet successfully received from the server was 106,759,000milliseconds ago. The last packet sentsuccessfully to the server was 106,759,000 milliseconds ago. is longer than theserver configured value of 'wait_timeout'. You should consider either expiringand/or testing connection validity before use in your application, increasingthe server configured values for client timeouts, or using the Connector/Jconnection property 'autoReconnect=true' to avoid this problem.
atsun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
atsun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
atsun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
atjava.lang.reflect.Constructor.newInstance(Constructor.java:526)
atcom.mysql.jdbc.Util.handleNewInstance(Util.java:404)
atcom.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:981)
atcom.mysql.jdbc.MysqlIO.send(MysqlIO.java:3652)
atcom.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2460)
atcom.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2625)
atcom.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2551)
atcom.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1861)
atcom.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1962)
atorg.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:96)
atorg.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:96)
atorg.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:70)
...71 more
Caused by: java.net.SocketException:Software caused connection abort: socket write error
atjava.net.SocketOutputStream.socketWrite0(Native Method)
atjava.net.SocketOutputStream.socketWrite(SocketOutputStream.java:113)
atjava.net.SocketOutputStream.write(SocketOutputStream.java:159)
atjava.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
atjava.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
atcom.mysql.jdbc.MysqlIO.send(MysqlIO.java:3634)
...79 more
根据日志描述来看,貌似是数据库连接因为多时未被使用或者被数据库服务断开导致已经失效,但是在tomcat的数据源池连接中并没有移除,也就是说tomcat数据源连接池中的连接此时是无效的,一旦被调用如果没有做检查,就会出现如上问题。
查找了很多解决方案:
1. mysql.ini文件中wait_timeout属性设置更长时间或者配置客户端连接超时时间,这个没试过,但是感觉有点不靠谱,毕竟并不能确定这些连接会被闲置多长时间然后被断开;
2. 连接配置加autoReconnect=true属性,测试没有解决问题;
3. 在连接池配置中加上检测连接的属性,暂时可行。如下所示:
<Resource name="jdbc/myDB"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
testOnBorrow="true"
testWhileIdle="true"
validationQuery="select 1 fromdual"
username="root"
password="123456"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC"/>
这样,每次在使用连接的时候会去检查数据库连接对象的有效性,如果连接无效,则换另一个,同时会有语句select 1 from dual配合检测。另外在连接空闲状态会检测,如果连接无效则drop掉,默认30分钟检测一次。对性能影响不是特别大。
测试流程:
1. 先将数据源按原始的配置,也就是没有检测属性,运行项目进行接口调用,应该是没有问题的;
2. 更改系统时间,将系统日期改成10天之后的日期,再进行调用,就能发现出现连接失效的问题了;
3. 关停tomcat进程,将数据连接检测属性加到数据源配置中,重启tomcat,再重复1和2的流程,如果没有出现连接失效问题,就OK了。
以上只是从应用层面作出一点小小的让步,暂时解决这个问题,增加数据源连接对象检测属性配置,至少可以保证应用的稳定,不会因为数据库连接的问题导致整个应用不可用。
如果有更好的解决方案,还请路人大神不吝赐教,感谢。
tomcat数据源配置相关参数说明:
dataSource: 要连接的 datasource (通常我们不会定义在 server.xml)
defaultAutoCommit: 对于事务是否 autoCommit, 默认值为 true
defaultReadOnly: 对于数据库是否只能读取, 默认值为 false
driverClassName:连接数据库所用的 JDBC Driver Class,
maxActive: 可以从对象池中取出的对象最大个数,为0则表示没有限制,默认为8
maxIdle: 最大等待连接中的数量,设 0 为没有限制 (对象池中对象最大个数)
minIdle:对象池中对象最小个数
maxWait: 最大等待秒数, 单位为 ms, 超过时间会丢出错误信息,-1为无限制
password: 登陆数据库所用的密码
url: 连接数据库的 URL
username: 登陆数据库所用的帐号
validationQuery: 验证连接是否成功, SQL SELECT 指令至少要返回一行
removeAbandoned: 是否自我中断, 默认是 false
removeAbandonedTimeout: 几秒后会自我中断, removeAbandoned 必须为 true
logAbandoned: 是否记录中断事件, 默认为 false
minEvictableIdleTimeMillis:大于0 ,进行连接空闲时间判断,或为0,对空闲的连接不进行验证;默认30分钟
timeBetweenEvictionRunsMillis:失效检查线程运行时间间隔,如果小于等于0,不会启动检查线程,默认-1
testOnBorrow:取得对象时是否进行验证,检查对象是否有效,默认为false
testOnReturn:返回对象时是否进行验证,检查对象是否有效,默认为false
testWhileIdle:空闲时是否进行验证,检查对象是否有效,默认为false
Ø 在使用DBCP的时候,如果使用默认值,则数据库连接因为某种原因断掉后,再从连接池中取得连接又不进行验证,这时取得的连接实际上就会是无效的数据库连接。因此为了防止获得的数据库连接失效,在使用的时候最好保证:
username: 登陆数据库所用的帐号
validationQuery:SELECT COUNT(*) FROM DUAL
testOnBorrow、testOnReturn、testWhileIdle:最好都设为true
minEvictableIdleTimeMillis:大于0 ,进行连接空闲时间判断,或为0,对空闲的连接不进行验证
timeBetweenEvictionRunsMillis:失效检查线程运行时间间隔,如果小于等于0,不会启动检查线程
Ø PS:在构造GenericObjectPool [BasicDataSource在其createDataSource () 方法中也会使用GenericObjectPool]时,会生成一个内嵌类Evictor,实现自Runnable接口。如果 timeBetweenEvictionRunsMillis大于0,每过timeBetweenEvictionRunsMillis毫秒 Evictor会调用evict()方法,检查对象的闲置时间是否大于minEvictableIdleTimeMillis毫秒(_minEvictableIdleTimeMillis小于等于0时则忽略,默认为30分钟),是则销毁此对象,否则就激活并校验对象,然后调用 ensureMinIdle方法检查确保池中对象个数不小于_minIdle。在调用returnObject方法把对象放回对象池,首先检查该对象是否有效,然后调用PoolableObjectFactory的passivateObject方法使对象处于非活动状态。再检查对象池中对象个数是否小于 maxIdle,是则可以把此对象放回对象池,否则销毁此对象
Ø 上述特性的可设置性已在代码中验证,具体性能是否能实现有待实际验证
removeAbandoned: 是否自我中断, 默认是 false
removeAbandonedTimeout: 几秒后会自我中断, removeAbandoned 必须为 true
logAbandoned: 是否记录中断事件, 默认为 false