Monday

자바 JDBC 드라이버 연결 원리(Class.forName()) 본문

언어/Java 기타

자바 JDBC 드라이버 연결 원리(Class.forName())

뉴비2 2021. 4. 6. 22:05

JDBC 드라이버 로드 시 Class.forName()을 사용합니다.

 

forName() 메소드는 다음과 같이 정의되어있습니다.

public static Class<?> forName(String className)
                       throws ClassNotFoundException

위 메소드에서 반환하는 "Class" 클래스란 클래스들의 정보(클래스의 필드, 메서드, 클래스의 종류(인터페이스 등))를 담는 메타 클래스입니다. 

 

JDBC 4.0 이전에는 Class.forName("드라이버 이름")을 통해서 해당 드라이버 클래스를 메모리에 로딩하였습니다.

그 원리는 해당 클래스를 찾게되면, JVM은 해당 드라이버 클래스를 메모리에 올리게 되고 해당 드라이버 클래스는 아래와 같이 정적 초기화 블록(static { })을 통해 메모리 로딩 시 DriverManager에 해당 드라이버 클래스를 등록합니다.

참조 : pjh3749.tistory.com/250

static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

 

하지만 JDBC 4.0(Java 6) 이후에는 Class.forName()을 명시하지 않아도 되게끔 업그레이드 되었습니다.

 

In previous versions of JDBC, to obtain a connection, you first had to initialize your JDBC driver by calling the method Class.forName. Any JDBC 4.0 drivers that are found in your class path are automatically loaded. (However, you must manually load any drivers prior to JDBC 4.0 with the method Class.forName.)
-Java JDBC Tutorial-

..앞 내용 생략.. JSR 221 is the specification of JDBC 4.0 (included in Java SE 6).[2]
JDBC 4.1, is specified by a maintenance release 1 of JSR 221[3] and is included in Java SE 7.[4]
JDBC 4.2, is specified by a maintenance release 2 of JSR 221[5] and is included in Java SE 8.[6]
The latest version, JDBC 4.3, is specified by a maintenance release 3 of JSR 221[7] and is included in Java SE 9
- https://en.wikipedia.org/wiki/Java_Database_Connectivity -

 

그 이유는 DriverManager.class파일에 변경점이 생겼기 때문입니니다. JDBC 버전에 따라 변경된 점들이 세부적으로는 다르나 원리는 비슷합니다. 보통 JDBC 드라이버를 사용할 때, DriverManger.getConnection(url.. 등) 함수를 사용해서 사용하게 되는데 이 코드는 2가지를 의미합니다.

  • 1. DriverManger.class가 메모리에 없다면 먼저 로딩이 필요함
  • 2. DriverManger.class를 메모리에 로딩 후 getConnection을 호출함

 즉, JDBC 연결을 하기 위해서는 DriverManager를 호출해야 합니다. JDBC 4.2 버전에서는 DriverManager 클래스 내 초기화 블록(static {})에 loadInitialDrivers() 함수에서 초기화를 진행하였습니다. loadInitialDrivers() 함수는 Service Loader(서비스 로더)를 이용하여 java.sql.Driver를 구현한 드라이버 클래스들을 메모리에 로딩시켰습니다. 서비스 로더에 대한 자세한 내용은 아래 블로그를 참고하면 좋을 것 같습니다. [https://velog.io/@adduci/Java-서비스-로더ServiceLoader]

 

JDBC 4.3 에서는 DriverManager 클래스 내 초기화 블록이 사라지고 getConnection() 함수 호출 시 초기화를 진행하는 방식으로 변경되었습니다. getConnection() 함수 내부의 ensureDriverInitialized() 함수에서 JDBC 4.2 방식과 유사하게 드라이버들을 불러오고 연결을 진행합니다. 아래는 getConnection 함수에 일부입니다. registeredDrivers를 순회하며 aDriver.driver.connect()함수를 통해 연결을 확인합니다. 메모리에 로딩된 드라이버(registeredDrivers에 등록된 드라이버) 중 제일 먼저 연결을 성공한 드라이버와의 연결을 반환하게 되는 것 입니다.

 

연결이 가능한 지 확인할 때는 보통 "jdbc:Driver 종류://IP:포트번호/DB명" 문자를 사용하여 확인합니다. jdbc 뒤 Driver 종류를 명시하게 되는데 벤더사 혹은 개인이 제작한 드라이버(java.sql.Driver를 구현한 드라이버) 클래스에서 위 문자를 사용하여 확인하는 코드가 명시되어있습니다. 예시로는 Trino라는 분산 쿼리 엔진의 jdbc 소스코드 일부를 첨부해두었습니다. 더 자세한 동작 방식은 소스코드를 읽고 확인하시면 좋을 것 같습니다.

 //  Worker method called by the public getConnection() methods.
    private static Connection getConnection(        
        ... 생략 ...
        
        for (DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if (isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }
 }
package io.trino.jdbc;

import okhttp3.OkHttpClient;

import java.io.Closeable;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;

import static io.trino.client.OkHttpUtil.userAgent;
import static io.trino.jdbc.DriverInfo.DRIVER_NAME;
import static io.trino.jdbc.DriverInfo.DRIVER_VERSION;
import static io.trino.jdbc.DriverInfo.DRIVER_VERSION_MAJOR;
import static io.trino.jdbc.DriverInfo.DRIVER_VERSION_MINOR;

public class NonRegisteringTrinoDriver
        implements Driver, Closeable
{
    private final OkHttpClient httpClient = newHttpClient();

    @Override
    public void close()
    {
        httpClient.dispatcher().executorService().shutdown();
        httpClient.connectionPool().evictAll();
    }

    @Override
    public Connection connect(String url, Properties info)
            throws SQLException
    {
        // 여기가 확인하는 부분입니다. url을 이용하여 이 드라이버에서 해당 연결을 지원해줄 수 있는 지 판단합니다.
        if (!acceptsURL(url)) {
            return null;
        }

        TrinoDriverUri uri = TrinoDriverUri.create(url, info);

        OkHttpClient.Builder builder = httpClient.newBuilder();
        uri.setupClient(builder);

        return new TrinoConnection(uri, builder.build());
    }

    @Override
    public boolean acceptsURL(String url)
            throws SQLException
    {
        if (url == null) {
            throw new SQLException("URL is null");
        }
        return TrinoDriverUri.acceptsURL(url);
    }
Comments