IBM Korea Skip to main content
       IBM 홈    |  제품 & 서비스  |  고객지원 & 다운로드  |  회원가입  

IBM developerWorks > 자바
developerWorks

JDBC 쿼리 로깅을 쉽게
고급 PreparedStatement를 사용하여 JCBC 코드에 로깅 추가하기

Level: Intermediate

Jens Wyke
Consultant, IBM
2003년 5월 20일

JDBC java.sql.PreparedStatement 인터페이스로 간단히 확장함으로서 쿼리 로깅의 에러를 줄일 수 있다. 코드도 더 깔끔해진다. IBM e-비즈니스 컨설턴트인 Jens Wyke가 래핑 기술을 설명한다.

대부분의 경우, JDBC PreparedStatement는 데이터베이스 쿼리를 쉽게 수행할 수 있도록 한다. 또한 전체 애플리케이션 퍼포먼스에도 뚜렷한 향상을 보인다. 하지만 PreparedStatement 인터페이스는 쿼리 문장을 로깅할 때 부족한점이 있다. PreparedStatement의 강점이 다양성에 있는 만큼, 좋은 로깅 엔트리 라면 데이터베이스로 보내지는 SQL이 실제 매개변수 값을 대체한 매개변수 플레이스홀더를 어떻게 다루는지를 설명해야 한다.

이 글에서는 쿼리 로깅을 위해 JDBC PreparedStatement 인터페이스를 확장하는 방법을 배운다. LoggableStatement 클래스는 PreparedStatement 인터페이스를 구현하지만 로깅에 적합한 포맷으로 쿼리 스트링을 얻기위한 메소드를 추가한다. LoggableStatement 클래스를 사용하면 로깅 코드에서 에러를 줄일 수 있고 관리가 쉬운 코드를 만들어 낼 수 있다.

일반적인 로깅 솔루션
Listing 1은 데이터베이스 쿼리를 만들 때 PreparedStatement의 전형적인 사용 예제를 보여주고 있다. 예제로 SQL query SELECT를 사용하겠지만 DELETE, UPDATE, INSERT 같은 기타 SQL 문장들도 적용할 것이다.

Listing 1. 일반적인 SQL 데이터베이스 쿼리

     String sql = "select foo, bar from foobar where foo < ? and bar = ?";
    String fooValue = new Long(99);
    String barValue = "christmas";

    Connection conn = dataSource.getConnection();
    PreparedStatement pstmt = conn.prepareStatement(sql);

    pstmt.setLong(1,fooValue);
    pstmt.setString(2,barValue);

    ResultSet rs = pstmt.executeQuery();

    // parse result...

Listing 1에서 쿼리를 위한 좋은 로그 엔트리라면 다음과 같을 것이다:


Executing query: select foo,bar from foobar where foo < 99 and 
bar='christmas'

그러한 엔트리용 로깅 코드가 어떻게 보이는지에 대한 예제도 있다. Listing 1의 물음표는 각각의 매개변수 값으로 대체되었다.


