我几天前部署了我的第一个Java Web应用程序,并意识到发生了一件奇怪的事情。一段时间后,所有依赖于我的数据库连接的动态内容和功能(推荐提交,管理员登录)都停止了工作。似乎这种情况每24小时左右就会发生一次。每天早上我都意识到它还没有再起作用。
我通过访问Tomcat Web应用程序管理器并单击“#34; reload"”来解决此问题。在有问题的网络应用程序上。网站的动态功能立即再次运作。
我的服务器正在运行Tomcat
7和MySQL
,并且网络应用使用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){}
}
}
登录表单控制器:
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;
}
答案 0 :(得分: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();
}
}
}
公共连接池为Apache Commons-DBCP和C3P0。
由于管理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.
答案 1 :(得分:4)
MySQL在一段时间后超时连接。处理此问题的标准方法是使用正确配置的连接池(使用已配置的DataSource),而不是直接使用DriverManager。
连接池将检查并丢弃“陈旧”连接。