- [Spring] 스프링 DB 1편 2) 커넥션 풀과 데이터소스 이해2023년 11월 12일 09시 21분 59초에 업로드 된 글입니다.작성자: 민발자728x90
Session 2 커넥션 풀과 데이터소스 이해
1. 커넥션 풀 이해
1) 데이터베이스 커넥션 획득 과정
1. 애플리케이션 로직은 DB 드라이버를 통해 커넥션을 조회
2. DB 드라이버는 DB와 TCP/IP 커넥션을 연결, 이 과정에서 3-way-handshake 같은 TCP/IP 연결을 위한 네트워크 동작 발생
3. DB 드라이버는 커넥션이 연결되면 ID, PW와 기타 부가 정보를 DB에 전달
4. DB는 ID, PW로 내부 인증을 완료하고 내부에 DB 세션 생성
5. DB는 커넥션 생성이 완료되었다고 응답을 보냄
6. DB 드라이버는 커넥션 객체를 생성해 클라이언트에 반환
▶ 커넥션을 획득하는 과정이 복잡하고 시간이 많이 소모, DB는 물론이고 애플리케이션 서버에서도 TCP/IP 커넥션을 새로 생성하기 위한 리소스를 매번 사용해야 함
▶ 커넥션을 미리 생성해두고 사용하는 커넥션 풀 방법을 사용
2) 커넥션 풀 초기화
애플리케이션을 시작하는 시점에 커넥션 풀은 필요한 만큼 커넥션을 미리 확보해 풀에 보관, 기본값은 보통 10개
3) 커넥션 풀 연결 상태
커넥션 풀에 들어 있는 커넥션은 TCP/IP로 DB와 커넥션이 연결되어 있는 상태
언제든 즉시 SQL을 DB에 전달할 수 있다.
4) 커넥션 풀 사용
요청이 들어오면 커넥션 풀을 조회해 이미 생성되어 있는 커넥션 객체 참조를 가져다 쓰기만 하면 된다.
애플리케이션 로직은 커넥션 풀에서 받은 커넥션을 사용해 SQL을 데이터베이스에 전달하고 그 결과를 받아서 처리
커넥션을 모두 사용하고 나면 종료하는 것이 아니라 그대로 커넥션 풀에 반환
5) 커넥션 풀 정리
성능 테스트를 통해 커넥션 풀의 숫자를 정함
커넥션 풀은 서버당 최대 커넥션 수를 제한할 수 있어 DB에 무한정 연결이 생성되는 것을 막아줘 DB 보호 효과도 있음
커넥션 오프소스를 통해 성능도 뛰어나고 편리하게 사용 가능 commons-dbcp2, tomcat-jdbc pool, HikariCP 등
최근에는 성능, 사용의 편리함, 안전성 측면에서 검증된 HikariCP 주로 사용 스프링 부트 2.0 이상부터 기본으로 제공
2. 데이터소스 이해
1) 커넥션 획득 방법 변경 시 문제점
DriverManager를 통해 커넥션을 획득하다가 커넥션 풀로 변경하면 애플리케이션 코드도 함께 변경해야 한다.
의존관계가 DriverManager에서 HikariCP로 변경되기 때문
2) 커넥션 획득 방법의 추상화
커넥션 획득 방법을 추상화한 javax.sql.DataSource 인터페이스를 통해 위의 문제점을 해결할 수 있다.
대부분의 커넥션 풀은 DataSource 인터페이스를 이미 구현
개발자는 DBCP2, HikariCP 커넥션 풀의 코드를 직접 의존하는 것이 아니라 DataSource 인터페이스에만 의존하도록 애플리케이션 로직을 작성, 커넥션 풀 구현 기술을 변경하고 싶으면 해당 구현체로 변경만 하면 된다.
DriverManager은 DataSource인터페이스를 사용하지 않기 때문에 커넥션 풀을 변경하려면 관련 코드 모두 수정 필요, 스프링은 DriverManager도 DataSource를 통해 사용할 수 있도록 DriverManagerDataSource라는 DataSource 구현 클래스 제공
3. 데이터소스 예제 1 - DriverManager
1) DriverManager를 통해 커넥션 획득
Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
2) DriverManagerDataSource를 통해 커넥션 획득
void dataSourceDriverManager() throws SQLException { DriverManagerDataSource dataSource = new DriverManagerDataSource(URL,USERNAME, PASSWORD); useDataSource(dataSource); } private void useDataSource(DataSource dataSource) throws SQLException { Connection con1 = dataSource.getConnection(); Connection con2 = dataSource.getConnection(); log.info("connection={}, class={}", con1, con1.getClass()); log.info("connection={}, class={}", con2, con2.getClass()); }
3) DriverManager와 DriverManagerDataSource 차이
DriverManager는 커넥션을 획득할 때마다 파라미터를 계속 전달해야 한다.
DataSource를 사용하는 방식은 처음 객체를 생성할 때만 파라미터를 넘겨두고 커넥션을 획득할 때는 getConnection() 메서드만 호출
4) 설정과 사용 분리
- 설정
DataSource를 만들고 필요한 속성들을 사용해 URL, USERNAME, PASSWORD 같은 부분을 입력하는 것을 말함
설정과 관련된 속성들은 한 곳에 있는 것이 향후 변경에 더 유연하게 대체 가능
- 사용
설정을 신경 쓰지 않고 DataSource의 getConnection()만 호출해서 사용
- 설정과 사용 분리 설명
필요한 데이터를 DataSource가 만들어지는 시점에 미리 넣어두면 DataSource를 사용하는 곳에서는 메서드만 호출하게 되므로 URL, USERNAME, PASSWORD 같은 속성들에 의존하지 않아도 된다. 그냥 DataSource만 주입받아서 getConnection()만 호출하면 된다.
리포지토리는 DataSource만 의존하고 속성을 몰라도 된다.
4. 데이터소스 예제 2 - 커넥션 풀
1) 데이터소스 커넥션 풀 추가
void dataSourceConnectionPool() throws SQLException, InterruptedException { //커넥션 풀링:HikariProxyConnection(Proxy)->JdbcConnection(Target) HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(URL); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); dataSource.setMaximumPoolSize(10); dataSource.setPoolName("MyPool"); useDataSource(dataSource); Thread.sleep(1000);//커넥션 풀에서 커넥션 생성 시간 대기 }
HikariCP 커넥션 풀 사용
HikariDataSource는 DataSource 인터페이스 구현하고 있음
커넥션 풀에서 커넥션을 생성하는 작업은 애플리케이션 실행 속도에 영향을 주지 않기 위해 별도의 스레드에서 작동
별도의 쓰레드에서 동작하기 때문에 테스트가 먼저 종료되기 때문에 대기시간을 주어야 쓰레드 풀에 커넥션이 생성되는 로그를 확인할 수 있다.
2) 실행 결과 로그
#커넥션 풀 초기화 정보 출력
HikariConfig - MyPool - configuration:
HikariConfig - maximumPoolSize................................10
HikariConfig - poolName................................"MyPool"
#커넥션 풀 전용 쓰레드가 커넥션 풀에 커넥션을 10개 채움
[MyPool connection adder] MyPool - Added connection conn0: url=jdbc:h2:.. user=SA
[MyPool connection adder] MyPool - Added connection conn1: url=jdbc:h2:.. user=SA
[MyPool connection adder] MyPool - Added connection conn2: url=jdbc:h2:.. user=SA
...
[MyPool connection adder] MyPool - Added connection conn9: url=jdbc:h2:.. user=SA
#커넥션 풀에서 커넥션 획득1
ConnectionTest - connection=HikariProxyConnection@446445803 wrapping conn0: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
#커넥션 풀에서 커넥션 획득2
ConnectionTest - connection=HikariProxyConnection@832292933 wrapping conn1: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
MyPool - After adding stats (total=10, active=2, idle=8, waiting=0)- HikariConfig : HikariCP 관련 설정 확인, 풀 이름과, 최대 풀 수 확인
- MyPool connection adder
별도의 쓰레드 사용해서 커넥션 풀에 커넥션을 채우고 있음 최대 풀 수 10까지 채운다.
커넥션 풀에 커넥션을 채우는 것은 상대적으로 오래 걸리기 때문에 애플리케이션을 실행할 때 커넥션 풀을 채울 때까지 대기할 수 없다.
별도의 쓰레드를 사용해서 커넥션 풀을 채워야 애플리케이션 실행 시간에 영향을 주지 않는다.
- 커넥션 풀에서 커넥션 획득
현재 2개를 획득하고 반환하지 않았음
active=2 2개 사용 중이고 idle=8 풀에서 대기 상태인 커넥션이 8개임을 확인할 수 있다.
5. 데이터소스 적용
1) 데이터소스 적용
@Slf4j public class MemberRepositoryV1 { private final DataSource dataSource; public MemberRepositoryV1(DataSource dataSource) { this.dataSource = dataSource; } ... private void close(Connection con, Statement stmt, ResultSet rs) { JdbcUtils.closeResultSet(rs); JdbcUtils.closeStatement(stmt); JdbcUtils.closeConnection(con); } private Connection getConnection() throws SQLException { Connection con = dataSource.getConnection(); log.info("get connection={}, class={}", con, con.getClass()); return con; } }
- DataSource 의존관계 주입
직접 만든 DBConnectionUtil 대신 외부에서 DataSource를 주입받아 사용
DataSource는 표준 인터페이스, 구현체가 변경되어도 해당 코드를 변경하지 않아도 된다.
- JdbcUtils 편의 메서드
커넥션을 좀 더 편리하게 닫을 수 있음
2) 테스트
@Slf4j class MemberRepositoryV1Test { MemberRepositoryV1 repository; @BeforeEach void beforeEach() throws Exception { //기본 DriverManager - 항상 새로운 커넥션 획득 DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD); //커넥션 풀링:HikariProxyConnection->JdbcConnection HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(URL); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); repository = new MemberRepositoryV1(dataSource); } ... }
MemberRepositoryV1은 의존관계 주입이 필요 beforeEach()에서 주입
- DriverManagerDataSource 사용
get connection=conn0: url=jdbc:h2:.. user=SA class=class org.h2.jdbc.JdbcConnection
get connection=conn1: url=jdbc:h2:.. user=SA class=class org.h2.jdbc.JdbcConnection
get connection=conn2: url=jdbc:h2:.. user=SA class=class org.h2.jdbc.JdbcConnection
get connection=conn3: url=jdbc:h2:.. user=SA class=class org.h2.jdbc.JdbcConnection
get connection=conn4: url=jdbc:h2:.. user=SA class=class org.h2.jdbc.JdbcConnection
get connection=conn5: url=jdbc:h2:.. user=SA class=class org.h2.jdbc.JdbcConnectionconn0~5까지 항상 새로운 커넥션이 생성된다.
- 커넥션 풀 사용
get connection=HikariProxyConnection@xxxxxxxx1 wrapping conn0: url=jdbc:h2:... user=SA
get connection=HikariProxyConnection@xxxxxxxx2 wrapping conn0: url=jdbc:h2:... user=SA
get connection=HikariProxyConnection@xxxxxxxx3 wrapping conn0: url=jdbc:h2:... user=SA
get connection=HikariProxyConnection@xxxxxxxx4 wrapping conn0: url=jdbc:h2:... user=SA
get connection=HikariProxyConnection@xxxxxxxx5 wrapping conn0: url=jdbc:h2:... user=SA
get connection=HikariProxyConnection@xxxxxxxx6 wrapping conn0: url=jdbc:h2:... user=SAconn0이 계속 재사용된다.
테스트가 순서대로 진행되면서 사용 후 반환하는 것을 반복
웹 애플리케이션이 동시에 여러 요청이 들어오면 여러 쓰레드에서 커넥션 풀의 커넥션을 가져가 사용한다.
728x90'정리 > Spring' 카테고리의 다른 글
[Spring] 스프링 DB 1편 3) 트랜잭션 이해(2) (0) 2023.11.17 [Spring] 스프링 DB 1편 3) 트랜잭션 이해(1) (0) 2023.11.13 [Spring] 스프링 DB 1편 1) JDBC 이해 (0) 2023.11.11 [Spring] 스프링 MVC 1편 7) 스프링 MVC 웹 페이지 만들기(2) (2) 2023.11.10 [Spring] 스프링 MVC 1편 7) 스프링 MVC 웹 페이지 만들기(1) (0) 2023.11.09 다음글이 없습니다.이전글이 없습니다.댓글