System.out.println("Executing query: select foo, bar from foobar where foo
< "+fooValue+" and bar = '+barValue+"'")

보다 나은 접근방식으로는 메소드를 만드는 것이다. replaceFirstQuestionMark를 호출해본다. 이것은 쿼리 스트링을 가지고 물음표를 매개변수 값으로 대체한다. (Listing 2). 메소드를 사용하면 SQL 문을 설명하기 위한 중복 스트링을 만들 필요가 없다.

Listing 2. 스트링 대체를 위해 replaceFirstQuestionMark 사용하기

      // listing 1 goes here

     sql = replaceFirstQuestionMark(sql, fooValue);
     sql = replaceFirstQuestionMark(sql, barValue);
     System.out.println("Executing query: "+sql);

구현은 쉽지만 솔루션은 이상적인 것은 아니다. SQL 템플릿이 변경될 때 마다 로깅 코드도 변경된다는 것이 문제이다. 실수하기 쉬운 부분이다. 쿼리는 바뀌지만 로깅 코드를 업데이트 하는 것을 잊을 수 있고 데이터베이스에 보내진 쿼리와 맞지 않는 로깅 엔트리로 끝낼 수 있다. 디버깅은 악몽이다!

각각의 매개변수(fooValuebarValue)를 사용하도록 하는 디자인이 필요하다. 실제 값으로 대체된 매개변수 플레이스홀더가 있는 쿼리 스트링이 마음에 든다. java.sql.PreparedStatement는 그와 같은 메소드가 없기 때문에 우리가 그것을 구현해야 한다.

맞춤 솔루션
우리가 구현한 PreparedStatement는 JDBC 드라이버가 제공한 "실제 문장" 주위에서 래퍼로 작동한다. 래퍼 문장은 모든 메소드 호출(예를 들어, setLong(int, long)setString(int,String))을 "실제 문장"으로 전달한다. 이 전에 관련된 매개변수 값을 저장하여 로깅 아웃풋을 만드는데 사용될 수 있도록 한다.

Listing 3은 LoggableStatement 클래스가 java.sql.PreparedStatement를 구현하는 방법을 보여주고 있다. JDBC 커넥션과 인풋으로서 SQL 템플릿을 사용하여 만들어지는 방법을 보여준다.

Listing 3. java.sql.PreparedStatement를 구현하는 LoggableStatement

  public class LoggableStatement implements java.sql.PreparedStatement {

     // used for storing parameter values needed
      // for producing log
     private ArrayList parameterValues;     
          
     // the query string with question marks as  
     // parameter placeholders
     private String sqlTemplate;       
               
     // a statement created from a real database     
     // connection                                       
     private PreparedStatement wrappedStatement; 
                                                 

    public LoggableStatement(Connection connection, String sql) 
      throws SQLException {
      // use connection to make a prepared statement
      wrappedStatement = connection.prepareStatement(sql);
      sqlTemplate = sql;
      parameterValues = new ArrayList();
    }
     }

LoggableStatement 작동 방법
Listing 4는 LoggableStatementsaveQueryParamValue() 메소드로 호출을 추가하고 setLongsetString 메소드를 위한 "실제 문장"에 대한 상응 메소드를 호출하는 것을 보여주고 있다. saveQueryParamValue() 호출은 비슷한 방식으로 매개변수 설정(예를 들어, setChar, setLong, setRef, setObj)에 사용된 모든 메소드에 추가된다. executeQuery 메소드가 saveQueryParamValue()를 호출하지 않고 어떻게 래핑되는지를 보여주고 있다. 이것은 "매개변수 설정" 메소드가 아니기 때문이다.

Listing 4. LoggableStatement 메소드

     public void setLong(int parameterIndex, long x) 
         throws java.sql.SQLException {
      wrappedStatement.setLong(parameterIndex, x);
      saveQueryParamValue(parameterIndex, new Long(x));
   }

   public void setString(int parameterIndex, String x) 
       throws java.sql.SQLException {
      wrappedStatement.setString(parameterIndex, x);
      saveQueryParamValue(parameterIndex, x);
   }

  public ResultSet executeQuery() throws java.sql.SQLException {
     return wrappedStatement.executeQuery();
   }

saveQueryParamValue() 메소드는 Listing 5에서 볼 수 있다. 각각의 매개변수 값을 String 표시로 변환하면서 getQueryString 메소드에 의해 나중에 사용될 수 있도록 이를 저장한다. 기본적으로 하나의 객체는 toString 메소드를 사용하는 String으로 변환되겠지만 객체가 String 이나 Date라면 싱글 쿼트 부호 ('')가 붙는다. getQueryString() 메소드로는 로그에서 대부분의 쿼리를 복사하여 변경하지 않고 대화형 SQL 프로세서에 붙일 수 있다. 테스팅과 디버깅에 쓰인다. 필요한 경우 메소드를 수정하여 다른 클래스의 매개변수 값을 변환할 수 있다.

Listing 5. saveQueryParamValue() 메소드

  private void saveQueryParamValue(int position, Object obj) {
      String strValue;
      if (obj instanceof String || obj instanceof Date) {
           // if we have a String, include '' in the saved value
           strValue = "'" + obj + "'";
      } else {
           if (obj == null) {
                // convert null to the string null
                 strValue = "null";
           } else {
                // unknown object (includes all Numbers), just call toString
                strValue = obj.toString();
           }
      }
      // if we are setting a position larger than current size of 
      // parameterValues, first make it larger
      while (position >= parameterValues.size()) {
           parameterValues.add(null);
      }
      // save the parameter
      parameterValues.set(position, strValue);
 }

모든 매개변수들이 표준 메소드를 사용하여 설정되면 LoggableStatementgetQueryString() 메소드를 호출하여 쿼리 스트링을 얻는다. 모든 물음표는 실제 매개변수 값으로 대체된다.

LoggableStatement 사용하기
Listing 6은 Listings 1과 2의 코드가 LoggableStatement를 사용하기 위해 어떻게 변경되었는지를 보여주고 있다. LoggableStatement를 우리의 애플리케이션 코드에 도입하면 중복된 매개변수 문제를 해결할 수 있다. SQL 템플릿이 변경되면, PreparedStatement에 대한 매개변수 설정 호출을 업데이트하면 된다. 변경사항은 로깅 코드를 업데이트 하지 않고도 로깅 아웃풋에 반영된다.

Listing 6. LoggableStatement 작동

    String sql = "select foo, bar from foobar where foo < ? and bar = ?";
    long fooValue = 99;
    String barValue = "christmas";

    Connection conn = dataSource.getConnection();
    PreparedStatement pstmt;

    if(logEnabled) // use a switch to toggle logging.
        pstmt = new LoggableStatement(conn,sql);
    else
        pstmt = conn.prepareStatement(sql);

    pstmt.setLong(1,fooValue);
    pstmt.setString(2,barValue);

    if(logEnabled)
       System.out.println("Executing query: "+
         ((LoggableStatement)pstmt).getQueryString());

    ResultSet rs = pstmt.executeQuery();

참고자료

목 차:
일반적인 로깅 솔루션
맞춤 솔루션
LoggableStatement 작동 방법
LoggableStatement 사용하기
참고 자료
필자 소개
기사에 대한 평가
관련 dW 링크:
JDBC tips
Java design patterns 101
Performance management -- do you have a plan?
Subscribe to the developerWorks newsletter
US 원문 읽기
Also in the Java zone:
Tutorials
Tools and products
Code and components
Articles
필자소개
Jens Wyke는 IBM Business Consulting Services(스웨덴)에서 컨설턴트로 일하고 있다.
이 기사에 대하여 어떻게 생각하십니까?

정말 좋다 (5) 좋다 (4) 그저그렇다 (3) 수정보완이 필요하다(2) 형편없다 (1)

  회사소개  |  개인정보 보호정책  |  법률  |  문의