본문 바로가기

프로젝트/APM prototype 개발

[HikariCP][Delegate]ProxyPreparedStatement

목차

    타깃 코드

    다음 코드의 동작과 흐름을 확인해보자.

    public abstract class ProxyPreparedStatement extends ProxyStatement implements PreparedStatement {
        ProxyPreparedStatement(ProxyConnection connection, PreparedStatement statement) {
            super(connection, statement);
        }
    
        public boolean execute() throws SQLException {
            this.connection.markCommitStateDirty();
            return ((PreparedStatement)this.delegate).execute();
        }
    
        public ResultSet executeQuery() throws SQLException {
            this.connection.markCommitStateDirty();
            ResultSet resultSet = ((PreparedStatement)this.delegate).executeQuery();
            return ProxyFactory.getProxyResultSet(this.connection, this, resultSet);
        }
    
        public int executeUpdate() throws SQLException {
            this.connection.markCommitStateDirty();
            return ((PreparedStatement)this.delegate).executeUpdate();
        }
    
        public long executeLargeUpdate() throws SQLException {
            this.connection.markCommitStateDirty();
            return ((PreparedStatement)this.delegate).executeLargeUpdate();
        }
    }

     

    이 추상 클래스이자, Hikari Pstmt 구현체의 부모는 '정보를 받아 마킹'하고, 'return을 통해 대리자 객체에게 실제 요청을 전달'한다.

     

    ProxyPreparedStatement.executeQuery 메서드 흐름

    트랜잭션 상태 마킹

    this.connection.markCommitStateDirty(); 호출을 통해, 현재 커넥션의 트랜잭션 상태가 "더티(dirty)" 상태로 마킹된다.

    이는 트랜잭션이 변경되었음을 나타내며, 트랜잭션을 커밋하거나 롤백해야 함을 의미한다.

     

    이 connection은 실제로 CP중 하나인 ProxyConnection 객체이다.

    정확히 말하자면 this는 ProxyStatement를 의미한다.

     

    즉, 부모 ProxyStatement 객체의 ProxyConnection의 Dirty 호출을 의미한다. 트랜잭션 용도로 사용하는 것 같다.

     

    PreparedStatement 실행 

    PreparedStatement

     

    ((PreparedStatement)this.delegate).executeQuery(); 부분.

    ProxyPreparedStatement의 delegate 필드에 저장된 실제 PreparedStatement 객체의 executeQuery 메서드가 호출된다.

     

    부모 객체의 delegate

     

    이 호출을 통해 데이터베이스에 SQL 쿼리가 실행되고, 실행 결과로 ResultSet 객체가 반환된다.

     

    ResultSet 프록싱 

     

    ProxyStatement

     

    반환된 ResultSet 객체는 ProxyFactory.getProxyResultSet(this.connection, this, resultSet);를 통해 프록시 객체로 감싸진다.

     

    //ProxyFactory의 프록시로 감싸는 부분. 디컴파일러로 보기 힘든 듯 하다.
    
    static ResultSet getProxyResultSet(final ProxyConnection connection, final ProxyStatement statement, final ResultSet resultSet)
       {
          // Body is replaced (injected) by JavassistProxyFactory
          throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
       }

     

    이 과정에서 ProxyResultSet 객체가 생성되며, 원본 ResultSet 객체를 대리(delegate)하게 된다.

    ProxyResultSet은 원본 ResultSet의 모든 메서드 호출을 내부적으로 위임(delegate)하면서 추가적인 로깅, 리소스 관리, 또는 다른 기능을 수행할 수 있는 확장 지점을 제공한다.

     

    요약

    1. 이 쿼리가 수행되면 부모 객체인 Statement 구현체의 Connection을 통해서 일단 'dirty check'를 한다.
    2. 이후 마찬가지로 부모 객체인 Statement 구현체의 delegate 객체를 사용해서 (정확히 말하자면 delegate의 executeQuery()) ResultSet 객체를 가져온다.
    3. 이후 2번의 ResultSet 객체를 Proxy.Factory.getProxyResultSet을 통해 참조 객체를 만들어 return한다. (이것은 원본의 값이 아닌, 참조 프록시 객체이다.)

     

     

    HikariProxyPreparedStatement :

    return super.executeQuery(var1);

     

    부모인 ProxyStatement의 메서드 실행

       /** {@inheritDoc} */
       @Override
       public ResultSet executeQuery(String sql) throws SQLException
       {
          connection.markCommitStateDirty();
          ResultSet resultSet = delegate.executeQuery(sql);
          return ProxyFactory.getProxyResultSet(connection, this, resultSet);
       }

     

    < 로직 실행 >

    delegate 객체를 통해 실제 요청 및 실행. 이 값을 가져온다.

    'return ProxyFactory.getProxyResultSet(this.connection, this, resultSet);' 을 통해 프록시 객체로 감싸져 반환된다.

     

    ProxyStatement : delegate 객체?

    delegate 객체는 실제 JDBC Connection 객체에서 생성된 PreparedStatement 또는 Statement 객체의 실제 인스턴스를 참조한다.

     

    이는 원래 JDBC Interface인 statement에서는 존재하지 않았지만, Hikari에서 Proxy를 사용하기 위해 만든 개념일 것이다.

    Proxy - delegate 관계 예시

    Statement(delegate) 객체의 역할과 동작

    1. 역할 

    delegate 객체는 실제 DB 작업(쿼리 실행, 결과 처리 등)을 수행하는 Statement 또는 PreparedStatement 객체를 참조한다.

    프록시 객체는 이 delegate 객체를 통해 실제 데이터베이스 작업을 위임하게 된다.

     

    즉, 프록시 객체는 요청을 받아 실제 작업을 delegate 객체에게 전달하고, 그 결과를 클라이언트에게 반환한다

     

    2. 동작

    public abstract class ProxyStatement implements Statement {
        protected final ProxyConnection connection;
        final Statement delegate; //이 delegate 객체는 실제 일하는.. JDBC 객체의 참조이다.
        private boolean isClosed;
        private ResultSet proxyResultSet;
    
        ProxyStatement(ProxyConnection connection, Statement statement) {
            this.connection = connection;
            //생성자에서 실제 객체를 입력받는다.
            //Statement 객체(delegate)는 실제 JDBC 커넥션을 통해 생성된 Statement 인스턴스인 것이다.
            this.delegate = statement;
        }
        ..생략
     }
    final Statement delegate는 ProxyStatement 클래스 내부에서 실제 데이터베이스 작업을 수행하는 java.sql.Statement 객체의 참조이다.

     

    클라이언트가 프록시 객체를 통해 SQL 쿼리를 실행하려고 할 때, 프록시 객체는 내부적으로 delegate 객체의 해당 메서드를 호출하여 실제 데이터베이스 작업을 수행한다.

    이 과정에서 프록시 객체는 필요에 따라 요청 전후로 추가적인 로직(예: 로깅, 트랜잭션 관리, 리소스 모니터링 등)을 실행할 수 있다.

     

     

    반환 값 :  ProxyResultSet 

    executeQuery 메서드는 ProxyResultSet 객체를 반환한다.

    이 ProxyResultSet 객체는 실제 데이터베이스 쿼리 결과(ResultSet)를 대리하는 프록시 객체로, 애플리케이션 코드는 이 프록시 객체를 통해 쿼리 결과를 순회하고 데이터를 추출할 수 있다.

     

    즉, ProxyResultSet 객체는 애플리케이션 코드나 프레임워크(예: Spring, Hibernate 등)에서 사용된다.

     

     

     

    이슈 : 두 가지 방식의 executeQuery 메서드 사용

    위의 흐름 분석과 별개로, HikariProxyStatement는 두 가지 타입의 executeQuery가 있다.

    이 둘의 차이점을 명확히 인지해야 한다.

    바이트코드 조작을 통해 로깅 과정 중 오버로드 된 메서드를 이제서야 확인했다..

     

    확인하다 보니, 위의 설명과 별개로 PSTMT의 두 가지 사용방법이 있음을 확인했다.

     

    executeQuery(String sql)와 Statement

    이 구문은 주로 JDBC를 직접 사용하는 상황에서 볼 수 있는 패턴에서 호출된다고 한다.
    우리가 JDBC를 처음 접해보며 SetString .. 등으로 직접 넣었던 그 구문 말이다.

     

    즉, JPA 등에서는 거의 쓰이지 않는다.

     

    이 메서드는 Statement 인터페이스에 정의되어 있으며, SQL 문자열을 직접 받아 실행한다. PreparedStatement에서는 이 메서드 대신 SQL 문자열을 미리 지정하여 객체를 생성하고, executeQuery() 메서드를 사용하여 쿼리를 실행한다.

     

        public ResultSet executeQuery(String var1) throws SQLException {
            Logger var3 = LoggerFactory.getLogger(HikariProxyPreparedStatement.class);
            try {
                return super.executeQuery(var1);
            } catch (SQLException var4) {
                throw this.checkException(var4);
            }
        }

     

    -> 이건 return에서 (Hikari - pool package 내부)'ProxyStatement 클래스'의 executeQuery(String sql) 로 간다.

     

    executeQuery()와 PreparedStatement

    JPA 환경에서 executeQuery() (파라미터 없는 버전)가 주로 호출되는 이유는, 대부분의 쿼리 실행이 PreparedStatement를 통해 이루어지기 때문이다.

     

    PreparedStatement는 SQL 쿼리에 파라미터를 바인딩하여 실행하는데 사용되며(즉, SQL 코드는 이미 저장되어 있고, 파라미터 값만 바꾸는 것이다.),

     

    이 경우 쿼리 문자열은 PreparedStatement 생성 시 이미 지정되어 있다.

    (JPA 고수준 언어에서 jpql, critica 등으로 이미 쿼리 언어는 지정되었으며, 내부 인자 값 역시 생성 시 설정되었다.)

    실행 시에는 파라미터 없이 executeQuery() 메서드를 호출하게 된다.

     

    public ResultSet executeQuery() throws SQLException {
        try {
            return super.executeQuery();
        } catch (SQLException var2) {
            throw this.checkException(var2);
        }
    }

     

    -> 이건 return에서 (Hikari - pool package 내부)ProxyPreparedStatement 클래스의 executeQuery(String sql) 로 간다.