简体   繁体   English

数据库连接在一段时间后停止,没有明显原因

[英]Database connection stops after period of time for no apparent reason

I deployed my first Java web application a couple of days ago and realized a strange thing was happening. 几天前,我部署了我的第一个Java Web应用程序,并意识到发生了一件奇怪的事情。 After a period of time all the dynamic content and functionality that relied on a connection to my database (testimonial submission, admin login) stopped working. 一段时间后,所有依赖于与我的数据库的连接的动态内容和功能(推荐提交,管理员登录)均停止工作。 It seems like this is happening every 24 hours or so. 似乎每24小时左右就会发生一次。 Every morning I realize it isn't working again. 每天早晨,我意识到它不再起作用。

I solve the issue by going in to the Tomcat web application manager and clicking "reload" on the web app in question. 我通过进入Tomcat Web应用程序管理器并在有问题的Web应用程序上单击“重新加载”来解决此问题。 Immediately the dynamic features of the website work again. 该网站的动态功能会立即再次起作用。

My server is running Tomcat 7 and MySQL and the web app uses the JDBC driver to establish the connection to the database. 我的服务器正在运行Tomcat 7和MySQL并且该Web应用程序使用JDBC驱动程序来建立与数据库的连接。 I've made no alterations to Apache or Tomcat settings. 我没有对Apache或Tomcat设置进行任何更改。

I have other web apps written in PHP that work persistently without fault it just seems to be this Java web app that has this problem. 我还有其他用PHP编写的Web应用程序,这些应用程序可以持续正常运行而不会出现故障,这似乎就是这个有问题的Java Web应用程序。

What would cause this to happen and how can I make it so the web app doesn't need to be reloaded before it can establish a database connection again? 是什么原因导致这种情况发生,以及如何使它不再需要重新加载Web应用程序才能重新建立数据库连接?

EDIT: attached some code for database connection 编辑:附加了一些数据库连接的代码

Database connection 数据库连接

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){}
    }
}

Controller for login form: 登录表单的控制器:

    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");
    }
}
}

Login service checks login details are correct: 登录服务检查登录详细信息是否正确:

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;
}

UPDATE 更新

If I understand correctly I should not be handling a direct connection to a database and instead be using a service that will manage connections for me. 如果我理解正确,那么我不应该处理与数据库的直接连接,而应该使用可以为我管理连接的服务。

This is my example of establishing a DataSource connection to a MysQL database. 这是我建立与MysQL数据库的数据源连接的示例。

Establish a new DataSource instance of this class: 建立此类的新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;
    }

}

Instantiating a new DataSource for checking user login details 实例化新的数据源以检查用户登录详细信息

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;
}

Given that you've never heard of a connection pool before I'm assuming that you not are not very effectively managing database resources. 考虑到您在听说您不是非常有效地管理数据库资源之前从未听说过连接池。 The most basic way to access the database is to obtain a connection, execute some statements & close the connection. 访问数据库的最基本方法是获得连接,执行一些语句并关闭连接。 In the code you provided I don't see you obtaining or closing a connection, so I assume that you create a single connection when you start your application and keep the connection open "forever". 在您提供的代码中,我看不到您获得或关闭连接,因此我假设您在启动应用程序时创建了一个连接,并保持连接“永远”开放。 After a certain amount of time your MySql server decides to kill the connection as it's been open for too long. 一段时间后,由于打开时间太长,您的MySql服务器决定终止连接。

When you create and close a connection each time you need one, you normally won't encounter any connection timeouts, but you might experience a lot overhead from creating a connection each time your application needs one. 每次需要建立和关闭连接时,通常不会遇到任何连接超时,但是每次应用程序需要建立连接时,您可能会遇到很多开销。

This is where a connection pool comes in; 这是连接池的来源。 a connection pool manages a number of database connections and your application borrows one each time it needs one. 一个连接池管理着许多数据库连接,而您的应用程序每次需要一个时都会借用一个。 By properly configuring your connection pool the pool will normally transparently take care of broken connections (you might for example configure the pool to renew a connection once it's x minutes/hours old). 通过正确配置您的连接池,该池通常可以透明地处理断开的连接(例如,您可以将池配置为在x分钟/小时后重新建立连接)。

You also need to pay attention to resource management; 您还需要注意资源管理; eg close a statement as soon as you no longer need it. 例如,在不再需要该语句时立即将其关闭。

The following code demonstrates how your "check user" method can be improved: 以下代码演示了如何改进“检查用户”方法:

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();
        }
    }
}

Common connections pools are Apache Commons-DBCP and C3P0 . 常见的连接池是Apache Commons-DBCPC3P0

As managing sql resources can be quite repetitive and cumbersome you might want to consider using a template: for example Spring's JdbcTemplate 由于管理sql资源可能非常重复和繁琐,因此您可能需要考虑使用模板:例如Spring的JdbcTemplate

Example C3p0 configuration: 示例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.

MySQL times out the connection after some period of time. MySQL会在一段时间后使连接超时。 The standard way to deal with this is to use a properly configured connection pool (with a configured DataSource) instead of using DriverManager directly. 解决此问题的标准方法是使用正确配置的连接池(带有配置的DataSource),而不是直接使用DriverManager。

The connection pool will check for and discard "stale" connections. 连接池将检查并丢弃“陈旧”的连接。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM