Tomcat Web 应用程序中使用 Java 和 MySQL 进行连接池

发布于 2024-08-05 19:56:38 字数 3615 浏览 8 评论 0原文

我最近编写了一个 Java Web 应用程序并将其部署到服务器上,我发现了一个在开发或测试期间没有出现的不寻常问题。

当用户这么长时间登录并去显示数据库中的数据时,页面显示没有记录可查看。但页面刷新时,前 x 条记录将根据分页规则显示。

检查日志,发现:

ERROR|19 09 2009|09 28 54|http-8080-4|myDataSharer.database_access.Database_Metadata_DBA| - Error getting types of columns of tabular Dataset 12

com.mysql.jdbc.CommunicationsException: Communications link failure due to underlying exception: 

** BEGIN NESTED EXCEPTION ** 

java.io.EOFException

STACKTRACE:

java.io.EOFException
    at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1956)
    at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2368)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2867)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1616)

等等几百行。

该应用程序目前设置为约 100 个用户,但尚未充分使用。它在 Apache Tomcat servlet / jsps 和 MySQL 数据库之间使用连接池,以下代码示例形成数据库操作的一般安排,其中通常每页有几个:

// Gets a Dataset.
public static Dataset getDataset(int DatasetNo) {
    ConnectionPool_DBA pool = ConnectionPool_DBA.getInstance();
    Connection connection = pool.getConnection();
    PreparedStatement ps = null;
    ResultSet rs = null;

    String query = ("SELECT * " +
                    "FROM Dataset " +
                    "WHERE DatasetNo = ?;");

    try {
        ps = connection.prepareStatement(query);
        ps.setInt(1, DatasetNo);
        rs = ps.executeQuery();
        if (rs.next()) {
            Dataset d = new Dataset();
            d.setDatasetNo(rs.getInt("DatasetNo"));
            d.setDatasetName(rs.getString("DatasetName"));
            ...

            }

            return d;
        }
        else {
            return null;
        }
    }
    catch(Exception ex) {
        logger.error("Error getting Dataset " + DatasetNo + "\n", ex);            
        return null;
    }
    finally {
        DatabaseUtils.closeResultSet(rs);
        DatabaseUtils.closePreparedStatement(ps);
        pool.freeConnection(connection);
    }
}

有谁能够建议纠正此问题的方法吗?

我相信这是由于 MySQL 将连接轮询连接打开长达八小时,但我不确定。

谢谢马丁

·奥谢。


只是为了澄清有关我的连接池方法的一点,我在应用程序中使用的不是 Oracle,而是我自己的一个类,如下所示:

package myDataSharer.database_access;

import java.sql.*;
import javax.sql.DataSource;
import javax.naming.InitialContext;
import org.apache.log4j.Logger;

public class ConnectionPool_DBA {

    static Logger logger = Logger.getLogger(ConnectionPool_DBA.class.getName());

    private static ConnectionPool_DBA pool = null;
    private static DataSource dataSource = null;


    public synchronized static ConnectionPool_DBA getInstance() {
        if (pool == null) {
            pool = new ConnectionPool_DBA();
        }
        return pool;
    }

    private ConnectionPool_DBA() {
        try {
            InitialContext ic = new InitialContext();
            dataSource = (DataSource) ic.lookup("java:/comp/env/jdbc/myDataSharer");
        }
        catch(Exception ex) {
            logger.error("Error getting a connection pool's datasource\n", ex);
        }
    }

    public void freeConnection(Connection c) {
        try {
            c.close();
        }
        catch (Exception ex) {
            logger.error("Error terminating a connection pool connection\n", ex);           
        }
    }

    public Connection getConnection() {
        try {
            return dataSource.getConnection();
        }
        catch (Exception ex) {
            logger.error("Error getting a connection pool connection\n", ex);            
            return null;
        }
    }    
}

我认为提及 Oracle 是因为我使用了类似的名称。

I recently wrote and deployed a Java web application to a server and I'm finding an unusual problem which didn't appear during development or testing.

