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

crm*_*ham 0 java mysql database tomcat jdbc

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

我通过访问Tomcat Web应用程序管理器并单击相关Web应用程序上的"重新加载"来解决此问题.网站的动态功能立即再次运作.

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

我有其他用PHP编写的Web应用程序,它们可以持久地工作而不会出现故障,这似乎是这个Java Web应用程序存在这个问题.

导致这种情况发生的原因是什么?如何在不再需要重新加载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){}
    }
}
Run Code Online (Sandbox Code Playgroud)

登录表单控制器:

    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");
    }
}
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

UPDATE

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

这是我建立与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;
    }

}
Run Code Online (Sandbox Code Playgroud)

实例化新的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;
}
Run Code Online (Sandbox Code Playgroud)

Gre*_*eek 5

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

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


Pie*_*ter 5

鉴于您在我假设您没有非常有效地管理数据库资源之前,您从未听说过连接池.访问数据库的最基本方法是获取连接,执行一些语句并关闭连接.在您提供的代码中,我没有看到您获取或关闭连接,因此我假设您在启动应用程序时创建单个连接并保持连接"永久"打开.经过一段时间后,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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

公共连接池是Apache Commons-DBCPC3P0.

由于管理sql资源可能非常重复和繁琐,您可能需要考虑使用模板:例如Spring的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.
Run Code Online (Sandbox Code Playgroud)