-
🌊 Connection Pool🌱 spring 2023. 1. 13. 16:26
인프런 김영한 님의 스프링 DB 1 강의를 듣고 정리한 글
(결제하고 못 들은 강의 듣고 다시 내용 정리하기)커넥션풀 ❓
데이터베이스 커넥션을 획득 과정
- DB 드라이버를 통해 커넥션 조회
- TCP/IP 커넥션 연결 (DB 드라이버 - DB)
- 커넥션이 연결되면 Id, PW와 같은 부가정보 DB에 전달
- 내부 인증을 완료 후 내부에 DB 세션 전달
- DB는 커넥션 생성 완료 응답을 보낸다.
- DB 드라이버는 커넥션 객체를 생성해 클라이언트에 반환
매번 커넥션을 새로 만드는 것은 매우 복잡하고 시간도 많이 걸린다.
⇒ 커넥션을 미리 생성해두고 사용하는 ‘커넥션 풀’ 이라는 방법을 사용.
커넥션 풀 초기화
애플리케이션을 시작하는 시점에 커넥션 풀은 필요한 만큼 커넥션을 미리 풀에 보관
- default - 10개
❗커넥션 풀에 들어있는 커넥션은 DB와 TCP/IP로 커넥션이 연결되어 있는 상태이다.
- 즉시 SQL을 DB에 전달할 수 있다.
커넥션 풀 사용
이제는 DB 드라이버를 통해 새로운 커넥션을 획득하지 않는다 ❌
커넥션 풀을 통해 이미 생성되어 있는 커넥션을 객체 참조로 그냥 가져다 사용한다.
- 커넥션 풀에 커넥션 요청 → 커넥션 풀은 가지고 있는 커넥션 중 하나 반환
- 받은 커넥션을 사용해 SQL을 데이터베이스에 전달, 그 결과를 받아 처리
- 커넥션을 모두 사용하면 다시 커넥션 풀에 반환
❓❗ DataSource
커넥션을 얻는 여러 가지 방법
- DriverManager를 통해 획득
- 커넥션 풀을 사용해 획득
DriverManager → 커넥션 풀로 변경 시 문제 발생
- 애플리케이션 코드 변경 필요
- 의존관계가 DriverManager 에서 HikariCP 같은 커넥션 풀로 변경
커넥션을 획득하는 방법을 추상화
자바에서는 이런 문제를 해결하기 위해 javax.sql.DataSource 라는 인터페이스를 제공한다.
- DataSource는 커넥션을 획득하는 방법을 추상화하는 인터페이스
- 핵심 기능 : 커넥션 조회
DataSource 인터페이스
public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException; ... }
대부분의 커넥션 풀은 DataSource 인터페이스를 이미 구현해 두었다.
- 개발자는 HikariCP 커넥션 풀, DBCP2 커넥션 풀 등의 코드를 직접 의존하는 것이 아니다 ❌
- DataSource 인터페이스만 의존 ❗
⚠️ DriverManager는 DataSource 인터페이스를 사용하지 않는다.
따라서 DriverManager → DataSource 기반 커넥션 풀로 변경하려면 코드를 고쳐야 한다.
- 이를 해결하기 위해 스프링은 DriverManagerDataSource 라는 DataSource를 구현 클래스를 제공
⌨️ DataSource code
기존 DriverManager를 통해 커넥션 획득
@Slf4j public class ConnectionTest { @Test void driverManager() throws SQLException { Connection connection = DriverManager.getConnection(ConnectionConst.URL, ConnectionConst.USERNAME, ConnectionConst.PASSWORD); Connection connection2 = DriverManager.getConnection(ConnectionConst.URL, ConnectionConst.USERNAME, ConnectionConst.PASSWORD); log.info("connection = {}, class = {}", connection, connection.getClass()); log.info("connection = {}, class = {}", connection2, connection2.getClass()); } }
결과
DriverManagerDataSource 사용
@Slf4j public class ConnectionTest { @Test void dataSourceDriverManager() throws SQLException { //DriverManagerDataSource - 항상 새로운 커넥션 획득 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()); } }
결과
차이점
기존 DriverManager를 통해 커넥션을 획득하는 방법과 DataSource를 통해 커넥션을 획득하는 방법의 차이점
- DriverManager는 커넥션을 획득(getConnection())할 때마다 파라미터를 계속 전달한다.
- 하지만 DataSource를 사용하는 방식은 처음 객체를 생성할 때만 필요한 파라미터를 넘겨준다.
📖 설정과 사용의 분리
설정
- DataSource를 만들고 필요한 속성들을 사용해 입력
- 설정과 관련된 속성들은 한 곳에 있는 것이 변경에 유연하게 대처할 수 있다.
사용
- 설정은 신경 쓰지 않고 DataSource의 getConnection()만 호출해 사용한다.
따라서, DataSource를 사용하는 곳에서는 속성들에 의존하지 않고 그냥 DataSource만 주입받아 getConnection()만 호출하면 된다.- 리포지토리는 DataSource만 의존하고 속성들은 몰라도 된다.
커넥션 풀
@Test 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); // 커넥션 풀에서 커넥션 생성 시간 대기 }
- 커넥션 풀에서 커넥션을 생성하는 작업은 애플리케이션 실행 속도에 영향을 주지 않기 위해 별도의 스레드에서 작동한다.
- 별도 쓰레드에서 동작해 테스트가 먼저 종료되어 버린다. → 따라서 대기 시간을 줌
결과
- 활성 2개, 대기 8개 → 전체 10개 커넥션 생성
- 활성 2개인 이유 - close를 안 해줘서 활성화되어있다.
📖 MyPool connection adder
- 별도의 스레드를 통해 커넥션 풀에 커넥션을 채운다. (최대 풀 수 까지)
왜 별도의 쓰레드를 사용❓
❗커넥션 풀에 커넥션을 채우는 작업은 상대적으로 오래 걸림.
- 따라서 별도의 쓰레드를 통해 애플리케이션 실행 시간에 영향을 주지 않게 한다.
DataSource 적용
커넥션을 DataSource에서 가져오도록 한다.
private final DataSource dataSource; private Connection getConnection() throws SQLException { Connection connection = dataSource.getConnection(); log.info("get connection={}, class={}", connection, connection.getClass()); return connection; }
connection, statement, resultset 을 close 해주는 코드를 JdbcUtils를 통해 간단히 리팩토링
private void close(Connection con, Statement stmt, ResultSet rs) { JdbcUtils.closeResultSet(rs); JdbcUtils.closeStatement(stmt); JdbcUtils.closeConnection(con); }
📖 JdbcUtils
- 스프링은 JDBC를 편리하게 다룰 수 있는 JdbcUtils 라는 편의 메서드를 제공
- 커넥션을 좀 더 편리하게 닫을 수 있다.
DriverManagerDataSource VS HikariDataSource
DriverManagerDataSource 를 사용하면 항상 새로운 커넥션이 생성되어 사용된다
반면, Hikari 같은 커넥션 풀을 사용하면 커넥션을 재사용한다.
- 커넥션을 사용하고 다시 돌려준다.
참고자료
https://www.inflearn.com/course/스프링-db-1
'🌱 spring' 카테고리의 다른 글
🐱 Tomcat (0) 2023.03.12 ThreadLocal (0) 2023.01.18 JDBC ❓ (0) 2023.01.13 Spring Interceptor에서 Request 데이터 처리 (1) 2022.12.06 애플리케이션 컨텍스트와 빈팩토리 (0) 2022.11.16