Connection Pool
JDBC 코딩 과정은 드라이버로드-연결-쿼리실행-연결종료이다.
이 중 일반적으로 가장 많은 시간이 걸리는건 연결부분이다.
conn=DriverManager.getConnection(
"jdbc:oracle:thin:@127.0.0.1:1521:xe","jsp","oracle");//DB연결
이 코드가 쓰여질 때마다 DB에 연결해서 connection객체를 생성하는 과정을 거친다.
연결을 한 후 connection 객체를 자바 메모리에서 저장하고 관리하면 어떨까?
이 때 Connection Pool을 사용한다.
웹 컨테이너(WAS)가 실행되면서 DB와 미리 connection(연결)을 해놓은 객체들을
pool에 저장해두었다가 클라이언트 요청이 오면 connection을 빌려주고,
처리가 끝나면 다시 connection을 반납받아 pool에 저장하는 방식을 말합니다.
실제로 Connection 객체를 위한 DB연결은 Connection Pool에 저장할 때 한번만 하고
웹 애플리케이션에서는 Connection Pool에 저장된 객체를 사용한다.
그리고 Connection Pool을 사용하지 않았을 때는
Connection.close()를하면 실제DB와의 연결이 끊어지지만
Connection Pool을 이용해 Connection 객체를 얻었을 때는
Connection.close()는 단순히 ConnectionPool로부터 빌린 객체를 반환한다.
(실제DB와의 연결이 끊기는 것이 아니다.)
커넥션 풀(DBCP) 특징
- 웹 컨테이너(WAS)가 실행되면서 connection 객체를 미리 pool에 생성해 둔다.
- HTTP 요청에 따라 pool에서 connection객체를 가져다 쓰고 반환한다.
- 이와 같은 방식으로 물리적인 데이터베이스 connection(연결) 부하를 줄이고 연결 관리 한다.
- pool에 미리 connection이 생성되어 있기 때문에
connection을 생성하는 데 드는 요청 마다 연결 시간이 소비되지 않는다. - 커넥션을 계속해서 재사용하기 때문에 생성되는 커넥션 수를 제한적으로 설정한다.
동시 접속자가 많을 경우
- 위에 커넥션 풀 설명에 따르면,
동시 접속 할 경우 pool에서 미리 생성 된 connection을 제공하고
남은 connection이 없는 경우는 사용자는 connection이 반환될 때까지
번호순대로 대기상태로 기다린다. - 여기서 WAS에서 커넥션 풀을 크게 설정하면
메모리 소모가 큰 대신 커넥션 갯수가 늘어나 대기시간이 줄어들고,
반대로 커넥션 풀을 적게 설정하면 그 만큼 대기시간이 길어진다.
DBCP 적용 전에 좀 더 자세한 내용을 알고 싶다면
https://brilliantdevelop.tistory.com/174 참고하자.
DBCP(DataBase Connection Pool) 적용하기
DBCP를 사용하기 위해선 관련 라이브러리들을 다운받아야한다.
Maven repostiroy에서
commons-dbcp2-2.1.1.jar
commons-pool2-2.4.2jar
commons-logging-1.2.jar 를 다운받자.
dbcp, pool, logging로 검색.
다운받은 jar파일들을 WEB-INF/lib폴더에 넣어준다.
자 이제 Connection Pool을 사용할 준비는 끝났다.
앞으로 우리가 만들 예제에는 직접 DB에 연결하는 코드대신
Connection Pool을 이용해 Connection 객체를 얻을 코드를 사용하려고 한다.
그렇다면 JSP 실행전에 Connection Pool에 Connection이 등록되어있어야한다.
그렇다면 어떻게 jsp실행 전, Connection Pool에 Connection이 등록이 되어있을까?
바로 서블릿을 이용해 서버가 처음 실행될 시 등록하면된다
또 드라이버로드도 한번만 하면 되기때문에
매번 JSP 실행시마다 드라이버 로드 코드인 Class.forName()을 쓰기보다는
서버 실행 시 드라이버로드 한번만 하고
다른곳에서는 Class.forName()을 쓰지 않는 것이 좋다.
즉 서버실행시 드라이버로드 + 커넥션풀에 connection 등록, 이렇게 2가지를 하는것이 좋다.
이를 위해 서블릿 ConnectionPoolServlet.java를 만들어 보자.
ConnectionPoolServlet .java
package com.study.servlet;
import java.sql.DriverManager;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.PoolingDriver;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
@SuppressWarnings("serial")
public class ConnectionPoolServlet extends HttpServlet {
@Override
public void init() throws ServletException {
loadJDBCDriver();
initConnectionPool();
}
//ApplicationLoader가 하는역할
private void loadJDBCDriver() {
// 1. 드라이버 로드
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
System.out.println("오라클 드라이버 로딩 성공");
} catch (ClassNotFoundException e) {
throw new RuntimeException("fail to JDBC Driver ", e);
}
} // loadJDBCDriver
private void initConnectionPool() {
try {
// 커넥션 풀이 새로운 커넥션을 생성할 때 사용할 커넥션 팩토리를 생성한다.
ConnectionFactory connFactory =
new DriverManagerConnectionFactory(
"jdbc:oracle:thin:@127.0.0.1:1521:xe","jsp","oracle");
PoolableConnectionFactory poolableConnFactory
= new PoolableConnectionFactory(connFactory, null);
poolableConnFactory.setValidationQuery("select 1 from dual");
// 커넥션이 유효한지 확인하기 위한 쿼리
@SuppressWarnings("rawtypes")
GenericObjectPoolConfig poolConofig = new GenericObjectPoolConfig();
// 커넥션풀의 설정정보. 유휴 커넥션 검사주기, 검사여부, 커넥션 최소,최대 갯수
poolConofig.setTimeBetweenEvictionRunsMillis(1000L *60L * 10L); // 10분
poolConofig.setTestWhileIdle(true);
poolConofig.setMinIdle(4);
poolConofig.setMaxTotal(4); // default는 8. 수업때문에 4개로 줄인것
@SuppressWarnings("unchecked")
GenericObjectPool<PoolableConnection> connectionPool
= new GenericObjectPool<>(poolableConnFactory, poolConofig);
poolableConnFactory.setPool(connectionPool);
// 커넥션 풀 등록
Class.forName("org.apache.commons.dbcp2.PoolingDriver");
PoolingDriver driver = (PoolingDriver)DriverManager.getDriver("jdbc:apache:commons:dbcp:");
driver.registerPool("study",connectionPool);
System.out.println("DBCP study 등록 성공");
} catch (Exception e) {
throw new RuntimeException(e.getMessage(),e);
}
} // initConnectionPool
} // class
또 web.xml에 다음과 같은 태그를 추가하자.
<servlet> <servlet-name>driver</servlet-name>
<servlet-class>com.study.servlet.ConnectionPoolServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
DriverLoader.java에서 중요한건 처음의 init()메소드이다.
이는 서블릿 요청시 한번만 실행되는 코드다.
web.xml에 url패턴대신 <load on startup> 태그를 사용함으로서
url요청이아닌 서버 실행될 때 한번 실행하도록 한다.
즉 서버가 실행될 때 init()메소드 안의
loadJDBCDriver(), initConnectionPool() 가 실행되면서
드라이버로딩과 Connection Pool에 Connection 등록이 완료된다.
Connection Pool 관련 코드는 주석을 천천히 읽어보자.
여기서는 Connection Pool관련 코드를 완전히 익히는것보단
Connection Pool의 개념을 이해하고, 서버가 시작될 때
Connection Pool에 Connection이 등록되었군. 하는정도만 알고 넘어가자.
이제 Connection Pool을 이용해 DB에 연결해보자.
DBCP를 사용하지 않았을 경우 매 요청마다 실제 DB에 연결을 많이했지만
Connection Pool을 사용할 경우 실제 DB연결은
Connection Pool에 등록된 Connection 수만큼만 하고
요청마다 ConnectionPool에서 connection 객체를 얻으면 된다.
//conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:xe", "jsp", "oracle"); //실제 DB연결
conn = DriverManager.getConnection("jdbc:apache:commons:dbcp:study"); //커넥션풀한테 Connection 객체 요청