【问题标题】:Database connection stops after period of time for no apparent reason数据库连接在一段时间后无明显原因停止
【发布时间】:2014-09-06 10:29:29
【问题描述】:

几天前我部署了我的第一个 Java Web 应用程序,并意识到发生了一件奇怪的事情。一段时间后,依赖于我的数据库连接的所有动态内容和功能(推荐提交、管理员登录)停止工作。这似乎每 24 小时左右就会发生一次。每天早上我都意识到它不再起作用了。

我通过进入 Tomcat Web 应用程序管理器并在有问题的 Web 应用程序上单击“重新加载”来解决此问题。网站的动态功能立即再次起作用。

我的服务器正在运行Tomcat 7 和MySQL 并且网络应用程序使用JDBC 驱动程序来建立与数据库的连接。我没有对 Apache 或 Tomcat 设置进行任何更改。

我有其他用 PHP 编写的 web 应用程序可以持续运行而没有错误,似乎只是这个 Java web 应用程序有这个问题。

什么会导致这种情况发生,我怎样才能做到这一点,以便网络应用程序不需要重新加载就可以再次建立数据库连接?

编辑:附上一些数据库连接代码

数据库连接

public class DBConnection {
    private static Connection conn; 
    private static final Configuration conf     = new Configuration();
    private static final String dbDriver        = conf.getDbDriver();
    private static final String dbHostName      = conf.getDbHostname();
    private static final String dbDatabaseName  = conf.getDbDatabaseName();
    private static final String dbUsername      = conf.getDbUsername();
    private static final String dbPassword      = conf.getDbPassword();

    public Connection getConnection(){
        try{
            Class.forName(dbDriver);
            Connection conn = (Connection) DriverManager.getConnection(dbHostName + dbDatabaseName, dbUsername, dbPassword);
            return conn;
        } catch(Exception e){
            e.printStackTrace();
        }
        return conn;
    }
    public void disconnect(){

        try{
            conn.close();
        } catch (Exception e){}
    }
}

登录表单控制器:

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String form         = request.getParameter("form");
// check login details
if(form.equals("loginForm")){
    String username = request.getParameter("username").trim();
    String password = request.getParameter("password").trim();

    password = loginService.hashPassword(password);
    boolean isValidUser = loginService.checkUser(username, password);

    if(isValidUser){

        Cookie loggedIn = new Cookie("loggedIn", "true");
        loggedIn.setMaxAge(60*60*24);
        response.addCookie(loggedIn);

        out.print("success");

    }else{
        out.print("nope");
    }
}
}

登录服务检查登录详细信息是否正确:

public boolean checkUser(String username, String password){
    boolean isValid = false;
    try{
        sql = "SELECT username, password FROM morleys_user WHERE username=? AND password=? AND isActive=1 LIMIT 1";
        prep = conn.prepareStatement(sql);
        prep.setString(1, username);
        prep.setString(2, password);
        rs = prep.executeQuery();
        if(rs.next()){
            return true;
        }
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        connection.disconnect();
        }
    return isValid;
}

更新

如果我理解正确,我不应该处理与数据库的直接连接,而是使用将为我管理连接的服务。

这是我建立与 MySQL 数据库的 DataSource 连接的示例。

建立这个类的一个新的DataSource实例:

package uk.co.morleys;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

import javax.sql.DataSource;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

public class DataSourceFactory {