When a user logs in after so long and goes to display data from the database, the page indicates that there are no records to see. But upon page refresh, the first x records are shown according to the pagination rules.

Checking the logs, I find:

ERROR|19 09 2009|09 28 54|http-8080-4|myDataSharer.database_access.Database_Metadata_DBA| - Error getting types of columns of tabular Dataset 12

com.mysql.jdbc.CommunicationsException: Communications link failure due to underlying exception: 

** BEGIN NESTED EXCEPTION ** 

java.io.EOFException

STACKTRACE:

java.io.EOFException
    at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1956)
    at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2368)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2867)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1616)

And so on for several hundred lines.

The application is currently set for about 100 users but is not yet in full use. It uses connection pooling between the Apache Tomcat servlets / jsps and a MySQL database with the following code example forming the general arrangement of a database operation, of which there are typically several per page:

// Gets a Dataset.
public static Dataset getDataset(int DatasetNo) {
    ConnectionPool_DBA pool = ConnectionPool_DBA.getInstance();
    Connection connection = pool.getConnection();
    PreparedStatement ps = null;
    ResultSet rs = null;

    String query = ("SELECT * " +
                    "FROM Dataset " +
                    "WHERE DatasetNo = ?;");

    try {
        ps = connection.prepareStatement(query);
        ps.setInt(1, DatasetNo);
        rs = ps.executeQuery();
        if (rs.next()) {
            Dataset d = new Dataset();
            d.setDatasetNo(rs.getInt("DatasetNo"));
            d.setDatasetName(rs.getString("DatasetName"));
            ...

            }

            return d;
        }
        else {
            return null;
        }
    }
    catch(Exception ex) {
        logger.error("Error getting Dataset " + DatasetNo + "\n", ex);            
        return null;
    }
    finally {
        DatabaseUtils.closeResultSet(rs);
        DatabaseUtils.closePreparedStatement(ps);
        pool.freeConnection(connection);
    }
}

Is anyone able to advise a way of correcting this problem?

I believe it is due to MySQL leaving connection poll connections open for up to eight hours but am not certain.

Thanks

Martin O'Shea.


Just to clarify one point made about my method of connection pooling, it isn't Oracle that I'm using in my application but a class of my own as follows:

package myDataSharer.database_access;

import java.sql.*;
import javax.sql.DataSource;
import javax.naming.InitialContext;
import org.apache.log4j.Logger;

public class ConnectionPool_DBA {

    static Logger logger = Logger.getLogger(ConnectionPool_DBA.class.getName());

    private static ConnectionPool_DBA pool = null;
    private static DataSource dataSource = null;


    public synchronized static ConnectionPool_DBA getInstance() {
        if (pool == null) {
            pool = new ConnectionPool_DBA();
        }
        return pool;
    }

    private ConnectionPool_DBA() {
        try {
            InitialContext ic = new InitialContext();
            dataSource = (DataSource) ic.lookup("java:/comp/env/jdbc/myDataSharer");
        }
        catch(Exception ex) {
            logger.error("Error getting a connection pool's datasource\n", ex);
        }
    }

    public void freeConnection(Connection c) {
        try {
            c.close();
        }
        catch (Exception ex) {
            logger.error("Error terminating a connection pool connection\n", ex);           
        }
    }

    public Connection getConnection() {
        try {
            return dataSource.getConnection();
        }
        catch (Exception ex) {
            logger.error("Error getting a connection pool connection\n", ex);            
            return null;
        }
    }    
}

I think the mention of Oracle is due to me using a similar name.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

千紇 2024-08-12 19:56:39

如果您还没有找到答案,我在最后一天一直在处理这个问题。我基本上在做与你相同的事情,只是我的池化基于 apache.commons.pool。您看到的错误与 EOF 完全相同。检查 mysqld 错误日志文件,该文件很可能位于数据目录中。查找 mysqld 是否崩溃。如果 mysqld 崩溃,mysqld_safe 将快速重新启动它,因此除非您查看其日志文件,否则不会明显看出这种情况。 /var/log 对于这种情况没有帮助。

崩溃前创建的连接将在崩溃后结束。

