JDBC - 커넥션 풀(Connection Pool)과 데이터 소스(DataSource)
커넥션 풀이란?
데이터베이스에 접근을 어떻게 하든, 어떤 데이터베이스에 접근을 하든간에 해당 데이터베이스에 대해서 커넥션을 획득해야 한다는 것을 알고 있을 것이다. 커넥션이란 말 그대로 특정 데이터베이스에 접근하기 위한 권한을 획득하는 것이다. 우리는 데이터베이스 커넥션을 획득하려면 특정 데이터베이스에 대한 아이디, 패스워드 등의 값을 입력해서 넘겨야 하고, 또한 네트워크를 사용하는 등 많은 자원과 비용을 소모하게 된다.
만약에 몇백만의 사용자가 이용하는 웹 사이트가 존재한다고 가정해보자. 웹 사이트에 대한 각각의 리소스에 접근하려면 데이터베이스에 접근을 해서 특정 데이터를 불러와야 할 것이다. 하지만 이런 상황이 발생할 때마다 데이터베이스에 대한 커넥션을 획득하는 로직을 계속해서 실행하게 된다면, 수 많은 자원과 비용을 소모하게 되고 서버에 과부하가 발생하게 될 것이다.
이를 예방하고 효과적으로 커넥션을 관리하기 위해서 커넥션 풀이라는 것을 사용한다. 즉, 커넥션 풀이란 미리 커넥션 풀이라는 저장공간에 커넥션들을 몇개 생성해두고 커넥션이 필요할 때마다 전달해주는 것이다. JDBC를 이용하면 JDBC드라이버를 통해서 커넥션 풀에 접근하는데 이를 그림으로 표현하면 다음과 같다.
커넥션풀을 사용한다고 하면 각각의 DB드라이버는 미리 커넥션을 생성하는데, 생성하는 로직은 다음과 같다. 여기서는 JDBC드라이버를 사용한다고 가정한다.
- JDBC 드라이버는 커넥션풀에 커넥션을 조회한다.
- 커넥션이 설정한 양 만큼 존재하지 않으면 DB와 TCP/iP 커넥션 연결을 시도한다.(커넥션 풀에 존재하는 커넥션들의 양을 커스텀하게 설정할 수 있다.)
- DB와 관련된 ID, PW등을 전달한다.
- DB는 내부 인증로직을 마치고 DB 세션을 생성하고, DB 드라이버에 커넥션을 전달한다.
- 드라이버는 전달받은 커넥션을 커넥션풀에 보관한다.
이런 과정을 거쳐서 커넥션 풀에 존재하는 커넥션들의 연결상태를 보면 위의 두번째 그림과 같게 된다. 모두 DB와 연결되어 있다는 의미이다.
커넥션 풀에 커넥션들을 모두 넣어놓았으면 이제 우리는 애플리케이션 로직을 수행할 때, 커넥션 로직을 수행할 필요없이 커넥션 풀에서 커넥션을 조회해서 가져오기만 하면 된다. 위에서 그림으로 설명해 놓았다.
커넥션 풀을 사용할 때는 주의할 점이 있는데, 아까 말했듯이 커넥션 풀에는 보관할 수 있는 커넥션의 양을 설정할 수 있다. 그럼 커넥션의 양을 무한대로 설정하면 좋지 않겠느냐고 생각할 수 있는데, 절대 아니다. 커넥션 풀도 '자원'이고 '비용'이기 때문에 애플리케이션의 상황에 따라서 커넥션 풀의 크기를 적절히 설정해야 한다. 너무 많게 설정하면 자원의 낭비가 발생할 수 있기 때문에 상황에따라 적절히 설정하는 것이 중요하다.
대표적인 커넥션 풀 오픈소스는 commons-dbcp2, tomcat-jdbc pool, HikariCP 등이 있다. 성능과 사용의 편리함 측면에서는 최근에 HikariCP를 사용한다. 스프링 부트 2.0부터는 기본 커넥션 풀로 HikariCP를 사용하고 있다.
데이터소스(DataSource)
우리는 위에서 커넥션을 얻는 방법을 공부했다.
커넥션을 얻으려면 직접 DB드라이버를 통해서 커넥션을 생성하거나 여러가지 커넥션 풀 오픈소스를 사용하면 될 것이다. 하지만 여기서 문제점이 존재한다. 만약 DB드라이버를 이용해서 커넥션을 생성하다가 커넥션 풀 오픈소스를 사용하려고 로직을 바꾸려면 어떻게 해야할까? 그렇다. 애플리케이션 로직을 모두 변경해야하는 번거로움이 발생하고, 코드가 길다면 로직이 복잡해서 변경하는 와중에 예외가 발생할 수 있다.
따라서 자바에서는 이런 변경에 닫혀있는 방법을 제공하기 위해서 커넥션을 획득하는 방법을 추상화하는 방식을 제공한다. DataSource라는 인터페이스가 바로 이것이다. 자바에서 이 인터페이스의 구현체로 거의 대부분을 구현해 두었다. 우리는 이 인터페이스를 사용해서 애플리케이션 실행 시점에 유연하게 DI를 사용해서 실제 구현체만 주입 시켜주면 된다.
DataSource 인터페이스의 일부분을 보면 다음과 같다.
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
//...//
}
이 인터페이스의 구현체들의 일부분을 보면 다음과 같다.
구현체들을 몇개 보면 HikariCP, Driver 등 우리가 알고있는 구현체를 볼 수 있다. 자바에서는 이처럼 우리가 필요한 모든 것을 제공해주고 있어서 우리는 그저 사용하기만 하면 된다.