Database/Spring DB 접근 1

2. Connection Pool 과 DataSource 의 이해

hongod 2022. 6. 22. 01:26
private void close(Connection con, Statement stmt, ResultSet rs) {
	JdbcUtils.closeResultSet(rs);
	JdbcUtils.closeStatement(stmt);
	JdbcUtils.closeConnection(con);
}

1. DB 커넥션을 얻는 대가

 DB 커넥션을 얻는 코드는 짧지만 생각보다 코스트가 비싼 행위이다. 애플리케이션 밑바닥의 TCP/IP 3-way handshake 과정부터 시작해서 ID, PW 를 보내 인증과정까지 거쳐야 비로소 커넥션 객체가 DB 드라이버로 부터 돌아오기 때문에 DB 연결이 필요한 시점 마다 DriverManger를 통해 커넥션 요청부터 시작하면 쿼리 수행 시간에 연결 요청 시간이 추가되기 때문에 성능측면에서 좋지 않다. JDBC의 DriverManager나 Spring에서 제공하는 DataSourceDriverMananger 의 getConnection이 이런식으로 커넥션을 얻는다.

 

 

출처 : 스프링 DB 1편 - 데이터 접근 핵심 원리(인프런) / 김영한

 

2. 커넥션 미리 받아놓기(Connectino Pool)

 DB 커넥션을 매번 받아오는게 비용이 크다면 어떻게 이 비효율을 해결할까? 일반적으로 서버가 기동될 때 DB 드라이버가 DB로 부터미리 n 개의 커넥션을 받아서 어딘가에 저장해놓은 뒤 애플리케이션이 커넥션을 필요로 할 때 불러서 사용하고 사용후 커넥션을 그대로 유지한 채로 다시 반납하는 방식으로 해결한다. 이렇게 하면 DB 부하도 일정 수준(n개로 고정)으로 제한할 수 있고 , TCP/IP는 한 번 커넥션이 생성되면 따로 close하기 전까지 유지되기 때문에 매번 커넥션을 생성할 때 생기는 오버헤드 역시 줄일 수 있다. 이런 방식을 Connection Pool 이라고 한다.

 

 

출처 : 스프링 DB 1편 - 데이터 접근 핵심 원리(인프런) / 김영한

 

 이런 커넥션 풀은 사용자가 직접 구현할 수도 있지만 보통 HickariCP, tomcat-jdbc-pool 과 같은 오픈소스를 사용하고 요즘에는 HickariCP가 거의 평정한 상태이다. 실무에서는 레거시 프로젝트를 제외하면 HickariCP를 사용한다고 보면 된다.

 

3. DataSource

 커넥션을 DriverManager를 통해 얻든, HickariCP 풀을 통해 얻든, DBCP2 풀을 통해 얻는 클라이언트 입장에서는 '얻는' 행위 자체에만 집중하고싶다. 그렇지 않고 '어떻게/어디게서 얻는가'에 휘둘리면 커넥션을 만들어주는 객체 구현체가 바뀔 때마다 코드도 영향을 많이 받게 된다.

 

출처 : 스프링 DB 1편 - 데이터 접근 핵심 원리(인프런) / 김영한

 

이럴 때 '어떻게/어디에서'를 추상화한게 DataSource 이다. 어떤 방식(구현체)이던 간에 클라인터는 DataSource의 일관된 getConnection 메서드만 가지고 커넥션을 얻겠다는 것이다. DataSource에는 다른 메서드도 있긴 있지만 커넥션을 얻는 방법을 추상화한다는 관점에서는 getConnection만 기억해도 충분할 것 같다.

 

출처 : 스프링 DB 1편 - 데이터 접근 핵심 원리(인프런) / 김영한

 

 앞서 말한 다양한 커넥션 풀들은 대부분 DataSource를 구현해서 만들어져있기 때문에 필요한 경우 구현체를 바꿔 끼워서 사용하면 클라이언트 코드에서는 변경할 부분이 거의 없다. 다만 JDBC DriverManager는 DataSource를 구현하지 않는데, 이로 인해 발생하는 문제를 해결하기 위해 스프링에서 DataSource를 구현하는 DriverManagerDataSource라는 것을 만들어 두었다. 또한 JDBC DriverManager는 커넥션을 얻을 때 아래와 같이 설정 부(url, username 등의 메타정보 입력)와 실제로 케넥션을 얻는 부분이 나눠져있지 않은데, 

Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);

 DataSource interface를 구현하는 클래스들을 이용하면 설정에 필요한 값은 한 부분에 몰아두고 사용할 때에는 getConnection만을 사용해서 간단하게 설정/사용을 분리할 수 있다.

HickariDataSource dataSource = new HickariDataSource()
dataSource.setJdbcUrl(URL);
dataSource.setUserName(USERNAME);
dataSource.setPassWord(PASSWORD);

Connection con = dataSource.getConnection(); // connection 얻을 때에는 설정 값 불필요

 

4. JdbcUtils

 DataSource를 통해 커넥션을 얻어 DB와 통신한 후에는 사용된 자원들의 연결을 끊어주는 과정이 항상 뒤따르는데 이 코드들을 매번 사용하는 것이 상당히 번거롭다. 

private void close(Connection con, Statement stmt, ResultSet rs) {
	if (rs != null) {
		try {
			rs.close();
		} catch (SQLException e) {
			log.info("error", e);
		}
	}
	if (stmt != null) {
		try {
			stmt.close();
		} catch (SQLException e) {
			log.info("error", e);
		}
	}
	if (con != null) {
		try {
			con.close();
		} catch (SQLException e) {
			log.info("error", e);
		}
	}
}

 스프링은 이 과정을 편리하게 다룰 수 있는 JdbcUtils라는 클래스를 제공하는데 이 클래스는 스태틱 메서드로  closeResultSet(), closeStatement(), closeConnection()과 같은 커넥션 관리 함수들을 가지고 있다. 이 메서드들을 이용해서 커넥션 관리에 필요하지만 코드를 지저분(?)하게 만드는 try, catch 구조를 숨길 수 있다.

private void close(Connection con, Statement stmt, ResultSet rs) {
	JdbcUtils.closeResultSet(rs);
	JdbcUtils.closeStatement(stmt);
	JdbcUtils.closeConnection(con);
}

'Database > Spring DB 접근 1' 카테고리의 다른 글

4. 스프링과 문제해결 - 스프링 트랜잭션 처리  (0) 2022.07.25
3. 트랜잭션 이해  (0) 2022.07.03
1. JDBC 이해  (0) 2022.06.12