On the off chance you haven't found your answer I've been dealing with this for the last day. I am essentially doing the same thing you are except that I'm basing my pooling off of apache.commons.pool. Same exact error you are seeing EOF. Check your mysqld error log file which is most likely in your data directory. Look for mysqld crashing. mysqld_safe will restart your mysqld quickly if it crashes so it won't be apparent that this is the case unless you look in its logfile. /var/log is not help for this scenario.

Connections that were created before the crash will EOF after the crash.

洋洋洒洒 2024-08-12 19:56:38

有一些关于避免这种情况的指示,可以从其他来源获得,特别是从其他驱动程序的连接池实现和其他应用程序服务器获得。有关 JNDI 数据源的 Tomcat 文档中已经提供了一些信息。

  1. 建立一个清理/收割计划,如果连接池中的连接在一定时间内不活动,则将关闭该计划。将数据库连接保持打开状态 8 小时(MySQL 默认值)并不是一个好习惯。在大多数应用程序服务器上,非活动连接超时值是可配置的,通常小于 15 分钟(即连接在池中保留的时间不能超过 15 分钟,除非它们被一次又一次地重用)。在 Tomcat 中,使用 JNDI 数据源时, 使用removeAbandoned 和removeAbandonedTimeout 设置执行相同操作。
  2. 当新连接从池返回到应用程序时,请确保首先对其进行测试。例如,据我所知,大多数应用程序服务器都可以配置为通过执行“SELECT 1 FROM Dual”来测试与 Oracle 数据库的连接。在Tomcat中,使用validationQuery属性为MySQL设置适当的查询 - 我相信这是“SELECT 1”(不带引号)。设置validationQuery属性的值之所以有帮助,是因为如果查询执行失败,连接就会从池中删除,并在其位置创建新的连接。

就应用程序的行为而言,用户可能第一次看到池返回到应用程序的陈旧连接的结果。第二次,池可能返回一个可以为应用程序的查询提供服务的不同连接。

Tomcat JNDI 数据源基于 Commons DBCP,因此适用于 DBCP 的配置属性将也适用于 Tomcat。

There are a few pointers on avoiding this situation, obtained from other sources, especially from the connection pool implementations of other drivers and from other application servers. Some of the information is already available in the Tomcat documentation on JNDI Data Sources.

  1. Establish a cleanup/reaper schedule that will close connections in the pool, if they are inactive beyond a certain period. It is not good practice to leave a connection to the database open for 8 hours (the MySQL default). On most application servers, the inactive connection timeout value is configurable and is usually less than 15 minutes (i.e. connections cannot be left in the pool for more than 15 minutes unless they are being reused time and again). In Tomcat, when using a JNDI DataSource, use the removeAbandoned and removeAbandonedTimeout settings to do the same.
  2. When a new connection is return from the pool to the application, ensure that it is tested first. For instance, most application servers that I know, can be configured so that connection to an Oracle database are tested with an execute of "SELECT 1 FROM dual". In Tomcat, use the validationQuery property to set the appropriate query for MySQL - I believe this is "SELECT 1" (without quotes). The reason why setting the value of the validationQuery property helps, is because if the query fails to execute, the connection is dropped from the pool, and new one is created in its place.

As far are the behavior of your application is concerned, the user is probably seeing the result of the pool returning a stale connection to the application for the first time. The second time around, the pool probably returns a different connection that can service the application's queries.

Tomcat JNDI Data Sources are based on Commons DBCP, so the configuration properties applicable to DBCP will apply to Tomcat as well.

说谎友 2024-08-12 19:56:38

我想知道为什么你在代码中使用 ConnectionPool_DBA 而不是让 Tomcat 处理池并简单地使用 JNDI 查找连接。

为什么将 Oracle 连接池与 MySQL 一起使用?当我进行 JNDI 查找和连接池时,我更喜欢 Apache DBCP 库。我发现它效果很好。

我还会问您的 DatabaseUtils 方法是否抛出任何异常,因为如果在调用 pool.freeConnection() 之前的任何一个调用抛出异常,您将永远不会释放该连接。

我不太喜欢你的代码,因为执行 SQL 操作的类应该将其 Connection 实例传递给它,并且不应该承担获取和使用 Connection 的双重责任。持久性类无法知道它是否正在较大的事务中使用。最好有一个单独的服务层来获取连接、管理事务、编组持久性类并在完成时进行清理。

更新:

Google 找到了与您同名的 Oracle 类。现在我真的不喜欢你的代码,因为当可以轻松获得更好的替代方案时,你编写了自己的代码。我会立即放弃您的并使用 DBCP 和 JNDI 重做此操作。

I'd wonder why you're using ConnectionPool_DBA in your code instead of letting Tomcat handle the pooling and simply looking up the connection using JNDI.

Why are you using an Oracle connection pool with MySQL? When I do JNDI lookups and connection pooling, I prefer the Apache DBCP library. I find that it works very well.

I'd also ask if your DatabaseUtils methods throw any exceptions, because if either of the calls prior to your call to pool.freeConnection() throw one you'll never free up that connection.

I don't like your code much because a class that performs SQL operations should have its Connection instance passed into it, and should not have the dual responsibility of acquiring and using the Connection. A persistence class can't know if it's being used in a larger transaction. Better to have a separate service layer that acquires the Connection, manages the transaction, marshals the persistence classes, and cleans up when it's complete.

UPDATE:

Google turned up the Oracle class with the same name as yours. Now I really don't like your code, because you wrote something of your own when a better alternative was easily available. I'd ditch yours right away and redo this using DBCP and JNDI.

淤浪 2024-08-12 19:56:38

此错误表明服务器意外关闭连接。这可能发生在以下两种情况,

  1. MySQL 在一定时间(默认为 8 小时)后关闭空闲连接。发生这种情况时,没有线程负责关闭连接,因此连接会变得陈旧。如果此错误仅在长时间空闲后发生,这很可能是原因。

  2. 如果您没有完全读取所有响应,连接可能会以繁忙状态返回到池中。下次,命令发送到 MySQL 并关闭错误状态的连接。如果错误频繁发生,则可能就是这个原因。

同时,设置驱逐线程将有助于缓解该问题。将这样的内容添加到数据源中,

          ...
          removeAbandoned="true"
          removeAbandonedTimeout="120"
          logAbandoned="true"
          testOnBorrow="false"
          testOnReturn="false"
          timeBetweenEvictionRunsMillis="60000"
          numTestsPerEvictionRun="5"
          minEvictableIdleTimeMillis="30000"
          testWhileIdle="true"
          validationQuery="select now()"

This error indicates server closes connection unexpectedly. This can occur in following 2 cases,

  1. MySQL closes idle connection after certain time (default is 8 hours). When this occurs, no thread is responsible for closing the connection so it gets stale. This is most likely the cause if this error only happens after long idle.

  2. If you don't completely read all the responses, the connection may get returned to the pool in busy state. Next time, a command is sent to MySQL and it closes connection for wrong state. If the error occurs quite frequent, this is probably the cause.

Meanwhile, setting up an eviction thread will help to alleviate the problem. Add something like this to the Data Source,

          ...
          removeAbandoned="true"
          removeAbandonedTimeout="120"
          logAbandoned="true"
          testOnBorrow="false"
          testOnReturn="false"
          timeBetweenEvictionRunsMillis="60000"
          numTestsPerEvictionRun="5"
          minEvictableIdleTimeMillis="30000"
          testWhileIdle="true"
          validationQuery="select now()"
傲鸠 2024-08-12 19:56:38

Web 服务器和数据库之间是否有一个路由器可以透明地关闭空闲 TCP/IP 连接?

如果是这样,您必须让连接池丢弃池中超过 XX 分钟未使用的连接,或者每隔 YY 分钟对连接执行某种 ping 操作以使其保持活动状态。

Is there a router between the web server and the database that transparently closes idle TCP/IP connections?

If so, you must have your connection pool either discard unused-for-more-than-XX-minutes connections from the pool, or do some kind of ping every YY minutes on the connection to keep it active.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文