주요글: 도커 시작하기
반응형
DBCP API를 이용하여 커넥션 풀을 사용하는 방법에 대해서 살펴본다.

커넥션 풀과 자카르타 DBCP API

DBCP API는 커넥션 풀 기능을 제공하는 API로서 자카르타의 또 다른 프로젝트인 Pool API에 기반하고 있다. DBCP API는 사용방법이 비교적 쉬우며, 파일을 통해서 커넥션 풀을 설정할 수 있고 또한 프로그램에서 직접 커넥션 풀을 설정할 수 있기 때문에 커넥션 풀을 사용하려는 개발자에게 매우 유용한 API이다.

커넥션 풀이란?

커넥션 풀에 대한 개념이 없는 사람을 위해 DBCP API를 이용한 커넥션 풀에 대해서 살펴보기 전에 간단하게 커넥션 풀이 무엇인지에 대해서 살펴보도록 하자. 커넥션 풀 기법이란 데이터베이스와 연결된 커넥션을 미리 만들어서 풀(pool)이란 저장소에 저장해 두고 있다가 필요할 때에 커넥션을 풀에서 가져다 쓰고 다시 풀에 반환하는 기법을 의미한다


커넥션 풀 기법에서는 위 그림과 같이 풀 속에 데이터베이스와 연결된 커넥션을 미리생성해놓고 있는다. 데이터베이스 커넥션이 필요할 경우, 커넥션을 새로 생성하는 것이 아니라 풀 속에 미리 생성되어 있는 커넥션을 가져다가 사용하게 된다. 다 사용한 커넥션은 다시 풀에 반환한다. 풀에 반환된 커넥션은 다음에 다시 사용된다.

커넥션 풀의 특징은 다음과 같다.

  • 풀 속에 미리 커넥션이 생성되어 있기 때문에 커넥션을 생성하는 데 드는 연결 시간이 소비되지 않는다.
  • 커넥션을 계속해서 재사용하기 때문에 생성되는 커넥션 수가 많지 않다.
커넥션을 생성하고 닫는 데 필요한 시간이 소모되지 않기 때문에 그 만큼 어플리케이션의 실행 속도가 빨라지며, 또한 한번에 생성될 수 있는 커넥션 수를 제어하기 때문에 동시 접속자수가 몰려도 웹 어플리케이션이 쉽게 다운되지 않는다.

커넥션 풀을 사용하면 전체적인 웹 어플리케이션의 성능 및 처리량이 높아지기 때문에 많은 웹 어플리케이션에서 커넥션 풀을 기본으로 사용하고 있다.

DBCP API의 사용방법

자카르타 프로젝트의 DBCP API를 사용할 때에는 다음과 같은 과정을 거치면 된다.

  1. DBCP 관련 Jar 파일 및 JDBC 드라이버 Jar 파일 설치하기
  2. 커넥션 풀 관련 설정 파일 초기화하기
  3. 커넥션 풀 관련 드라이버 로딩하기
  4. 커넥션 풀로부터 커넥션 사용하기
이 네 가지 절차에 대해서 차례대로 살펴보도록 하자.

필요한 Jar 파일 복사

DBCP API를 사용하기 위해서는 다음과 같은 라이브러리가 필요하다.

  • DBCP API 관련 Jar 파일
  • DBCP API가 사용하는 자카르타 Pool API의 Jar 파일
  • Pool API가 사용하는 자카르타 Collection API의 Jar 파일
이들 라이브러리의 최신 버전은 http://jakarta.apache.org/site/binindex.cgi 에서 다운로드 받을 수 있으며, 이 글에서는 다음 버전을 사용하여 예제를 작성하였다.

  • DBCP 1.2.1 - commons-dbcp-1.2.1.zip
  • Pool 1.2 - commons-pool-1.2.zip
  • Collection 3.1 - commons-collections-3.1.zip
이들 파일의 압축을 풀면 다음과 같은 Jar 파일들을 발견할 수 있는데, 이들 Jar 파일들을 사용하면 된다.

  • commons-dbcp-1.2.1.jar
  • commons-pool-1.2.jar, commons-collections-3.1.jar
