▸JSP & Servlet/기본 문법

JDBC를 이용한 DB작업_커넥션풀(Connection Pool) [3/3]

코데방 2020. 2. 20.
728x90

[ DBCP (DataBse Connection Pool) ]

  • 스레드 풀과 마찬가지로 커넥션 객체를 모아둔 개념
  • 커넥션 객체 생성과 삭제에 드는 오버헤드를 방지하기 위한 객체 재활용 방식

커넥션풀은 스레드풀과 비슷한 개념입니다. DB도 외부 리소스이기 때문에 커넥션을 생성하고 삭제하는 것은 오버헤드가 발생합니다. 또한 DBMS 입장에서도 커넥션을 계속 맺고 끊어야하기 때문에 오버헤드가 발생합니다. 따라서 커넥션 객체를 일정량 모아둔 Pool을 생성해두고 계속 재활용하는 방식입니다. 요청을 처리하는 각 스레드에서 커넥션을 따로 생성해주지 않고 커넥션풀의 커넥션 객체를 할당받아 사용한 뒤 반납합니다.

 

 


 

 

[ 커넥션풀(Connection Pool) 생성 ]

  • javax.naming.Context 인터페이스 / InitialContext 클래스를 이용해 생성
  • 커넥션풀을 관리해주는 javax.sql.DataSource 객체가 동시에 생성됨
  • context.xml 파일에 DB 관련 설정 정보 입력 필요

javax.naming.InitalContext 클래스는 생성자가 없지만 생성될 때 context.xml 파일에 기입해둔 정보를 읽어옵니다.  이 정보는 컨테이너가 구동되면서 미리 읽어온 뒤 저장하고 있습니다. DB 정보를 입력하였으므로 내부적인 로직을 통해 DB와 연동해 커넥션풀을 생성한 뒤, type으로 지정해둔 DataSource 객체를 만들어 보관합니다. 이 객체는 커넥션풀을 관리해주는 역할을 합니다. 

 

DB연결 정보는 Connection 객체 생성 때와 같습니다. 다만 커넥션풀 내의 커넥션 객체들에 대한 추가적인 설정을 해줘야 합니다. 다섯 가지 설정은 아래와 같습니다. DBCP1와 DBCP2가 설정값 이름이 다른부분이 있습니다.

 

커넥션풀 설정값 (DBCP2에서 이름) 설명
initialSize 커넥션풀 생성 시 최초 생성한 Connection 객체의 수 (기본값 0)
minIdle 최소한으로 유지될 Connection 객체의 수 (기본값 0)
maxIdle 반납된 유휴 Connection 객체를 유지할 수 있는 최대 값 (기본값 8)
maxActive (maxTotal) 동시에 사용할 수 있는 최대 커넥션 갯수 (기본값 8)
maxWait (maxWaitMillis) 할당받을 Connection 객체가 없을 때 스레드를 블록시킬 시간 (1/1000초 단위)

 

이 설정 값은 커넥션풀과 DB 작업 성능에 큰 영향을 줄 수 있으니 개념을 잘 알아놔야 합니다. 서버 스펙, 접속자 수 등의 여러 요소를 파악해서 설정해야 합니다. 아래 값들은 생략하면 기본값으로 셋팅됩니다.

 

* InitialSize

최초 커넥션풀이 생성된 후 실제 DB와 연결되는 Connection 객체를 몇 개 생성할지를 결정해줍니다. 

 

* minIdle

Connection 객체가 유지될 최소 갯수입니다. 만약 오류가 나서 연결이 끊어지면 해당 Connection 객체가 제거되는데, 이렇게 객체가 제거되다가 minIdle 설정값 이하로 떨어지면 다시 새로 만들어서 숫자를 채워줍니다.

 

* maxIdle

minIdle 설정값은 최소 유지 갯수입니다. 그것보다 더 많은 경우에 대해서는 관여하지 않습니다. 만약 현재 5개의 Connection 객체가 생성돼 있는데 사용자 요청이 들어올 경우 추가적으로 생성해서 사용합니다. 이 커넥션은 사용이 끝난 뒤 반납되어 유휴상태가 되는데 이 때 유지할 최대 갯수를 설정하는 값입니다. maxIdle이 10일 때 11번째부터 생성된 커넥션은 반납되면 자원이 해제됩니다.

 

* maxActive

