<< 학습 목표 >>
1. Connection Pool을 사용해야하는 이유를 설명할 수 있다.
2. Connection Pool을 사용하도록 설정할 수 있다.
3. Connection Pool에서 Connection을 꺼내는 코드를 작성할 수 있다.
이 글에서는 Connection Pool을 사용해야하는 이유와 Connection Pool에 대해서 배울 것
이전 글에서 만든 회원 가입 서비스를 면밀히 분석해보자
회원 가입 서비스의 동작 순서를 시각화한 그림을 보여줄텐데 모든 상황을 시각화 하면 그림이 복잡해지므로 회원 가입에 성공하는 상황만 시각화를 해보자

시각화한 그림을 흐름도(flow chart) 라고 부름
흐름도에서 회원가입 컨트롤러부터 코드와 비교해서 보자
( 클릭해서 확대해서 보세요 )

파라미터 검증 부분은 흐름도에서 생략했음
클라이언트에서 보낸 회원 정보를 임시로 저장하기 위해 DTO 를 생성하고 생성과 동시에 정보를 임시로 저장하고 있음

회원 정보를 DB에 저장 하기 위해 서비스 객체를 생성하고 회원 가입 메서드인 join 메서드를 호출하고 있음
join 메서드를 호출하면서 저장할 회원 정보를 인자로 전달했음
( 컨트롤러 내 코드를 어디까지 봤는지 잘 기억해두자 )

서비스의 join 메서드를 호출했으니 이제 서비스로 넘어가서 보자

서비스의 회원가입 기능(join 메서드)에서 DB와 통신을 해야하므로 DAO 객체를 생성하고 있음

그 후 서비스에서는 DAO를 사용해 id 중복 여부 확인(1), 연락처 중복 여부 확인(2), 회원가입(3) 처리를 하고 있음

아이디 중복 여부를 확인하기 위해서 selectOneById 메서드를 호출하고 있음
selectOneById 메서드를 호출하면서 사용자가 입력한 아이디를 인자로 전달하고 있음
( 컨트롤러 내 어디까지 봤는지 잘 기억해야하고 서비스의 어디까지 봤는지도 잘 기억하자 )

DAO의 selectOneById 메서드를 호출했으니 이제 DAO로 넘어가서 보자

쿼리를 전송하기 위해 "DB에 접속"한 후 SELECT 쿼리를 준비하고 보낸 후 실행했음
그 다음 DB로부터 결과를 받아와 rs 객체에 저장했음

! 여기서 잠깐 ! 다른 것 보다 "DB에 접속" 을 강조했음
Connection Pool을 왜 사용하는지 알기 위해서는 "쿼리를 보내고 실행한 후 결과를 받아오기 위해 DB에 접속했다" 가 굉장히 중요한 포인트임
굉장히 중요한 포인트라 흐름도에 그렸어야 했지만 그림이 너무 길어져 작게 보여 어쩔 수 없이 생략했음
다시 DAO 로 돌아가서 id 로 조회한 결과가 있다면 if문 안으로 들어가 조회한 데이터를 member 객체(DTO)에 담음
id 로 조회한 결과가 없다면 if문을 건너뛰어 member 객체(DTO)는 그대로 null 상태임
( 이미지는 생략했음 )
selectOneById 메서드에서 조회 결과를 반환하기 전에 "DB와 관련된 자원을 해제" 한 후 조회 결과를 반환함
! 여기서 잠깐 ! "DB에 접속" 을 강조했던 것처럼 여기서도 "DB와 관련된 자원을 해제" 를 강조했음
"DB와 관련된 자원을 해제" 한다는건 rs.close(), pstmt.close(), conn.close() 를 하는 것인데 conn.close() 는? DB 접속을 끊는 것
역시나 Connection Pool 을 왜 사용하는지 알기 위해서는 "쿼리를 보내고 실행한 후 결과를 받아온 다음에 DB 접속을 끊었다" 가 굉장히 중요한 포인트임
굉장히 중요한 포인트라 흐름도에 그렸어야 했지만 그림이 너무 길어져 작게 보여 어쩔 수 없이 생략했음
강조한 부분을 정리해보면 "쿼리를 보내고 실행한 후 결과를 받아오기 위해 [ DB에 접속 ] 했고 결과를 받아온 다음 [ DB 접속을 끊음 ]" 임
DAO가 결과를 반환했으니 다시 서비스의 selectOnById 메서드를 호출한 부분으로 돌아가자
DAO의 selectOneById 메서드가 반환한 정보가 있다면 if 문 안으로 들어가 서비스를 빠져나가 컨트롤러에게 결과(duplicate_id) 를 전달함 / 그러나 회원 가입에 성공한 상황만 본다고 했으므로 selectOneById 메서드가 반환한 정보가 없다고 하자

