JDBC MySql 连接池的最佳实践,以避免连接池耗尽

11

我有一个运行在GlassFish上的Java-JSF Web应用程序,我想要使用连接池。因此,我创建了一个应用程序范围的bean,为其他bean提供Connection实例:

public class DatabaseBean {

    private DataSource myDataSource;

    public DatabaseBean() {
        try {
            Context ctx = new InitialContext();
            ecwinsDataSource = (DataSource) ctx.lookup("jdbc/myDataSource");
        } catch (NamingException ex) {
            ex.printStackTrace();
        }
    }

    public Connection getConnection() throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException {
        Connection connection = myDataSource.getConnection();
        System.out.println("Succesfully connected: " + connection);
        //Sample: Succesfully connected: com.sun.gjc.spi.jdbc40.ConnectionHolder40@7fb213a5
        return connection;
    }
}

这种方式会导致连接池非常快地被填满。在通过“与数据库相关”的视图进行几次导航后,应用程序将停止,并显示以下消息:

RAR5117:从连接池[mysql_testPool]无法获得/创建连接,原因:正在使用的连接等于max-pool-size且已过期的max-wait-time。不能分配更多连接。RAR5114:分配连接时出错:[分配连接时出现错误。原因:正在使用的连接等于max-pool-size且已过期的max-wait-time。不能分配更多连接。] java.sql.SQLException:分配连接时出现错误。原因:正在使用的连接等于max-pool-size且已过期的max-wait-time。不能分配更多连接。

我在每个方法中都关闭了连接和其他资源。如果使用独立的连接,应用程序可以正常运行。

我做错了什么?有任何提示或建议将不胜感激。

2个回答

21
该异常表示应用程序代码泄漏数据库连接的典型情况。您需要确保在同一方法块中按照常规JDBC语法,在 try-with-resources 块中获取并关闭所有连接(ConnectionStatementResultSet)。请注意保持HTML标记原样。
public void create(Entity entity) throws SQLException {
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL_CREATE);
    ) { 
        statement.setSomeObject(1, entity.getSomeProperty());
        // ...
        statement.executeUpdate();
    }
}

如果你不使用Java 7,可以在try-finally块中关闭它们。在finally中关闭将确保在发生异常的情况下也会关闭它们。

public void create(Entity entity) throws SQLException {
    Connection connection = null;
    PreparedStatement statement = null;

    try { 
        connection = dataSource.getConnection();
        statement = connection.prepareStatement(SQL_CREATE);
        statement.setSomeObject(1, entity.getSomeProperty());
        // ...
        statement.executeUpdate();
    } finally {
        if (statement != null) try { statement.close(); } catch (SQLException logOrIgnore) {}
        if (connection != null) try { connection.close(); } catch (SQLException logOrIgnore) {}
    }
}

是的,即使使用连接池,您仍然需要自己关闭连接。对于初学者来说,这是一个常见的错误,他们认为连接池会自动处理关闭。但这是不正确的。连接池实际上返回一个封装的连接,在关闭()方法中执行以下操作:

public void close() throws SQLException {
    if (this.connection is still eligible for reuse) {
        do not close this.connection, but just return it to pool for reuse;
    } else {
        actually invoke this.connection.close();
    }
}

不关闭它们将导致连接没有被释放回池中以供重用,因此它将一次又一次地获取新的连接,直到数据库用尽连接,这将导致应用程序崩溃。

另请参阅:


谢谢您的详细回答,非常有帮助!我已经编辑了我的评论并进行了应用程序修改。 - Daniel Szalay
再次感谢您提供的精彩信息! - Daniel Szalay

0

2
他已经在做那个了?他已经在应用服务器中配置了连接池数据源。只是根据异常,他没有正确关闭资源。 - BalusC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接