【发布时间】:2010-06-19 12:39:53
【问题描述】:
有没有办法检查 Java EE 应用程序中的连接泄漏?
应用程序正在我的本地计算机上运行。它使用 MySQL 数据库,用户将他的详细信息输入到该数据库中。
在我看来,连接泄漏意味着没有正确关闭连接对象。 我在我的应用程序中创建了太多的数据库连接。我想检查一下数据库连接是否有连接泄漏。
【问题讨论】:
标签: java mysql jdbc connection-leaks
有没有办法检查 Java EE 应用程序中的连接泄漏?
应用程序正在我的本地计算机上运行。它使用 MySQL 数据库,用户将他的详细信息输入到该数据库中。
在我看来,连接泄漏意味着没有正确关闭连接对象。 我在我的应用程序中创建了太多的数据库连接。我想检查一下数据库连接是否有连接泄漏。
【问题讨论】:
标签: java mysql jdbc connection-leaks
log4jdbc,一个 Java JDBC 驱动程序,可以记录 SQL 和/或其他 JDBC 驱动程序的 JDBC 调用,有一个记录器,记录连接打开和关闭事件以及转储所有打开的连接号。这对于查找连接泄漏问题非常有用。
您可能想要检查的另一个工具是ConnLeakFinder,一个用于查明 java 代码中 jdbc 连接泄漏的简单工具。不过我没有任何经验。
【讨论】:
如果您使用的是 Java EE 应用服务器,您应该能够将其配置为在连接断开时检查连接,并在旧连接不回来时回收它们。
连接泄漏确实是个问题。如果您将连接管理分散在代码中的很多地方,我会担心,要找到它们是个大问题。我希望看到一个仅在定义明确的持久层中使用的 Java EE 连接池。连接应该由管理该工作单元的事务的服务层打开,并在用例结束后立即关闭它,在 finally 块的方法范围内。
如果不是这样,我认为是时候重构了。
【讨论】:
尝试使用FindBug。它是一个静态代码分析工具,可作为 eclipse 插件和独立应用程序使用。除了连接泄漏,它还会在您的应用程序中发现其他问题
【讨论】:
使用连接工厂,例如:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionFactory {
private static Connection connection;
public static synchronized Connection getConnection() throws SQLException {
if (connection == null || connection.isClosed()) {
connection = DriverManager.getConnection("url");
}
return connection;
}
}
这样您就永远不会留下无人看管的连接。如果您需要多个连接(出于性能考虑),请使用连接池。大多数应用服务器都有 JDBC 连接池设施。
【讨论】:
tackle connection leaks is to do it during testing 的最佳方式。
您可以使用自动化实用程序,以便每次测试都验证是否存在连接泄漏。
@BeforeClass
public static void initConnectionLeakUtility() {
if ( enableConnectionLeakDetection ) {
connectionLeakUtil = new ConnectionLeakUtil();
}
}
@AfterClass
public static void assertNoLeaks() {
if ( enableConnectionLeakDetection ) {
connectionLeakUtil.assertNoLeaks();
}
}
ConnectionLeakUtil 看起来像这样:
public class ConnectionLeakUtil {
private JdbcProperties jdbcProperties = JdbcProperties.INSTANCE;
private List idleConnectionCounters =
Arrays.asList(
H2IdleConnectionCounter.INSTANCE,
OracleIdleConnectionCounter.INSTANCE,
PostgreSQLIdleConnectionCounter.INSTANCE,
MySQLIdleConnectionCounter.INSTANCE
);
private IdleConnectionCounter connectionCounter;
private int connectionLeakCount;
public ConnectionLeakUtil() {
for ( IdleConnectionCounter connectionCounter :
idleConnectionCounters ) {
if ( connectionCounter.appliesTo(
Dialect.getDialect().getClass() ) ) {
this.connectionCounter = connectionCounter;
break;
}
}
if ( connectionCounter != null ) {
connectionLeakCount = countConnectionLeaks();
}
}
public void assertNoLeaks() {
if ( connectionCounter != null ) {
int currentConnectionLeakCount = countConnectionLeaks();
int diff = currentConnectionLeakCount - connectionLeakCount;
if ( diff > 0 ) {
throw new ConnectionLeakException(
String.format(
"%d connection(s) have been leaked! Previous leak count: %d, Current leak count: %d",
diff,
connectionLeakCount,
currentConnectionLeakCount
)
);
}
}
}
private int countConnectionLeaks() {
try ( Connection connection = newConnection() ) {
return connectionCounter.count( connection );
}
catch ( SQLException e ) {
throw new IllegalStateException( e );
}
}
private Connection newConnection() {
try {
return DriverManager.getConnection(
jdbcProperties.getUrl(),
jdbcProperties.getUser(),
jdbcProperties.getPassword()
);
}
catch ( SQLException e ) {
throw new IllegalStateException( e );
}
}
}
IdleConnectionCounter 的实现可以在这个blog post 中找到,MySQL 版本如下:
public class MySQLIdleConnectionCounter implements IdleConnectionCounter {
public static final IdleConnectionCounter INSTANCE =
new MySQLIdleConnectionCounter();
@Override
public boolean appliesTo(Class<? extends Dialect> dialect) {
return MySQL5Dialect.class.isAssignableFrom( dialect );
}
@Override
public int count(Connection connection) {
try ( Statement statement = connection.createStatement() ) {
try ( ResultSet resultSet = statement.executeQuery(
"SHOW PROCESSLIST" ) ) {
int count = 0;
while ( resultSet.next() ) {
String state = resultSet.getString( "command" );
if ( "sleep".equalsIgnoreCase( state ) ) {
count++;
}
}
return count;
}
}
catch ( SQLException e ) {
throw new IllegalStateException( e );
}
}
}
现在,当您运行测试时,如果连接泄漏,您会遇到故障。
【讨论】: