最近做了一个基于手机客户端webview技术的小项目,项目的架构很简答,就是Spring MVC+Hibernate,后端的数据库为Oracle 10g,项目初期功能非常简单,就是简单的单点登录功能,登录完成之后,就简单的记录一下登录日志。项目在测试环境顺利测试通过,上线到的生产环境,因为是新项目,没有对外发布,只是内部测试使用,访问量非常的小,但是就是这样的情况下,系统竟然报错了,异常如下
??
class="java" name="code">Hibernate: insert into T_USER_LOGIN_LOG (LOGIN_TYPE, BAK1, MOBILE, LOGIN_TIME, BAK3, LOGIN_COST_TIME, BAK2, serv_id, ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?) 2014-08-19 00:17:11,180 [WARN] - SQL Error: 17410, SQLState: null [org.hibernate.util.JDBCExceptionReporter.logExceptions(JDBCExcept ionReporter.java:100)] 21753832 [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] WARN org.hibernate.util.JDBCExceptionRepor ter - SQL Error: 17410, SQLState: null 21753832 [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] WARN org.hibernate.util.JDBCExceptionRepor ter - SQL Error: 17410, SQLState: null 2014-08-19 00:17:11,181 [ERROR] - 无法从套接字读取更多的数据 [org.hibernate.util.JDBCExceptionReporter.logExceptions(JDBCExceptionRe porter.java:101)] 21753833 [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] ERROR org.hibernate.util.JDBCExceptionRepo rter - 无法从套接字读取更多的数据 21753833 [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] ERROR org.hibernate.util.JDBCExceptionRepo rter - 无法从套接字读取更多的数据 2014-08-19 00:17:11,181 [WARN] - SQL Error: 17410, SQLState: null [org.hibernate.util.JDBCExceptionReporter.logExceptions(JDBCExcept ionReporter.java:100)] 21753833 [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] WARN org.hibernate.util.JDBCExceptionRepor ter - SQL Error: 17410, SQLState: null 21753833 [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] WARN org.hibernate.util.JDBCExceptionRepor ter - SQL Error: 17410, SQLState: null 2014-08-19 00:17:11,182 [ERROR] - 无法从套接字读取更多的数据 [org.hibernate.util.JDBCExceptionReporter.logExceptions(JDBCExceptionRe porter.java:101)] 21753834 [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] ERROR org.hibernate.util.JDBCExceptionRepo rter - 无法从套接字读取更多的数据 21753834 [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] ERROR org.hibernate.util.JDBCExceptionRepo rter - 无法从套接字读取更多的数据 2014-08-19 00:17:11,183 [ERROR] - Could not synchronize database state with session [org.hibernate.event.def.AbstractFlushingEventLi stener.performExecutions(AbstractFlushingEventListener.java:324)] org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:14
?只是一条简单的insert语句,没有其他复杂的业务逻辑,而且这个问题在测试环境上也没有出现过,而且还是偶尔出现。
?
?
?? 从错误上看,应该是数据库连接不正常导致的,
?? 排查原因:
?
??? 在绝望是时候突然想到,生产的Oracle和测试的Oracle唯一的区别是:生产的Oracle对长期闲置的数据库连接会自动断开,而我们系统使用的数据库连接池是Apache的DBCP的BasicDataSource连接池。
?
?? 于是就在本地的Oracle中修改idle_time参数为一分钟,测试,果然出现了同样的问题,问题的根源找到,但是如何解决呢?
?
??? 现在问题是:Oracle将数据库连接断开了,但是连接池却认为该连接是可以用的,就将该连接分配给了应用程序使用,连接池和应用程序在分配和使用该连接之前没有校验数据库连接的可用性。
?
?? 查看DBCP的文档和源码,发现有这4个属性可以用:
??
?
至此,增加这些属性之后,连接池的配置如下
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>${jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
<property name="maxActive">
<value>20</value>
</property>
<property name="maxIdle">
<value>3</value>
</property>
<property name="maxWait">
<value>-1</value>
</property>
<property name="defaultAutoCommit">
<value>false</value>
</property>
<!-- 将从连接池中获取的数据库连接返回给调用者之前,通过这个sql查询判断其有效性 -->
<!-- 如果设置,这个查询sql必须至少有一条数据 -->
<property name="validationQuery">
<value>select 1 from dual</value>
</property>
<!-- 确定从连接池中获取的对象是否有效,如果无效从连接池移除,然后尝试下一个对象 -->
<property name="testOnBorrow">
<value>true</value>
</property>
<!-- 放回连接池之前判断对象是否有效-->
<property name="testOnReturn">
<value>true</value>
</property>
<!-- 通过空闲对象驱逐器判断对象是否有效,如果无效从连接池移除 -->
<property name="testWhileIdle">
<value>true</value>
</property>
</bean>
?
?
在生产上测试,问题得到解决。
?
?