예제로 제공되는 파일에는 pool\WEB-INF\lib 폴더에는 이미 이들 Jar 파일들이 포함되어 있으므로 별도로 복사하지 않더라도 DBCP를 사용하는 본 장의 예제들을 실행할 수 있게 된다.

커넥션 풀 설정 파일 작성하기

DBCP를 사용하는 방법에는 소스 코드 상에서 커넥션 풀을 설정하는 방법과 설정 파일을 통해서 커넥션 풀을 설정하는 방법 두가지 존재하는데 본 장에서는 설정 파일을 이용한 커넥션 풀 설정 방법에 대해서 살펴보도록 하겠다.

DBCP Pool API에서 사용되는 커넥션 풀 설정 파일의 기본 골격은 아래 코드와 같다.

    
    파일명: pool\WEB-INF\classes\pool1.jocl    
    <object class="org.apache.commons.dbcp.PoolableConnectionFactory"
        xmlns="http://apache.org/xml/xmlns/jakarta/commons/jocl">
      <object class="org.apache.commons.dbcp.DriverManagerConnectionFactory">
        <string value="jdbc:mysql://localhost:3306/.." />
        <string value="jspexam" />
        <string value="jspex" />
      </object>      
      <object class="org.apache.commons.pool.impl.GenericObjectPool">
        <object class="org.apache.commons.pool.PoolableObjectFactory" null="true" />
      </object>
      
      <object class="org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory"
              null="true" />
            <string null="true" />
            <boolean value="false" />
            <boolean value="true" />
    </object>

위 코드에서 나머지 부분은 그대로 입력하고 다음 부분만 알맞게 변경하면 된다.

    <object class="org.apache.commons.dbcp.DriverManagerConnectionFactory">
      <string value="jdbc:mysql://localhost:3306/..." />
      <string value="jspexam" />
      <string value="jspex" />
    </object>

위 코드에는 세 개의 <string> 태그가 사용되는데, 이들 태그는 각각 순서대로 JDBC URL, 데이터베이스 사용자 계정, 암호를 나타낸다.

설정 파일의 위치

DBCP API는 클래스패스로부터 설정 파일을 읽어온다. 따라서 앞서 작성한 커넥션 풀 설정 파일은 클래스패스에 위치해 있어야 한다. 웹 어플리케이션에서 DBCP API와 관련된 설정 파일의 위치로 가장 좋은 곳은 WEB-INF\classes 폴더이다. 본 글의 예제에서 사용하는 커넥션 풀 설정 파일은 모두 WEB-INF\classes 폴더에 위치시켰다.

커넥션 풀 초기화

DBCP API를 통해서 커넥션 풀을 사용하기 위해서는 커넥션 풀과 관련된 JDBC 드라이버를 로딩해주어야 한다. DBCP API를 사용할 때에 로딩해주어야 할 JDBC 드라이버는 다음과 같다.

  • org.apache.commons.dbcp.PoolingDriver - DBCP API의 JDBC 드라이버
  • DBMS에 연결할 때 사용될 JDBC 드라이버
웹 어플리케이션 시작할 때 위에서 언급한 두 가지 형태의 JDBC 드라이버를 로딩하도록 하면 편리할 것이다. 웹 어플리케이션이 시작할 때 자동으로 시작되는 JDBC 드라이버를 로딩하도록 구현한 서블릿 클래스는 다음 코드와 같다.

    
    파일명: pool\WEB-INF\src\madvirus.jdbcdriver\DBCPInit.java    
    package madvirus.jdbcdriver;
    
    import javax.servlet.http.HttpServlet;
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import java.util.StringTokenizer;
    
    public class DBCPInit extends HttpServlet {
    
        public void init(ServletConfig config) throws ServletException {
            try {
                String drivers = config.getInitParameter("jdbcdriver");
                StringTokenizer st = new StringTokenizer(drivers, ",");
                while (st.hasMoreTokens()) {
                    String jdbcDriver = st.nextToken();
                    Class.forName(jdbcDriver);
                }
                
                Class.forName("org.apache.commons.dbcp.PoolingDriver");                
                System.setProperty("org.xml.sax.drvier",
                       "org.apache.crimson.parser.XMLReaderImpl");
            } catch(Exception ex) {
                throw new ServletException(ex);
            }
        }
    }

