프로그래밍/Java

[JDBC] MySQL 드라이버 Class.forName()의 비밀 - 로드만 했을 뿐인데 getConnection()이 가능하다고?

Jay22 2019. 6. 4. 14:31
반응형

도대체 JDBC드라이버는 Class.forName()으로 로딩만 했을 뿐인데 어떻게 DriverManager 에서 사용이 되는 것일까?

문제의 코드

Class.forName의 호출

Class 클래스는 JVM에서 동작할 클래스들의 정보를 묘사하기 위한 일종의 메타 클래스이다. 객체의 참조 카운트가 0가 되면 해당 인스턴스는 더 이상 가리키는 참조 변수가 없는 것이므로 GC의 대상이 된다. 그런데 JDBC는 생성시키고 끝난다. 그러면 GC가 되지 않도록 하는 무언가가 있다고 한다.

JDBC 드라이버와 같이 인스턴스를 별도로 관리하지 않는 대부분의 클래스의 경우, 그 클래스가 스스로의 인스턴스를 정적 블록을 통해 생성하고 관리한다. 정적 블록을 갖는 클래스들은 Class.forName() 만 호출해도 초기화가 수행된다.

 

MySQL 드라이버 클래스를 찾아보자

그래서 실제 Driver의 구현모습을 보기위해 찾아보았다.

 

Driver 클래스 

 

저 위에 설명처럼 실제로 코멘트가 달려있었다.

2번 설명에서 인스턴스를 스스로 생성하는 부분은 밑의 static 블럭에서 확인할 수 있다. 실제 스스로 생성할 뿐 아니라 DriverManager에 등록까지 수행한다!

1번 설명 중에 DriverManager는 각 driver들을 차례로 찾는다고 했다. 그 부분도 찾아보았다. 그 부분은은 DriverManager.getConnection() 부분에서 확인할 수 있었다.

 

DriverManager 클래스 안의 getConnection() 함수

또 한 가지 의문점이 드는 부분

-> Driver를 수차례 로딩을 하는 경우? 과연 JVM에 계속 쌓일까? 사실 그럼 안될 것 같아 보였다.

코드를 찾아서 확인해 보니 실제로 그러지 않았다.

 

registerDriver 함수

 

맨 처음 부분인 Driver 클래스에서 registerDriver를 호출하는 부분을 타고 들어갔다. 드라이버를 등록하는 부분은 synchronized로 감싸져 있었다. 즉 한 자원 (드라이버 매니저) 에 대해 여러 쓰레드가 공유를 하기 때문에 저 키워드를 붙여 놓은 것으로 보인다. 어차피 JVM에서는 각 DBMS마다 정해진 드라이버 하나씩만 있으면 되기 때문에 같은 드라이버를 2개 이상 등록할 필요가 없다.

그리고 저 addIfAbsent함수를 타고 들어가보자. 이름으로 부터 알 수 있듯이 "없다면 등록해라"라는 기능을 수행할 것으로 예상된다.

 

CopyOnWriteArrayList의 addIfAbsent 함수

 

사실 저 addIfAbsent라는 함수는 CopyOnWriteArrayList의 메서드이다. CopyOnWriteArrayList는 List 인터페이스 구현체 중 하나인데 그 중 자주쓰이는 ArrayList는 Thread-safe하지 않다. 그렇기 때문에 Thread-safe하게 구현하려면 ArrayList에 synchronized를 써야하는데 그렇게되면 불필요한 성능 저하가 일어난다. 그 대용으로 쓰이는 컬렉션이 바로CopyOnWriteArrayList(Thread-safe 한) 이다. 그래서 여러 쓰레드에서 공유를 할 때 List의 내용이 바뀌는 것을 걱정하지 않아도 된다.

실제로도 DriverManager는 저 드라이버 리스트인 registeredDriver를 CopyOnWriteArrayList로 관리를 하고 있다. CopyOnWriteArrayList는 각 요소들을 snapshot으로 관리를 한다. 바로 위의 addIfAbsent를 보면 getArray()를 호출하여 snapshot을 반환 받아 사용하는데 getArray()는 CopyOnWriteArrayList의 요소들을 의미한다.

 

DriverManager의 registeredDrivers 의 자료형

 

CopyOnWriteArrayList의 문서내용을 찾아보았다.

 

CopyOnWriteArrayList 클래스에 관한 docs

 

위의 설명처럼 Driver 목록들은 Thread-safe하게 관리해야 하기 때문에 CopyOnWriteArrayList를 썼던 것이다.

결론:

Class.forName()을 호출하면 Driver 가 자기자신을 초기화하여 DriverManager에 등록을 한다. 즉 개발자가 따로 관리하지 않는 static 객체들이 알아서 DriverManager에 등록이 되는 것이다. 그래서 Class.forName()을 호출하고 나서 어떠한 인자로도 전달하지 않고 바로 getConnection()을 호출해도 드라이버가 찾아지는 것이다.

// 드라이버 로딩
try {
    Class.forName("com.mysql.cj.jdbc.Driver"); // <-- 어떠한 인자로도 전달하지 않고 그냥 호출만 함
} catch (ClassNotFoundException e) {
    System.err.println(" !! JDBC Driver load 오류: " + e.getMessage());
    e.printStackTrace();
}

// 드라이버 연결
try {
    con = DriverManager.getConnection("jdbc:mysql://" + server + "/" + database + "?useSSL=false", userName, password); // <-- 갑자기 뜬금포로 getConnection했을 때 위에서 로드한 드라이버가 호출됨
    System.out.println("정상적으로 연결되었습니다.");
} catch (SQLException e) {
    System.err.println("연결 오류:" + e.getMessage());
    e.printStackTrace();
}
반응형