Database 에 SQL 을 전송하기 위해서는 먼저 Database 에 접근하여 Connection 을 얻어와야한다. Connection 연결에 성공한 데이터베이스에서는 해당 커넥션에 맞는 Session 을 생성하여 보관하게 된다.

DriverManager

DriverManager 는 자바의 java.sql 에서 제공하며, 라이브러리에 등록된 Driver 들을 관리하고 SQL 을 전송하기 위해 필요한 데이터베이스 Connection 을 얻어오는 역할을 한다. DriverManager 로 부터 Connection 을 가져오려면 DB 접속에 필요한 url, username, password 정보가 필요하다.

DB 와의 Connection 을 가져올때마다 Checked 예외인 SQLException 를 던지게 된다. 커넥션을 연결할 때마다 try catch 로 잡아줄 순 없으므로, Connection 을 얻어오는 역할만 수행하는 Util 성 클래스로 분리해주는게 좋다.

public class DBConnectionUtil {  
  
    private static final Logger LOGGER = LoggerFactory.getLogger(DBConnectionUtil.class);  
    private static final String URL = "jdbc:h2:tcp://localhost/~/jdbc";  
    private static final String USER = "sa";  
    private static final String PASSWORD = "";  
  
    public static Connection getConnection() {  
        try {  
            Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);  
            LOGGER.info("Get Connection  = {}, Get Class = {}", connection, connection.getClass());  
            return connection;  
        }  
        catch (SQLException sqlException) {  
            throw new IllegalStateException("Connection 을 얻어오는데 실패했습니다.");  
        }  
    }  
}

아래와 같이 Connection 이 잘 얻어와지는지 테스트 코드를 짜볼 수 있는데, Connection 의 타입이 org.h2.jdbc.JdbcConnection 인 것을 볼 수 있다.

class DBConnectionUtilTest {  
  
    @Test  
    @DisplayName("DB Connection 연결 정보를 확인한다.")  
    public void connectionTest() {  
        Connection connection = DBConnectionUtil.getConnection();  
        assertThat(connection).isNotNull();  
    }  
}
14:43:17.163 [main] INFO  a.s.connection.DBConnectionUtil -- Get Connection  = conn0: url=jdbc:h2:tcp://localhost/~/jdbc user=SA, Get Class = class org.h2.jdbc.JdbcConnection

이 이유는 처음 프로젝트를 생성할때 H2 를 사용하려고 Gradle 에 H2 Driver 의존성을 추가해줘서 그렇다.

dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'  
    runtimeOnly 'com.h2database:h2'  
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
}

Driver 의 URL 판단 기준

DrvierManager 는 내부적으로 등록된 Driver 들을 순회하며 JDBC URL 주소 와 연결할 수 있는 Driver 를 찾는다. 여기서는 H2 와 연결할 수 있는 Driver 를 통해 커넥션을 맺고, 그 결과로 Connection을 반환하게 된다.

DriverManger

Driver 들이 JDBC URL 주소와 연결할 수 있는지는 각각의 Driver 들이 판단한게 된다. H2 Driver 와 같은 경우에는 입력으로 들어온 JDBC URL 의 접두사가 jdbc:h2 로 시작하면 자신이 처리할 수 있다고 판단하게 된다.

org.h2.Driver (java.sql.Driver 을 구현한 구현체)

Connection

Connection 은 DriverManager 가 DB 와 커넥션을 맺고 반환한 Interface 이다. 이 Connection 은 Transaction Commit, Rollback, SQL 을 날릴 수 있게 해주는 Statement 를 생성할 수 있게 해준다.

중요한 것은 내부적으로 사용하는 Driver 에 따라 반환되는 구현체가 달라진다는 것이다. ConnectionImpl 은 MySQL Driver 를 통해 생성된 인스턴스이며, JdbcConnection 은 H2 Driver 를 통해 생성된 인스턴스이다. 두 구현체 모두 Connection 인터페이스를 구현한 것을 알 수 있다.

여러개의 DBMS 연결

DriverManager 로부터 반환되는 Connection 은 내부적으로 사용하는 Driver 에 따라 반환되는 구현체가 달라진다. 이를 이용하면 하나의 애플리케이션에서 여러가지 DBMS 를 사용할 수 있다.