DBCPInit 서블릿은 "jdbcdriver" 초기화 파라미터로부터 로딩할 JDBC 드라이버를 입력받아 JDBC 드라이버를 차례대로 로딩한다. 그런 후, DBCP API의 JDBC 드라이버인 PoolingDriver 을 로딩한다. 마지막으로 설정 파일을 분석할 때 사용할 XML 파서를 지정한다. 위 코드는 Sun 사에서 배포한 JDK 1.4를 기준으로 XML 파서를 지정하였는데, 만약 다른 XML 파서를 사용한다면 알맞게 변경해주어야 한다.

WEB-INF\web.xml 파일에 DBCPInit 서블릿 클래스에 대한 설정 정보를 추가함으로써 웹 어플리케이션이 시작될 때 DBCPInit 서블릿 클래스가 시작될 수 있도록 할 수 있다. 예를 들면, 아래와 같은 코드를 web.xml 파일에 추가해주면 된다.

  <servlet>
     <servlet-name>DBCPInit</servlet-name>
     <servlet-class>madvirus.jdbcdriver.DBCPInit</servlet-class>
     <load-on-startup>1</load-on-startup>
     <init-param>
        <param-name>jdbcdriver</param-name>
        <param-value>com.mysql.jdbc.Driver</param-value>
     </init-param>
  </servlet>

위와 같이 코드를 web.xml 파일에 추가해주면 웹 어플리케이션이 시작할 때 DBCPInit 서블릿 클래스가 자동으로 시작되고 init() 메소드가 호출된다.

커넥션 풀로부터 커넥션 사용하기

커넥션 풀을 위한 JDBC 드라이버 및 DBMS에 연결할 때 사용할 JDBC 드라이버를 로딩하면 커넥션 풀로부터 커넥션을 가져와 사용할 수 있다. 커넥션 풀로부터 커넥션을 가져오는 코드는 별반 다르지 않으며, 다음과 같은 형태의 코드를 사용하면 된다.

    Connection conn = null;
    ....
    try {
        String jdbcDriver = "jdbc:apache:commons:dbcp:/pool1";
        conn = DriverManager.getConnection(jdbcDriver);
        ...
    } finally {
        ...
        if (conn != null) try { conn.close(); } catch(SQLException ex) {}
    }

위 코드를 보면 DBCP API 기반의 커넥션 풀을 사용한다고 해서 특별히 코드가 달라지는 부분이 없다는 것을 알 수 있다. 일반 경우와 마찬가지로 DriverManager.getConnection() 메소드를 사용해서 커넥션을 구해오고, 커넥션을 다 사용하면 close() 메소드를 사용하여 사용한 커넥션을 닫는다. 차이점이라면 JDBC URL이 다음과 같은 형태를 띈다는 점이다.

    jdbc:apache:commons:dbcp:/[풀이름]

[풀이름]은 여러 개의 커넥션 풀 중에서 사용할 커넥션 풀의 이름을 나타내는 것으로서 커넥션 풀 설정 파일에서 확장자를 제외한 나머지 이름을 [풀이름]으로 사용한다. 예를 들어, 앞서 작성했었던 pool1.jocl 파일이 설정한 커넥션 풀을 사용하고 싶다면 다음과 같은 JDBC URL을 사용한다.

    jdbc:apache:commons:dbcp:/pool1