동시에 사용할 수 있는 Connection 객체의 최대 값입니다. 이 값이 바로 커넥션풀에서 가질 수 있는 최대 Connection 객체의 갯수가 됩니다. maxIdle 값보다 더 많이 생성될 수 있지만 maxActive 값보다 더 많이 생성될 수는 없습니다. 다만 maxIdle 값보다 더 많이 생성된 객체는 반납 후 해제된다는 차이가 있습니다. 따라서 이 두 개의 설정은 같은 값으로 맞춰주는 것이 일반적입니다.

 

* maxWait

요청을 처리하는 스레드가 커넥션풀에 Connection 객체를 요청했을 때 존재하는 커넥션이 모두 사용중이고 더 만들 수도 없다면 스레드는 잠시 블로킹(Blocking) 됐다가 다시 요청을 합니다. 그러고도 커넥션을 사용하지 못한다면 예외가 발생합니다. 이 때 스레드가 블로킹 되는 시간을 설정하는 값입니다. 너무 길면 사용자가 오래 기다려야 할테고, 블로킹되는 스레드가 늘어나다보면 컨테이너의 스레드풀이 가진 스레드 갯수를 초과해버리는 문제가 생길 수 있습니다. 또한 너무 짧으면 사용자가 오류 메세지를 받을 확률이 높아지기 때문에 적절한 시간으로 설정해줘야 합니다. 

 

- context.xml 설정 코드

 

<Resource
        auth="Container"
        driverClassName ="oracle.jdbc.driver.OracleDriver"
        url="jdbc:oracle:thin:@localhost:1521:xe"
        username="admin"
        password="oracle"
        name="jdbc/Oracle11g"
        type="javax.sql.DataSource"
	initialSize="5"
	minIdle="5"
	maxIdle="50"
	maxActive="50"
	maxWait="1000"
    />

 

- Context 객체 생성

InitialContext 클래스는 Context 인터페이스를 상속받았으므로 오버라이딩해서 생성해줍니다. 

 

		try {
			Context context = new InitialContext();
		} catch (NamingException e) {
			e.printStackTrace();
		}

 

 


 

 

[ 스레드풀 관리자 객체 생성 ]

  • javax.sql.DataSource 클래스

해당 객체는 커넥션풀을 관리해줍니다. Context 객체에서 메소드를 통해 Object 타입으로 반환 받은 뒤 타입 캐스팅을 통해 변환해줍니다. Object 타입으로 받는 이유는 외부 리소스 종류에 따라 관리하는 클래스가 달라질 수 있기 때문입니다.

 

"java:comp/env/" 까지는 고정이고 그 뒤는 context.xml 파일에 기입할 정보 중 name 값을 넣어주면 됩니다.

 

		DataSource dataSource;
		try {
			Context context = new InitialContext();
			dataSource = (DataSource)context.lookup
					("java:comp/env/jdbc/Oracle11g");
		} catch (NamingException e) {
			e.printStackTrace();
		}

 

 

* 톰캣 구동 시 Cannot load JDBC driver class 'oracle.jdbc.driver.OracleDriver' 에러 메세지가 뜨는 경우, 톰이 설치된 폴더 안에 'lib' 폴더를 찾아 드라이버 라이브러리 파일(.jar)을 넣어주면 됩니다. 

 

 


 

 

[ 사용 가능한 Connection 객체 얻어오기 ]

  • DataSource 객체의 getConnection() 메소드 사용

사용할 수 있는 Connection 객체만 얻어오면 나머지 사용은 기존과 동일합니다. 아래는 전체 예시 코드입니다.

 

package control;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

@WebServlet("/")
public class Controller extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public Controller() {
		super();
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doAct(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doAct(request, response);
	}

	protected void doAct(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		DataSource dataSource = null;
		Connection conn = null;
		Statement stmt = null;
		ResultSet result = null;

		try {
			// 커넥션풀 관리 객체 생성
			Context context = new InitialContext();
			dataSource = (DataSource) context.lookup
					("java:comp/env/jdbc/Oracle11g");
		} catch (NamingException e) {
			e.printStackTrace();
		}

		try {
			
			// 커넥션 객체 얻기
			conn = dataSource.getConnection();
			
			// 이후는 DB작업 동일
			stmt = conn.createStatement();
			result = stmt.executeQuery("select * from ORG_USER");

			String id, pw;
			while (result.next()) {
				id = result.getString("user_id");
				pw = result.getString("user_pw");
				System.out.println(id + " : " + pw);
			}

		} catch (SQLException e) {
			e.printStackTrace();

		// 자원 반납 필수
		} finally {
			if (result != null)
			try {				
				result.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			if (stmt != null)
			try {
				stmt.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			
			if (conn != null)
			try {
				conn.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}
}
728x90

댓글

💲 추천 글