Java

[Java] JDBC에서 Class.forName과 클래스 로딩에 대해 알아보기

림 림 2021. 9. 7. 00:13
반응형

지난 포스팅에서 Java Reflection에 대해 다뤘습니다. JDBC를 사용할 때 쓰이는 Class.forName 역시 Java Reflection에서 제공하는 기능 중에 하나입니다. JDBC 를 사용하여 DB에 접근하기 위해서는 제일 먼저 드라이버 클래스를 로드해야 하는데, 그 때 Class.forName을 사용합니다. 이번 포스팅에서는 드라이버를 로드할 때 Class.forName이 어떻게 활용되는지 알아보겠습니다.

아래의 문서와 Java API를 참고하여 공부했습니다.

https://www.baeldung.com/java-classloaders

https://docs.oracle.com/javase/9/migrate/toc.htm#JSMIG-GUID-A868D0B9-026F-4D46-B979-901834343F9E

https://www.kdata.or.kr/info/info_04_view.html?dbnum=183810 

https://pjh3749.tistory.com/250

 

 

Index

1. JDBC 작업의 일반적인 순서

2. 예제 코드

3. Class.forName의 역할에 대해 궁금한 점

4. Class.forName JavaDocs 읽어보기

5. Class Loader의 종류와 계층 구조

6. Class Loading 시점에 따른 분류

7. JDBC 작업에서의 Class.forName 돌아보기

 

 

1. JDBC 작업의 일반적인 순서

JDBC를 사용하여 DB에 접근하는 일반적인 순서는 다음과 같습니다.

1. Driver 클래스 로드

2. DB 연결을 위한 Connection 객체 생성

3. SQL을 담는 Statement 또는 PreparedStatement 객체 생성

4. SQL 실행

5. 리소스 정리

 

 

2. 예제 코드

위에서 살펴본 순서에 대한 예제 코드는 다음과 같습니다.

// 1. Driver 클래스 로드
Class.forName("com.mysql.cj.jdbc.Driver");

// 2. DB 연결을 위한 Connection 객체 생성
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/...");

// 3. SQL을 담는 PreparedStatement 객체 생성
PreparedStatement preparedStatement = connection.prepareStatement(
"INSERT into user(id, name, password) values(?,?,?)"
);
preparedStatement.setLong(1, 1);
preparedStatement.setString(2, "name");
preparedStatement.setString(3, "password");

// 4. SQL 실행
preparedStatement.executeUpdate();

// 5. 리소스 정리
preparedStatement.close();
connection.close();

 

MySQL DBMS를 사용한다고 가정할 때, 맨 처음에 Driver 클래스를 로드하기 위해 Class.forName 메소드를 활용합니다. 

Class.forName("com.mysql.cj.jdbc.Driver");

 

 

3. Class.forName의 역할에 대해 궁금한 점

Reflection을 공부할 때, Class는 실행 중인 자바 프로그램에서 클래스와 인터페이스를 표현하는(정보를 담는) 클래스라고 배웠습니다. 그리고 Class의 static 메소드인 forName은 클래스의 이름을 매개변수로 받아서 Class 객체를 리턴해줍니다. 

그런데 JDBC에서는 Driver 클래스를 JVM에 로드하기 위해서 Class.forName이 사용되고 있습니다. 그러면 Class.forName의 역할은 단지 클래스의 이름으로 Class 객체를 얻는 것이 아닌, JVM에 클래스를 로딩하는 역할도 하는 것일까요?

 

4. Class.forName JavaDocs 읽어보기

Class.forName은 매개변수가 다른 두 개의 메소드가 있습니다.

Java 11 기준 forName(String className)의 JavaDocs를 읽어보았습니다.

forName(String className)을 실행한 결과는 Class.forName(className, true, currentLoader)를 실행한 결과와 같다고 합니다.

forName(String name, boolean initialize, ClassLoader loader)의 JavaDocs는 다음과 같습니다. 

클래스 로더에 의해 클래스 또는 인터페이스가 로드된다고 합니다. 별도의 클래스 로더를 지정하지 않으면 Bootstrap 클래스 로더에 의해 로드됩니다. Bootstrap 클래스 로더는 다음 절에서 살펴보겠습니다.