그 다음 연락처 중복 여부를 확인함
연락처 중복 여부 확인부터는 중요한 포인트만 언급하겠음
연락처 중복 여부 확인은 아이디 중복 여부 확인과 마찬가지로 DAO에서 쿼리를 보내고 실행한 후 결과를 받아오기 위해 [ DB에 접속 ] 했고 결과를 받아온 다음 [ DB 접속을 끊음 ]

서비스의 마지막으로 아이디, 연락처가 중복 되지 않았을 때는 회원가입을 진행함
회원 가입 또한 마찬가지로 DAO에서 쿼리를 보내고 실행한 후 결과를 받아오기 위해 [ DB에 접속 ] 했고 결과를 받아온 다음 [ DB 접속을 끊음 ]

회원 가입에 성공했다면 서비스는 컨트롤러에게 "OK" 를 반환하고 어떤 이유로 인해 회원 가입에 실패했다면 서비스는 컨트롤러에게 "unknown error" 을 반환함

이제 컨트롤러로 돌아가서 컨트롤러는 서비스가 반환한 결과에 따라 상태 코드를 클라이언트에게 전달함

여기까지 다소 길었지만 우리가 앞서 만든 회원 가입 서비스의 흐름을 알아봤음
중간에도 언급했지만 우리가 회원 가입 서비스의 흐름을 알아본 이유는 Connection Pool 을 왜 사용하는지 알기 위함임
지금까지가 서론이었고 이제부터 본론으로 들어가자~!
한 사람이 회원가입 하기 위해 DB에 접속하고 접속을 끊은 횟수는? 3번
1. 아이디 중복 여부를 확인하기 위해
2. 연락처 중복 여부를 확인하기 위해
3. 회원 가입을 하기 위해
열 사람이 회원 가입을 한다면 DB에 접속하고 접속을 끊는 횟수는? 30번
서비스가 대박나 천 사람, 만 사람이 회원 가입을 한다면 DB에 접속하고 접속을 끊는 횟수는? 3,000번 30,000번
이렇게 클라이언트의 요청 한번에 DB와 접속, 접속 끊기 횟수가 굉장히 많기 때문에 DB와 접속, 접속 끊기는 최소한으로 이뤄지는게 바람직함
DB와 접속, 접속 끊기를 최소화 해줄 수 있는 기술이 Connection Pool 임
Connection Pool은 미리 Connection 객체(DB에 접속)를 여러 개 만들어둠
그 후 DAO에서 Connection 객체(DB에 접속)가 필요할 때 Connection Pool에 있는 Connection 객체를 가져옴
DAO에서는 DB에 접속 하는 과정 대신 Connection Pool에서 Connection 객체를 가져오는 과정으로 바뀜
그 이후에는 지금의 DAO와 똑같이 쿼리를 보내고 결과를 가져옴
그리고 마지막으로 DB와 관련된 모든 자원들을 close 함
이때 conn.close() 를 해서 접속을 끊을텐데 Connection Pool 에서 가져온 Connection 객체를 close() 하게 되면 접속을 끊는게 아니라 가져왔던 Connection 객체를 Connection Pool에 반납하는 과정으로 바뀜
이전까지 DAO의 과정과 지금부터 사용할 Connection Pool을 사용한 DAO의 과정을 시각적으로 보자