실제로 커넥션 풀을 사용하는 완전한 예제는 다음 코드와 같다. (아래 코드를 여러분의 환경에 알맞게 변형시켜서 실행하기 바란다.)

    
    파일명: pool\usePool1.jsp    
    <%@ page contentType = "text/html; charset=euc-kr" %>
    
    <%@ page import = "java.sql.DriverManager" %>
    <%@ page import = "java.sql.Connection" %>
    <%@ page import = "java.sql.Statement" %>
    <%@ page import = "java.sql.ResultSet" %>
    <%@ page import = "java.sql.SQLException" %>
    
    <html>
    <head><title>회원 목록</title></head>
    <body>
    
    MEMBMER 테이블의 내용
    <table width="100%" border="1">
    <tr>
        <td>이름</td><td>아이디</td><td>이메일</td>
    </tr>
    <%
        
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        
        try {
            String jdbcDriver = "jdbc:apache:commons:dbcp:/pool1";
            String query = "select * from MEMBER order by MEMBERID";
            conn = DriverManager.getConnection(jdbcDriver);
            stmt = conn.createStatement();
            rs = stmt.executeQuery(query);
            while(rs.next()) {
    %>
    <tr>
        <td><%= rs.getString("NAME") %></td>
        <td><%= rs.getString("MEMBERID") %></td>
        <td><%= rs.getString("EMAIL") %></td>
    </tr>
    <%
            }
        } finally {
            if (rs != null) try { rs.close(); } catch(SQLException ex) {}
            if (stmt != null) try { stmt.close(); } catch(SQLException ex) {}
            if (conn != null) try { conn.close(); } catch(SQLException ex) {}
        }
    %>
    </table>
    
    </body>
    </html>

위 코드에서 커넥션 풀에서 구한 Connection의 close() 메소드를 호출하면, 커넥션이 닫히는 것이 아니라 커넥션 풀로 반환된다. 이렇게 커넥션 풀에 커넥션을 반환하는 메소드를 close()로 지정한 이유는 기존의 코드를 최소한으로 변경하는 범위 내에서 커넥션 풀을 사용할 수 있도록 하기 위함이다. 물론, JDBC 프로그래밍의 코딩 형태를 동일하게 유지하기 위한 것도 close() 메소드를 사용하는 이유이다.

커넥션 풀 속성 지정하기

앞에서 살펴본 커넥션 풀 설정 파일인 pool1.jocl은 커넥션 풀과 관련된 속성을 지정하지 않고 있다. DBCP의 커넥션 풀은 최대 커넥션 개수, 최소 유휴 커넥션 개수, 최대 유휴 커넥션 개수, 유휴 커넥션 검사 여부 등의 속성을 지정할 수 있다. pool1.jocl을 보면 다음과 같은 코드가 있는데,

  <object class="org.apache.commons.pool.impl.GenericObjectPool">
    <object class="org.apache.commons.pool.PoolableObjectFactory" null="true" />
  </object>

이 코드에 커넥션 풀과 관련된 속성 정보를 추가하면 된다. 예를 들면, 아래 코드와 같이 커넥션 풀 속성 정보를 추가하면 된다.

    
    파일명: pool\WEB-INF\classes\pool2.jocl    
    <object class="org.apache.commons.dbcp.PoolableConnectionFactory"
        xmlns="http://apache.org/xml/xmlns/jakarta/commons/jocl">
    
      <object class="org.apache.commons.dbcp.DriverManagerConnectionFactory">
        <string value="jdbc:mysql://localhost:3306/chap11?..." />
        <string value="jspexam" />
        <string value="jspex" />
      </object>
      
      <object class="org.apache.commons.pool.impl.GenericObjectPool">
        <object class="org.apache.commons.pool.PoolableObjectFactory" null="true" />
        <int value="10" />  <!-- maxActive -->
        <byte value="1" />  <!-- whenExhaustedAction -->
        <long value="10000" /> <!-- maxWait -->
        <int value="10" /> <!-- maxIdle -->
        <int value="3" /> <!-- minIdle -->
        <boolean value="true" /> <!-- testOnBorrow -->
        <boolean value="true" /> <!-- testOnReturn -->
        <long value="600000" /> <!-- timeBetweenEvctionRunsMillis -->
        <int value="5" /> <!-- numTestsPerEvictionRun -->
        <long value="3600000" /> <!-- minEvictableIdleTimeMillis -->
        <boolean value="true" /> <!-- testWhileIdle -->
      </object>      
      <object class="org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory"
          null="true" />
      
      <string null="true" />
      
      <boolean value="false" />
      
      <boolean value="true" />
    </object>