forName JavaDocs를 읽어보니 forName의 역할이 단지 Class 객체를 반환하는 것이 아니라 클래스 로더에 의해 그 클래스 객체가 로드된다는 것을 알 수 있었습니다.

 

 

5. Java Class Loader의 종류와 계층 구조

클래스 로더는 런타임에 클래스들을 동적으로 JVM에 로드합니다. 애플리케이션이 클래스를 필요로 할 때 메모리에 적재합니다. 

클래스 로더는 다음과 같은 계층 구조로 이루어져 있습니다.

- Bootstrap Class Loader: 모든 클래스들을 로드합니다. ($JAVA_HOME/jre/lib)

- Platform Class Loader: Java 표준 코어 클래스를 확장한 클래스를 로드합니다. ($JAVA_HOME/lib/ext, java.ext.dirs)

- System Class Loader: Classpath에 있는 클래스를 로드합니다.

 

각 계층에 있는 Class Loader는 그 역할을 상위 Class Loader에게 위임합니다. 상위 Class Loader는 각자가 맡은 경로에서 클래스를 찾아본 뒤, 없으면 다시 하위 클래스로 내려옵니다. 최하위 Class Loader에서 찾고자 하는 클래스를 찾을 수 없으면ClassNotFoundException을 발생시킵니다.

 

 

6. Class Loading 시점에 따른 분류

Load Time Dynamic Loading 

Load Time Dynamic Loading은 하나의 클래스를 로딩하는 시점에, 그 클래스에서 사용하는 다른 클래스들을 함께 로딩하는 것을 말합니다. 관련 클래스들은 아직 참조되지 않은 상태에서 JVM에 로드됩니다.

Run Time Dynamic Loading

Run Time Dynamic Loading은 클래스가 참조되는 시점에 로딩하는 것을 말합니다.

 

 

7. JDBC에서 Class.forName 돌아보기

JDBC에서 Class.forName의 역할을 알아보기 위해 Class Loading의 종류와 과정에 대해 알아보았습니다. 결론적으로 아래의 코드에서 Class.forName을 통해 Mysql DBMS에 접근할 수 있는 JDBC Driver 클래스가 클래스 로더에 의해 로드된다는 것을 알 수 있습니다.

Class.forName("com.mysql.cj.jdbc.Driver");

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/...");

PreparedStatement preparedStatement = connection.prepareStatement(
"INSERT into user(id, name, password) values(?,?,?)"
);
preparedStatement.setLong(1, 1);
preparedStatement.setString(2, "name");
preparedStatement.setString(3, "password");

preparedStatement.executeUpdate();

preparedStatement.close();
connection.close();

 

그러면 왜 Driver driver = new Driver()와 같이 객체를 생성하지 않고도 GC의 대상이 되지 않고 사용할 수 있는 것일까요? Driver 클래스의 내부 코드를 보았습니다.

 

static절은 클래스가 로딩되는 시점에 초기화를 위해 실행되는 부분입니다. Driver 클래스의 static 절의 내부에서 new Driver() 코드를 통해 인스턴스를 생성하고 있습니다. 그리고 DriverManager.registerDriver 메소드를 통해 Driver 인스턴스를 DriverManager에 등록합니다.

이렇게 static 블록을 가지는 클래스들은 Class.forName을 호출하기만 해도 초기화가 실행되기 때문에, 인스턴스를 별도로 관리하지 않는 클래스에서 자주 활용된다고 합니다. 이렇게 static 블록에서 클래스 자기 자신의 인스턴스를 생성하고 관리합니다.

DriverManager.registerDriver 의 코드는 다음과 같습니다.

    public static void registerDriver(java.sql.Driver driver)
        throws SQLException {

        registerDriver(driver, null);
    }
    public static void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if (driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

if문으로 driver가 null이 아닌지 체크하고, addIfAbsent 메소드를 호출하고 있습니다. addIfAbsent 메소드는 그 이름에서 알 수 있듯이, 현재 DriverManager에 등록된 드라이버가 없을 때에만 드라이버를 등록해주는 메소드입니다. 따라서 Class.forName을 여러 번 호출하더라도 static절에 있는 new Driver() 코드에 의해 드라이버 객체가 계속 생성되는 것을 막아줍니다.

반응형