우선 MySQL 을 사용할 수 있게하는 Driver 의존성을 추가해준다.

dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'  
    runtimeOnly 'com.h2database:h2'  
    runtimeOnly 'com.mysql:mysql-connector-j'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
}

그리고 DB 로부터 커넥션을 얻어오는 Util 클래스를 작성해준다.

public class DBConnectionUtil {  
  
    private static final Logger LOGGER = LoggerFactory.getLogger(DBConnectionUtil.class);  
  
    private static final String H2_URL = "jdbc:h2:tcp://localhost/~/jdbc";  
    private static final String H2_USER = "sa";  
    private static final String H2_PASSWORD = "";  
  
    private static final String MYSQL_URL = "jdbc:mysql://localhost/onsquad";  
    private static final String MYSQL_USER = "root";  
    private static final String MYSQL_PASSWORD = "1234";  
  
    public static Connection getH2Connection() {  
        return getConnection(H2_URL, H2_USER, H2_PASSWORD);  
    }  
  
    public static Connection getMySqlConnection() {  
        return getConnection(MYSQL_URL, MYSQL_USER, MYSQL_PASSWORD);  
    }  
  
    private static Connection getConnection(String url, String username, String password) {  
        try {  
            Connection connection = DriverManager.getConnection(url, username, password);  
            LOGGER.info("Get Connection  = {}, Get Class = {}", connection, connection.getClass());  
            return connection;  
        } catch (SQLException sqlException) {  
            throw new IllegalStateException("Connection 을 얻어오는데 실패했습니다.");  
        }  
    }  
}

이제 Connection 을 확인할 수 있는 테스트 코드를 짜고 실행해보면, H2 는 org.h2.jdbc.JdbcConnection 그리고 MySQL 은 com.mysql.cj.jdbc.ConnectionImpl 인 것을 확인할 수 있다.

class DBConnectionUtilTest {  
  
    @Test  
    @DisplayName("H2 그리고 MYSQL Connection 연결 정보를 확인한다.")  
    public void connectionTest() {  
        Connection h2Connection = DBConnectionUtil.getH2Connection();  
        Connection mySqlConnection = DBConnectionUtil.getMySqlConnection();  
        assertThat(h2Connection).isNotNull();  
        assertThat(mySqlConnection).isNotNull();  
    }  
}
16:02:31.579 [main] INFO  a.s.connection.DBConnectionUtil -- Get Connection  = conn0: url=jdbc:h2:tcp://localhost/~/jdbc user=SA, Get Class = class org.h2.jdbc.JdbcConnection
16:02:31.719 [main] INFO  a.s.connection.DBConnectionUtil -- Get Connection  = com.mysql.cj.jdbc.ConnectionImpl@78fb9a67, Get Class = class com.mysql.cj.jdbc.ConnectionImpl

발생할 수 있는 문제

쿼리를 수행할때마다 DriverManager 를 통해 Connection 을 얻어오는 것은 성능에 문제가 갈 수 있는 단점이 있다. 왜냐하면 Connection 을 얻어오기 위해서는 TCP 연결 하고, DB 에 한번은 갔다와야하기 때문이다. 때문에 일반적인 경우에는 미리 여러개의 Connection 들을 얻어와 이들을 모아둔 Connection Pool 이라는 개념을 사용하여 쿼리마다 Connection 을 얻어오는 행위를 방지하곤 한다.

정리

  1. DriverManager 는 DB 와 커넥션을 맺고 Connection 인스턴스를 반환한다.
    • DriverManager 는 내부적으로 Driver 구현체(H2 Driver, MySQL Driver) 들을 사용하여 Driver 에 맞는 Connection 구현체를 반환한다.
    • H2 Driver 가 구현한 Connection 은 JdbcConnection 이며, MySQL Driver 가 구현한 Connection 은 ConnectionImpl 이다.
  2. DriverManager 를 통해 매번 Connection 을 얻어오는 방법보다는 미리 Connection 들을 연결하고, 이들을 모아둔 Connection Pool 이라는 개념을 사용하여 성능 저하 문제를 예방한다.