굵게 표시한 부분이 커넥션 풀의 속성과 관련된 부분인데, 각 속성의 값이 무엇을 의미하는 지 우측에 주석으로 표시하였다. 각 속성이 의미하는 것은 다음표와 같다.

속성 설명
maxActive 커넥션 풀이 제공할 최대 커넥션 개수
whenExhaustedAction 커넥션 풀에서 가져올 수 있는 커넥션이 없을 때 어떻게 동작할지를 지정한다. 1일 경우 maxWait 속성에서 지정한 시간만큼 커넥션을 구할 때 까지 기다리며, 0일 경우 에러를 발생시킨다. 2일 경우에는 일시적으로 커넥션을 생성해서 사용한다.
maxWait whenExhaustedAction 속성의 값이 1일 때 사용되는 대기 시간. 단위는 1/1000초이며, 0 보다 작을 경우 무한히 대기한다.
maxIdle 사용되지 않고 풀에 저장될 수 있는 최대 커넥션 개수. 음수일 경우 제한이 없다.
minIdle 사용되지 않고 풀에 저장될 수 있는 최소 커넥션 개수.
testOnBorrow true일 경우 커넥션 풀에서 커넥션을 가져올 때 커넥션이 유효한지의 여부를 검사한다.
testOnReturn true일 경우 커넥션 풀에 커넥션을 반환할 때 커넥션이 유효한지의 여부를 검사한다.
timeBetweenEvctionRunsMillis 사용되지 않은 커넥션을 추출하는 쓰레드의 실행 주기를 지정한다. 양수가 아닐 경우 실행되지 않는다. 단위는 1/1000 초이다.
numTestsPerEvictionRun 사용되지 않는 커넥션을 몇 개 검사할지 지정한다.
minEvictableIdleTimeMillis 사용되지 않는 커넥션을 추출할 때 이 속성에서 지정한 시간 이상 비활성화 상태인 커넥션만 추출한다. 양수가 아닌 경우 비활성화된 시간으로는 풀에서 제거되지 않는다. 시간 단위는 1/1000초이다.
testWhileIdle true일 경우 비활성화 커넥션을 추출할 때 커넥션이 유효한지의 여부를 검사해서 유효하지 않은 커넥션은 풀에서 제거한다.

몇몇 속성은 성능에 중요한 영향을 미치기 때문에 웹 어플리케이션의 사용량에 따라서 알맞게 지정해주어야 하는데, 다음과 같이 고려해서 각 속성의 값을 지정하는 것이 좋다.

  • maxActive - 사이트의 최대 커넥션 사용량을 기준으로 지정. 동시 접속자수에 따라서 지정한다.
  • minIdle - 사용되지 않는 커넥션의 최소 개수를 0으로 지정하게 되면 풀에 저장된 커넥션의 개수가 0이 될 수 있으며, 이 경우 커넥션이 필요할 때 다시 커넥션을 생성하게 된다. 따라서 커넥션의 최소 개수는 5개 정도로 지정해두는 것이 좋다.
  • timeBetweenEvctionRunsMillis - 이 값을 알맞게 지정해서 사용되지 않는 커넥션을 풀에서 제거하는 것이 좋다. 커넥션의 동시 사용량은 보통 새벽에 최저이며 낮 시간대에 최대에 이르게 되는데 이 두 시간대에 필요한 커넥션의 개수 차이는 수십개에 이르게 된다. 이때 최대 상태에 접어들었더가 최소 상태로 가게 되면 풀에서 사용되지 않는 커넥션의 개수가 점차 증가하게 된다. 따라서 사용되지 않는 커넥션은 일정 시간 후에 삭제되도록 하는 것이 좋다. 보통 10~20분 단위로 사용되지 않는 커넥션을 검사하도록 지정하는 것이 좋다.
  • testWhileIdle - 사용되지 않는 커넥션을 검사할 때 유효하지 않은 커넥션은 검사하는 것이 좋다.
관련링크:

+ Recent posts