Tomcat에서 c3p0 데이터베이스 연결 풀 해제 문제 해결

c3p0 연결 풀을 사용하는 애플리케이션이 Tomcat 종료 시 다음과 같은 경고를 출력합니다:

경고: 웹 애플리케이션 [uavmonitor]이(가) [Timer-0] 스레드를 시작했지만 중지하지 못했습니다. 
이로 인해 메모리 누수가 발생할 가능성이 매우 높습니다.

경고: 웹 애플리케이션 [uavmonitor]이(가) [com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0] 
스레드를 시작했지만 중지하지 못했습니다.

이 문제는 c3p0가 생성한 백그라운드 스레드가 Tomcat 종료 시 적절하게 정리되지 않아 발생합니다. c3p0 공식 문서에 따르면, 라이브러리는 내부적으로 헬퍼 스레드와 java.util.Timer 스레드를 생성합니다.

연결 풀 관리 클래스 예시

public class ConnectionManager {
    private static PooledDataSource connectionPool;
    
    static {
        try {
            connectionPool = new ComboPooledDataSource();
            Properties config = loadDatabaseProperties();
            
            connectionPool.setDriverClass(config.getProperty("db.driver"));
            connectionPool.setJdbcUrl(config.getProperty("db.url"));
            connectionPool.setUser(config.getProperty("db.user"));
            connectionPool.setPassword(config.getProperty("db.password"));
            
            connectionPool.setInitialPoolSize(5);
            connectionPool.setMinPoolSize(5);
            connectionPool.setMaxPoolSize(20);
            connectionPool.setIdleConnectionTestPeriod(10);
            connectionPool.setTestConnectionOnCheckin(true);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    public static DataSource getConnectionPool() {
        return connectionPool;
    }
}

해결 방법: ServletContextListener 구현

커넥션 풀을 명시적으로 종료하는 리스너를 추가합니다:

public class ConnectionCleanupListener implements ServletContextListener {
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        try {
            DataSource ds = ConnectionManager.getConnectionPool();
            if (ds != null) {
                DataSources.destroy(ds);
                System.out.println("데이터베이스 연결 풀 정상 종료");
            }
        } catch (SQLException sqlEx) {
            sqlEx.printStackTrace();
        }
    }
    
    @Override
    public void contextInitialized(ServletContextEvent event) {}
}

web.xml에 리스너 등록:

<listener>
    <listener-class>com.example.ConnectionCleanupListener</listener-class>
</listener>

참고: 애플리케이션에서 직접 생성한 ExecutorService가 있을 경우, 동일한 리스너 내에서 shutdownNow()를 호출해 종료해야 합니다.

태그: C3P0 Tomcat 데이터베이스연결풀 메모리누수 커넥션풀관리

5월 26일 12:07에 게시됨