    public static DataSource getMySQLDataSource() {
        Properties props = new Properties();
        FileInputStream fis = null;
        MysqlDataSource mysqlDS = null;
        try {
            fis = new FileInputStream("db.properties");
            props.load(fis);
            mysqlDS = new MysqlDataSource();
            mysqlDS.setURL(props.getProperty("MYSQL_DB_URL"));
            mysqlDS.setUser(props.getProperty("MYSQL_DB_USERNAME"));
            mysqlDS.setPassword(props.getProperty("MYSQL_DB_PASSWORD"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return mysqlDS;
    }

}

实例化一个新的 DataSource 以检查用户登录详细信息

public boolean checkUser(String username, String password){
    boolean isValid = false;
    DataSource ds = DataSourceFactory.getMySQLDataSource();
    Connection con = null;
    ResultSet rs = null;
    PreparedStatement ps = null;

    try{
        con = ds.getConnection();
        sql = "SELECT username, password FROM morleys_user WHERE username=? AND password=? AND isActive=1 LIMIT ";
        ps = con.prepareStatement(sql);
        ps.setString(1, username);
        ps.setString(2, password);
        rs = ps.executeQuery();
        if(rs.next()){
            return true;
        }

    }catch(SQLException e){
        e.printStackTrace();
    }finally{
        try {
            if(rs != null) rs.close();
            if(ps != null) ps.close();
            if(con != null) con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
}
    return isValid;
}

【问题讨论】:

  • 你有什么例外吗?发布您获得的异常的完整堆栈跟踪。
  • 我们的DataSource是如何配置的?
  • 你能详细说明问题和配置吗? MySQL 有使用 mysql 连接器 j 的文档,即带有 tomcat dev.mysql.com/doc/connector-j/en/… 的 jdbc 驱动程序。
  • @DavieBrown 我希望不会,因为它只是使 cookie 过期,所以我只需要再次登录即可查看管理页面。
  • 依赖注入框架将是执行此操作的合适候选者,但我假设您没有使用它。一种简单的方法是在初始化 Web 应用程序时创建该数据源,并将其放入某个类的公共静态变量中。当您使用池化数据源时,重用同一个数据源非常重要,因为您希望重用池管理的数据库连接。

标签: java mysql database tomcat jdbc


【解决方案1】:

鉴于在我假设您没有非常有效地管理数据库资源之前您从未听说过连接池。 访问数据库最基本的方法是获取连接,执行一些语句并关闭连接。 在您提供的代码中,我没有看到您获取或关闭连接,因此我假设您在启动应用程序时创建了一个连接并“永远”保持连接打开。一段时间后,您的 MySql 服务器决定终止连接,因为它已打开太久。

当您在每次需要连接时创建和关闭连接时,您通常不会遇到任何连接超时,但您可能会因应用程序每次需要连接时创建连接而产生大量开销。

这就是连接池的用武之地;连接池管理许多数据库连接,并且您的应用程序每次需要一个时都会借用一个。通过正确配置连接池,连接池通常会透明地处理断开的连接(例如,您可以将连接池配置为在连接 x 分钟/小时后更新连接)。

你还需要注意资源管理;例如不再需要时立即关闭语句。

以下代码演示了如何改进您的“检查用户”方法:

public boolean checkUser(String username, String password) throws SQLException {
    //acquire a java.sql.DataSource; the DataSource is typically a connection pool that's set-up in the application of obtained via jndi
    DataSource dataSource = acquireDataSource();
    //java 7 try-with-resources statement is used to make sure that resources are properly closed
    //obtain a connection from the pool. Upon closing the connection we return it to the pool
    try (Connection connection = dataSource.getConnection()) {
        //release resources associated with the PreparedStatement as soon as we no longer need it.
        try(PreparedStatement ps = connection.prepareStatement("SELECT username, password FROM morleys_user WHERE username=? AND password=? AND isActive=1 LIMIT 1");){
            ps.setString(1, username);
            ps.setString(2, password);
            ResultSet resultSet = ps.executeQuery();
            return resultSet.next();
        }
    }
}

常见的连接池是Apache Commons-DBCPC3P0

由于管理 sql 资源可能非常重复和繁琐,您可能需要考虑使用模板:例如 Spring's JdbcTemplate

C3p0 配置示例:

public ComboPooledDataSource dataSource(String driver, String url, String username,String password) throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setDriverClass(driver);
    dataSource.setJdbcUrl(url);
    dataSource.setUser(username);
    dataSource.setPassword(password);

    dataSource.setAcquireIncrement(1);
    dataSource.setMaxPoolSize(100);
    dataSource.setMinPoolSize(1);
    dataSource.setInitialPoolSize(1);
    dataSource.setMaxIdleTime(300);
    dataSource.setMaxConnectionAge(36000);

    dataSource.setAcquireRetryAttempts(5);
    dataSource.setAcquireRetryDelay(2000);
    dataSource.setBreakAfterAcquireFailure(false);

    dataSource.setCheckoutTimeout(30000);
    dataSource.setPreferredTestQuery("SELECT 1");
    dataSource.setIdleConnectionTestPeriod(60);
    return dataSource;
}//in order to do a "clean" shutdown you should call datasource.close() when shutting down your web app.

【讨论】:

  • 我有几个问题@Pieter。在上面的示例中,我有一个建立与数据库的连接的类。然后我在 checkUser 方法和其他几个方法中创建这个类的一个实例。在这些方法结束时,我通过调用连接类的每个实例的 disconnect 方法来关闭连接。如果这不是真的发生了什么?同样在你上面的例子中。我想我现在了解数据源,但是我将在哪里建立数据库连接详细信息,例如在我的顶级示例中我建立驱动程序、主机名等?
  • 不完全相关,但 try-with-resources 关闭连接并为我准备好的语句是否正确?如果我想打印其中一个的堆栈跟踪,我应该使用 catch 吗?
  • try-with-resources 关闭任何实现AutoCloseable 接口的对象。要捕获异常,只需添加 catch 块。例子可以找到here
  • 我主要使用 spring 框架,所以我通常在 spring 上下文中声明 DataSource,并在需要访问它的地方使用依赖注入。您声明数据源的位置取决于您的项目设置。如果您使用的是 tomcat,那么在 tomcat 配置中配置 commons-dbcp 连接池相对容易,您可以通过 jndi 查找在代码中访问它。您还可以在自己的应用程序中的某处将数据源声明为静态字段。我添加了一个 C3p0 数据源的示例配置。
  • 因此,如果我创建一个自定义类,它只会在我实现AutoCloseable 时自动关闭。否则我需要finally 来关闭?
【解决方案2】:

MySQL 在一段时间后连接超时。处理此问题的标准方法是使用正确配置的连接池(带有已配置的 DataSource),而不是直接使用 DriverManager。

连接池将检查并丢弃“陈旧”连接。

【讨论】:

  • 您能否详细说明一下这个我以前从未听说过的“连接池”。
  • @GreyBeardedGeek 我用我认为有效的解决方案更新了我的问题。你觉得还可以吗?
  • 不,你不应该实现 DataSource - 你应该配置一个连接池,并使用它。阅读文档(尝试谷歌搜索“java DataSource”和“java connection pool”)比尝试重新发明轮子更有效率。
猜你喜欢
  • 2022-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-22
  • 1970-01-01
相关资源
最近更新 更多