Overview

之前将SecretEPDB部署到了云服务器上之后,再打开需要连接数据库的网页时总是会出现莫名其妙的错误,之前一直没管它,主要是因为这个错误不是每次都出现,出现之后刷新几次又可以访问了。

1. 错误描述

每次打开需要连接数据库的网页,就很有很大概率出现下面的错误信息:

Struts Problem Report

Struts has detected an unhandled exception:

Messages:   
Broken pipe
The last packet successfully received from the server was 144,004,781 milliseconds ago. The last packet sent successfully to the server was 144,004,781 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.
No operations allowed after connection closed.
could not execute query

第一反应是Struts的问题,但很快就可以排除,因为:

  1. 在本地开发时,不会有这个问题,只有部署到服务器上了才会出现。
  2. 错误信息很明确,这个服务器链接失效了。

2. 分析

查了一些资料,定位了这个错误,是因为MYSQL的链接失效问题,在 MySQL相关问题 中,我们配置了wait_timeout=31536000这句话,所以MYSQL31536000毫秒之后会失效,尽管我们已经将wait_timeout的值改得很大了(默认是8小时),但是即使再大,也会有失效的时候,这时候服务器会得到一个失效的数据库连接,但是Hibernate并不知道它失效了,继续交给Struts使用,就会报上面的错误。

重启tomcat可以解决这个问题,因为所有的连接都重新初始化了嘛,但是时间久了肯定还会出问题,这也是为什么在本地开发时不会出问题,因为tomcat总在不停地重启,连接就没等到失效的时候。

3. 解决方法

查了一下原因,为了偷懒,选择了最简单的解决方法,在hibernate配置文件hibernate.cfg.xml中添加下面的配置:

<property name="connection.autoReconnect">true</property> 
<property name="connection.autoReconnectForPools">true</property> 
<property name="connection.is-connection-validation-required">true</property> 

第二天发现又有问题了,看来这样不是很可行。

3.1 使用c3p0数据库连接池

其实也不是很麻烦,Hibernate默认就支持c3p0,先去你使用的Hibernate版本的文件包的lib文件夹中,找到c3p0-***.jar,版本号与当前的Hibernate有关。从Hibernate包中去找是一个最佳实践,可以让你避免一些不兼容性问题。我们使用的是Hibernate 3,所以我去下了一个Hibernate3的包,官网上已经不再提供Hibernate 4以下的包了,可以去 sourceforge上下载这个版本。

下载时候,将lib文件夹里面的c3p0-***.jar加入项目的引用,并在hibernate.cfg.xml中添加下面的配置(其实默认hibernate.cfg.xml中有这部分代码,只是是注释掉的,取消注释就可以):

<!-- configuration pool via c3p0-->      
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> 
<property name="c3p0.min_size">5</property> 
<property name="c3p0.max_size">30</property> 
<property name="c3p0.time_out">1800</property> 
<property name="c3p0.max_statement">50</property> 
<property name="c3p0.acquire_increment">1</property> 
<property name="c3p0.idle_test_period">120</property> 
<property name="c3p0.validate">true</property> 

重新发布项目,在服务器上部署以后,不仅解决了链接失效问题,数据库访问速度快了很多。因为c3p0维护了数据库连接池,并在使用时验证链接是否可用(<property name="c3p0.validate">true</property>),如果失效了,则重新打开。

4. 堆栈错误补充

还有一种情况可以导致MySQL链接失效,错误信息如下:

… Stacktraces
org.hibernate.exception.JDBCConnectionException: could not execute query
…
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
…
java.lang.OutOfMemoryError: Java heap space
… Struts Problem Report

Struts has detected an unhandled exception:

Messages:   
Java heap space
No operations allowed after connection closed.
could not execute query

我将这个错误分成两条显示,是为了表示下面的部分才是核心问题所在:由于堆栈溢出,导致java虚拟机崩溃,从而数据库连接中断,而且之后每次查询都会报数据库连接错误,根本原因是堆栈溢出。好了,知道了问题所在,就容易解决了:更改java虚拟机内存。
找到tomcatbin目录下的catalina.sh,打开它 (以tomcat7为例,如果是用apt-get命令安装的tomcat,这个bin目录在/usr/share/tomcat7下面)。
cygwin=false之前,加上下面这句话:

JAVA_OPTS="-Xmx512m"

注意:双引号要加上。如果512MB还是不行,可以加大。

问题起源:我们在secretEPDB项目线上版本测试时,发现查询第1962条数据时,查询总是中断,之后整个程序都会崩溃,即使第一次就查询第1962条数据仍会程序全部崩溃,我们推断,这不是数据库连接池的问题,应该是堆栈过小导致虚拟机崩溃的问题。结果和我们的推测一样,这条数据特别大,而我们的服务器默认的虚拟机内存为128MB,改为512MB之后,程序一切正常。

5. 参考资料