이전까지 DAO에서는 쿼리 하나를 전송 & 실행 & 결과 받기 를 하기 위해 접속, 끊기를 해야함
이전까지 DAO에서는 쿼리를 여러 번 전송 & 실행 & 결과 받기 를 하기 위해 접속, 끊기를 여러 번 해야함
Connection Pool을 사용한 DAO에서는 [ 쿼리 하나를 전송 & 실행 & 결과 받기 ] 를 하기 위해 접속, 끊기를 하지 않음
Connection Pool을 사용한 DAO에서는 [ 쿼리를 여러 번 전송 & 실행 & 결과 받기 ] 를 하기 위해 접속, 끊기를 하지 않음
접속 대신 Connection Pool에서 Connection 객체 가져오기를 함
끊기 대신 Connection 객체를 Connection Pool에 반납 함
Connection Pool은 톰캣이 제공하는 것이라 위 흐름도에서 Connection Pool에 톰캣의 이미지를 넣어놨음
톰캣에 Connection Pool 관련된 설정을 해두면 서버가 실행될 때 설정에 따라 Connection 객체(DB에 접속)을 생성함
따라서 이제는 서버가 실행될 때 여러 번의 DB 접속이 이뤄짐
그에 따라 서버가 실행될 때는 서버와 DB에 다소 부담이 생길 수 있음
그러나 서버가 완전히 실행되고 나서는 몇 번의 쿼리를 보내든 서버와 DB에 부담이 없음
( 완전히 없지는 않고 Connection Pool을 사용하기 전 보다 상당히 완화 되었음 )
이제 DAO가 Connection Pool 을 사용하기 위해서 톰캣에 Connection Pool 관련된 설정을 하자
톰캣에 Connection Pool 관련된 설정을 하려면 우선 DBCP 라이브러리를 다운 받아야함
DBCP는 DataBase Connection Poo의 약자임
라이브러리를 다운 받기 위해 구글에서 maven repositry 검색 후 첫 번째 검색 결과(1)로 들어가자
( 만약 검색 결과가 이와 다르다면 직접 들어가자 / https://mvnrepository.com/ )

tomcat dbcp 로 검색(1) 후 첫 번째 검색 결과로 들어가자(2)
( 만약 검색 결과가 이와 다르다면 직접 들어가자 / https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-dbcp )

적당한 버전을 선택해 들어가자(1)

DBCP 라이브러리가 압축되어있는 jar 형식 압축 파일을 다운 받자(1)

DBCP 라이브러리 추가의 마지막으로 다운로드 받은 jar 형식의 라이브러리를 프로젝트 -> src -> main -> webapp -> WEB-INF -> lib 로 드래그 & 드롭하자

여기까지 라이브러리 추가 완료
이제 톰캣에 Connection Pool 관련 설정을 하자
톰캣에 Connection Pool 관련 설정을 할 때는 context.xml(1) 에 해야함

간혹 인터넷에서 보면 프로젝트 -> WEB-INF -> web.xml 에 Connection Pool 관련된 설정을 하는 글들이 있는데 context.xml 대신 web.xml에 Connection Pool 설정을 하기도 하지만 web.xml에 Connection Pool을 설정하면 해당 프로젝트에서만 Coonection Pool 을 사용할 수 있음
( "이게 무슨 차이지?" 싶은 분들은 https://codingaja.tistory.com/17 이 글로 가서 [ 웹 서버 안에는 하나 또는 다수의 다양한 웹 서비스가 실행될 수 있음 ] 부분까지 읽어보고 오자 )
- context.xml 에 Connection Pool 설정을 한다 => 서버 단위로 Connection Pool 설정을 하므로 해당 서버 내 모든 웹 프로젝트들이 Connection Pool을 사용할 수 있음
- web.xml 에 Connection Pool 설정을 한다 => 웹 프로젝트 단위로 Connection Pool 설정을 하므로 해당 서버 내 웹 프로젝트 마다 web.xml에 Connection Pool 설정을 해줘야함
다시 Connection Pool 설정으로 돌아와서 context.xml 에 Connection Pool 설정을 하자
context.xml 의 가장 마지막인 </Context> 의 바로 위에 Resource 태그를 추가하자
Resource 태그가 Connection Pool 설정을 하는 태그

| name 속성 |
| Connection Pool의 이름 |
| auth 속성 |
| 인증 주체 ( 정확한 용도는 잘 모르겠음 ) |
| type 속성 |
| Connection Pool의 데이터 타입 |
| driverClassName 속성 |
| 사용하는 DB 마다 정해진 driver 이름 / mariadb가 아닌 DB를 사용하는 분들은 해당 DB에 맞는 driver 이름을 입력하면 됨 |
| url 속성 |
| DB에 접속하기 위한 URL |
| username 속성 |
| DB에 접속하기 위한 사용자 아이디 |
| password 속성 |
| DB에 접속하기 위한 사용자 비밀번호 |
| maxTotal 속성 |
| 동시에 사용할 수 있는 최대 Connection 개수 간단하게 서버가 시작되면 Connection Pool에서는 Connection 객체가 50개 생성된다고 생각하면 됨 톰캣 서버와 DB 서버의 사양을 고려해 적절한 값으로 설정함 취업 준비를 할 때는 마음대로 갯수를 설정해도 되지만 취업을 하고 나서 maxTotal 속성을 건드릴 일이 있다면 상사에게 물어보면 됨 |
| maxIdle 속성 |
| Connection Pool에 반납할 때 최대로 유지될 수 있는 Connection 개수 |
| maxWaitMillis 속성 |
| Connection Pool에서 Connection을 꺼내려고 하는데 모든 Connection이 사용중이여서 꺼낼 Connection이 없을 때 최대로 기다릴 시간 maxTotal 속성을 사용해서 DAO들이 사용할 Connection을 준비했는데 DAO들이 처리할 작업이 복잡해 Connection을 오래 사용하는 상황이라 Connection Pool에 있는 모든 Connection을 빌려갔는데 또 다른 DAO에서 Connection Pool에 있는 Connection을 꺼내려고 하면 Connection Pool에서는 빌려줄 Connection 이 없음 이때 DAO는 maxWaitMillis 만큼 기다리면서 Connection Pool이 Connection을 빌려주기를 기다림 maxWaitMillis 속성의 값은 밀리초 단위로 지정하며 1000 으로 하면 DAO가 Connection을 빌려주기를 1초 동안 기다린다는 것 이 속성의 기본값은 -1 로 -1은 DAO가 Connection을 빌려주기를 무한정 기다린다는 것 ( 빌려줄 때까지 계속 대기하겠다는 것 ) 취업 준비를 할 때는 -1 로 설정해도 상관 없지만 취업을 하고 나서 maxWaitMillis 속성을 건드릴 일이 있다면 상사에게 물어보면 됨 |
Connection Pool 설정과 관련된 속성은 이외에도 더 많으니 Coonection Pool 설정을 더 세세하게 하고 싶다면 직접 찾아보자
Connection Pool 과 관련된 제대로된 이해가 필요하다면 https://d2.naver.com/helloworld/5102792 이 블로그의 글을 보자
다시~! 본론으로 돌아와서 잠깐 지금까지 한 것들을 정리하자
1. DAO에서 Connection Pool을 사용하기 위해 DBCP 라이브러리를 추가했음
2. DAO에서 Connection Pool을 사용하기 위해 context.xml 에 Connection Pool 설정을 했음
여기까지 DAO에서 Connection Pool을 사용하기 위한 설정이 끝났으니 이제 DAO에서 Connection Pool을 사용하자
DAO에서 Connection Pool을 사용하기 위해서는 DAO 자체의 코드를 바꿀 필요는 없음
왜? 우리는 앞서 DBUtil 클래스에 getConnection 메서드를 선언해 DAO에서 사용할 Connection을 생성해 반환하도록 했으므로...
따라서~! 우리가 바꿔야하는건 DBUtil 클래스의 getConnection 메서드 뿐!
DBUitl 클래스의 getConnection 메서드에서 Connection Pool 을 가져온 뒤 Connection Pool 에서 Connection을 꺼내 반환하면 됨
아래 코드를 DBUtil 클래스의 getConnection 메서드에 입력하자
( Connection을 위해 선언해뒀던 멤버 변수들은 모두 지웠음 )
package utility;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
public class DBUtil {
public static Connection getConnection() {
Connection conn = null;
try {
Context ctx = new InitialContext();
Context envCtx = (Context) ctx.lookup("java:/comp/env");
DataSource ds = (DataSource) envCtx.lookup("jdbc/mariadb/cp1");
conn = ds.getConnection();
} catch (NamingException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
...
}
Connection Pool을 가져오기 위해서는 JNDI ( Java Naming and Directory Interface ) 를 사용해야함
톰캣이 실행될 때 Connection Pool 설정이 되어있다면 설정에 따라 Connection Pool 을 생성한 뒤 JNDI 에 Connection Pool을 저장해둠
JNDI 를 가져오기 위해 객체를 생성(1) 한 후 JNDI 를 가져옴(2)
그 후 JNDI 에서 jdbc/mariadb/cp1 이라는 이름의 값을 꺼낸 후(3) Connection Pool 로 형변환(4) 했음

JNDI 에서 값을 꺼낼 때 사용한 이름은 우리가 Resource 태그에 지정한 name 속성임
Connection Pool 로 형변환이 (Connection Pool) 이 아니라 (DataSource) 인 이유는 Resource 태그에 지정한 Connection Pool의 타입을 DataSource 로 지정했기 때문임

여기까지 DAO가 Connection Pool을 사용하기 위한 설정을 하고 DAO가 Connection Pool을 사용하도록 코드를 바꿨음
close를 하는 메서드는 바꿀 필요가 없음
getConnection 메서드의 try 의 마지막에서 conn = ds.getConnection() 을 하면서 Connection Pool에서 Conncetion을 꺼냈으므로 close 메서드의 내부 동작이 Connection Pool 로 반납하도록 바뀌었음
여기까지 Connection Pool을 사용해야하는 이유와 Connection Pool을 사용하도록 바꿨음
DB 관련되서 더 깊게 들어가 실무에 가까운 코드를 생각하고 있다면 DB 과목의 트랜젝션(Transaction) 을 공부해보자
'Servlet + JSP > Serlvet-Chapter04' 카테고리의 다른 글
| Chapter04. CRUD 프로젝트 / 회원 정보 수정 (2) | 2023.03.09 |
|---|---|
| Chapter04. CRUD 프로젝트 / 로그인 (0) | 2023.03.08 |
| Chapter04. CRUD 프로젝트 / 회원가입 (0) | 2023.03.07 |
| Chapter04. 자바의 DB와 관련된 자원은 close 를 해줘야한다. (2) | 2023.03.06 |
| Chapter04. 자바에서 쿼리 보내고 결과 받기 / SELECT (0) | 2023.03.06 |