정상혁정상혁

역시 최근 들어 스프링소스의 발걸음이 더욱 빨라졌습니다. Grails, Spring Python, tc Server, 3.0M1 발표.. 현란하게 소식이 쏟아지는군요. 그만큼 공부할 것도 더 늘겠지만, 그래도 반갑고 기대감을 가지게 하는 소식들입니다.

대규모 투자를 받고, 일을 더 벌이고 하면서 스프링소스가 돈맛에 초심을 잃을까봐 걱정을 하시는 분도 많이 계시는 듯 합니다. 그래도 성공적인 사업 구조가 정착되어서 그 수익이 개발자들에게 도움이 되는 제품들을 더 잘 개발하는데 쓰인다면야 굳이 돈을 많이 벌게 되는 것이 나쁘다고는 생각하지 않습니다. 유료로 제공되는 제품이 많아진다고 해도, 그럴만한 가치가 있는 제품이면 돈을 내고, 그것이 아니라도 프레임웍이나 서버, 개발도구의 시장은 독점 시장이 아니니 다른 선택을 할 수 있는 대안이 많을 것입니다. 물론 지금까지의 스프링의 행보를 봐서는 충분히 앞으로도 가치있는 결과물을 만들 것이라고 기대하고 있습니다.

여러 소식들 중에 솔직히 가장 재미있었던 것은, Spring one America 2008에 참석하신 분들을 통해 알게된, 로드존슨의 나이였습니다. 스프링 핵심 개발자인 유겐할러의 말에 의하면 로드존슨은 38살 정도라고 하네요.

왜 저는 로드존슨을 당연히 40대라고 생각했을까.. 하고 분석해보니, Expert one-on-one J2EE Design and Developement 책 때문에 그런 선입관을 가지게 된 것 같습니다.

이 책이 나온 것이 2003년인데, 그렇다면 이 때는 33살이였단 말이되는군요. 이 책을 쓰는데도 2년 정도가 걸렸다고 들은 것 같습니다. 그리고 책 표지에 보면 로드존슨은 음악학(Musicology)에 박사학위가 있다는 말도 있습니다. 그래서, 다른 분야도 박사까지 하고 컴퓨터 분야에서도 이정도 경지까지 가려면 적어도 이 책 출판시에 30대 중후반은 되지 않았을까.. 하는 추측을 한 것이죠.

정상혁정상혁

FindBugs를 이용한 코드검사를 Maven2을 통해 실행하고, Hudson을 통해 확인하는 설정을 정리해 봅니다.

Eclipse에서 findbugs로 코드검사를 해볼 수 있는 툴은 http://findbugs.cs.umd.edu/eclipse/ 를 update site로 지정하면 설치할 수 있습니다. 설치가 잘 되었다면 소스 폴더를 선택하고 마우스 우클릭을 하면 'Find Bugs’라는 메뉴가 생긴 것이 보일 것입니다. 그 메뉴를 통해 원하는 프로젝트를 검사하고, Bug Explorer 탭을 선택하보면 아래와 같은 화면이 나옵니다.

eclipse-findbugs.jpg

Bug Explorer 탭에서는 버그를 유형별로 정리해서 보여주고, 소스탭에서는 해당하는 코드에 벌레 모양 아이콘을 찍어줍니다. 그리고 Problems 창에서는 Eclipse에서 잡아내는 다른 경고처럼 warning으로 해당 소스를 표시해 줍니다. Bug Details 탭을 누르면 버그에 대한 자세한 설명도 볼 수 있습니다.

프로젝트의 Properties 메뉴에서 FindBugs 설정란으로 가면 검사할 규칙 등을 선택할 수 있습니다.

eclipse-findbugs-config1.jpg

이 설정화면에서 'Run FindBugs automatically’를 선택하면, 소스가 바뀔 때마다 자동으로 검사를 수행해 줍니다. 이 기능이 선택되어 있지 않다면, 지적된 소스를 수정해도 다시 수동으로 검사를 돌려야지 경고메시지 지워지므로, 이클립스가 아주 느리다는 느낌이 안 들정도라면 선택하는 것이 좋습니다. 이 기능을 선택해서, Eclipse의 Problems 탭에서 코드 작성 즉시 에러와 경로를 확인할 수 있게 되었다면 .project 파일에 아래와 같은 부분이 추가되어 있을 것입니다.

<natures>

    ....

  <nature>edu.umd.cs.findbugs.plugin.eclipse.findbugsNature</nature>

</natures>

FindBugs 설정 화면 중 Detector Configuration 탭에서는 검사할 규칙들을 지정할 수 있고, Reporter configuration 탭에서는 보고해 줄 버그의 경고단계와 분류를 선택할 수 있습니다.

eclipse-findbugs-config2.jpg

Filter files 탭에서는 별도의 XML파일로 선언된 포함하거나 제외시킬 버그와 파일에 대한 설정을 가지고 올 수 있습니다.

eclipse-findbugs-config3.jpg

아래에 자세히 설명하겠지만, Maven 설정에서 참조하는 findBugsExclude.xml을 Eclipse plugin에서도 똑같이 지정해서 Maven과 Eclipse에서 같은 기준으로 검사가 수행되도록 했습니다.

Maven2의 findbugs-maven-plugin은 pom.xml에 아래와 같이 설정됩니다.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>findbugs-maven-plugin</artifactId>
  <version>2.4.0</version>
  <configuration>
    <findbugsXmlOutput>true</findbugsXmlOutput>
    <findbugsXmlWithMessages>true</findbugsXmlWithMessages>
    <xmlOutput>true</xmlOutput>
    <excludeFilterFile>$\{basedir}/findBugsExclude.xml</excludeFilterFile>
   </configuration>
</plugin>

위의 설정에 들어가는 속성들에 대해서는 findbugs-maven-plugin 설명 페이지에서 자세한 내용을 보실 수 있습니다.

저는 제외할 검사규칙을 지정하기 위해서 excludeFilterFile속성에 findBugsExclude.xml을 지정했습니다.

findBugsExclude.xml의 내용은 아래와 같이 설정했습니다.

....
<FindBugsFilter>
....
....
  <Match>
....
....
   <Bug code="Se,SnVI,Dm,UwF,EI,EI2" />
....
....
  </Match>
....
....
</FindBugsFilter>
....

제외할 것을 선언하는 파일에 이렇게 적었으니 Bug code가 "Se,SnVI,Dm"에 해당하는 버그검사는 제외한다는 의미입니다. Filter의 설정 방법에 대해서는 findbugs의 매뉴얼을 참조하시면 됩니다.

버그 코드 중 Se,SnVI는 serialVersionUID에 관한 것이고 Dm은 String.toUpperCase 등의 메소드에서 Local설정을 권유하는 검사입니다. (버그 코드에 대한 설명 페이지 참조)

이렇게 설정을 하고 mvn findbugs:findbugs 로 maven을 실행시키면 필요한 라이브러리들을 다운로드 받고 빌드가 실행됩니다. 실행이 성공했다면 목적지 폴더에 findbugs.xml과 findbugsXml.xml파일이 생성이 되었을 것입니다.

이것을 Hudson을 통해서 보기 위해서는 Hudson에서도 findbugs plugin을 설치해야 됩니다.

Hudson 첫 화면에서 Manage Hudson - Manage Plugins 메뉴를 찾아갑니다. Available 탭에서 findbugs를 선택하고 화면 우측하단의 'install’버튼을 누르면 Hudson이 알아서 라이브러리를 다운 받아줍니다. 설치한 plug-in이 실행되기 위해서는 Hudson을 재시작해야 합니다.

그런 다음에 findbugs를 적용하고자 하는 프로젝트에 가서 Configure메뉴를 선택하면 아래와 같이 Publish FindBugs Analysis Result라는 부분이 추가된 것을 보실 수 있을 것입니다.

hudson-findbugs-config.jpg

이것을 선택하고 원하는 기준값이 있을 경우 입력한 뒤에 "save’버튼을 누르고 build를 해보면 됩니다. 물론 build에는 findbugs:findbugs goal이 포함되어야 하겠죠.

빌드가 성공하는 것을 보고 프로젝트의 메뉴를 보면 FindBugs Warnings라는 메뉴가 추가된 것을 확인하실 수 있습니다.

hudson-findbugs-menu.jpg

그 메뉴를 누르면 생성된 보고서가 보입니다.

hudson-findbugs-report.jpg

warning이 존재할 경우 건수를 클릭하면 해당하는 클래스들이 나오고, 클래스를 선택하면 소스에서 warning을 발생시키는 부분까지 보여줍니다.

만약 hudson의 findbugs plugin을 실행할 때 Cannot find setter nor field in org.apache.maven.plugins.site.SiteMojo for 'xmlOutput' 와 같은 에러가 난다면 Hudson plugin 수동으로 빌드&업로드를 참조해서 최신 버전으로 플러그인을 업데이트 해보시기 바랍니다.

정상혁정상혁

다음의 코드는 1000건을 insert하면서 100건마다 한번씩 commit을 하는 기능을 수행합니다. 대용량 데이터를 입력 할 때 이렇게 주기적으로 commit을 해주는 것은 일반적인 처리방식입니다. Cubrid DB에서는 이와 같은 코드를 실행시키면 'Attempt to access a closed PreparedStatement.'라는 에러 메시지를 받게 될 수도 있습니다.

package test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Properties;

public class JDBCTest {

  public static void main(String[] args) {
    String query = "INSERT into common_cd(cd_type, cd, cd_name) values(?, ?, ?);";
    Properties cubrid = new Properties();
    cubrid.put("url","jdbc:cubrid:127.0.0.1:43003:test:::");
    cubrid.put("driver","cubrid.jdbc.driver.CUBRIDDriver");
    cubrid.put("user","test");
    cubrid.put("password","test");

    insertAndCommit(cubrid, query);
 }

  private static void insertAndCommit(Properties prop,String query) {
    Connection con =  null;
    PreparedStatement psmt  = null;
    try{
     Class.forName (prop.getProperty("driver"));
     con = DriverManager.getConnection(prop.getProperty("url"),prop);
     con.setAutoCommit(false);
     psmt = con.prepareStatement(query);
     for(int i=1;i<=1000;i++)\{
       psmt.setString(1, "00");
       psmt.setString(2, String.valueOf(i));
       psmt.setString(3, "code:"+i);
       psmt.execute();
       System.out.println(i+"th row set");
       if(i%100==0) {
         con.commit();
         System.out.println("commit-------------------------------------");
       }
     }
    } catch (Exception ex){
      ex.printStackTrace();
    } finally{
     try{ psmt.close();} catch(Exception e){}
     try{ con.close();} catch(Exception e){}
    }
 }
}

commit 을 했을 뿐인데, Statement가 닫혀버리는 형상이 발생한 것이죠. 원인을 찾기 위해 Cubrid JDBC드라이버의 클래스 파일들을 역컴파일 해서 봤더니, 아래와 같은 completeAllStatements()라는 메소드가 commit 메소드 안에서 호출이 되는 것이였습니다.

private void completeAllStatements() throws SQLException {
    for (int i = 0; i < statements.size(); i++) {
        UniSQLStatement unisqlstatement = (UniSQLStatement) statements.get(i);
        if (unisqlstatement instanceof UniSQLPreparedStatement) {
            statements.remove(i);
            if (u_con.brokerInfoStatementPooling())   unisqlstatement.complete();
            else   unisqlstatement.close();
        } else{
        unisqlstatement.complete();
        }
    }
}

Connection의 close시에 Statement가 close되는 것은 JDBC명세에 나와있지만 commit시에 close될 수 있다는 것은 의외였습니다. 혹시나 싶어서 MySql, Oracle, Hsql의 JDBC구현들을 다 살펴보았지만, 비슷한 동작을 할 가능성이 있는 코드들은 찾을 수가 없었습니다. Cubrid만의 독특한 점이죠.

이 문제를 해결하는 방법은 DB 서버에 있는 설정파일인 Cubrid_broker.conf 파일에서 STATEMENT_POOLING속성을 ON으로 표시해주는 것이라고 합니다.

이 속성은 서버에서 Statement Pooling을 하겠다는 의미가 아니고, Connection을 받아서 쓰는 Client 쪽에서 Connection Pooling을 하고 있으니 Commit시에 Statement를 끊지 말라고 알려주는 것이라고 합니다. 위에 있는 completeAllStatements메소드 안의 u_con.brokerInfoStatementPooling()코드가 그것을 검사하는 것 같습니다. 사실 앞의 코드의 commit을 여러번 하는 예는 Statement pooling도 아니고 한 번 만들어진 Statement를 계속 쓰는 것 뿐인데, 이것을 Statement pooling을 쓰는 것으로 인식시켜야 한다니 논리적으로 이상하기는 합니다.

그리고, Cubrid에 접근하는 Client는 CAS(Common Application Server)라는 것을 거치게 되어 있는데, 이 CAS보다 요청하는 Client의 갯수가 많을 때에는 항상 에러를 내는 것이 아니고, 이미 연결되어 있는 다른 Connection이 Transaction 중이 아니라면 그 Connection의 Statement자원을 해제시켜 버리고 가져와서 쓸 수 도 있다고 합니다. 이것을 막는 설정이 KEEP_CONNECTION이라는 속성입니다. CUBRID Manager Client프로그램에서 바꿀 수 있습니다.

Cubrid설정.JPG

또, Cubrid는 하나의 Connection에서 동시에 두개의 Prepare를 오픈 할 수 없는 구조라고 하네요. (참고자료:http://cubrid.com/bbs/view.php?id=faq&no=318&category=3[] http://cubrid.com/bbs/view.php?id=faq&no=318&category=3)

이런 특징들을 보면, Cubrid는 Client의 명시적인 요청이 없이도 자원을 최대한 빨리 해제시켜 버리는 것이 사상이 아닌가 하는 생각도 듭니다. 어쩌면 제대로 자원 해제를 못하는 JDBC 코드가 많았던 시절에 그로 인해 생기는 문제들을 대응하면서 생긴 구조일 것 같다는 추측도 해봅니다.(참고: JDBC에서 Connection, Statement,ResultSet의 close ) 그러나 대부분 프레임웍 기반의 개발을 하고 필요할 때 자원을 close시켜주는 코드들이 정착되고 있는 지금 시점에서는 큰 의미가 없는 특징이고, 오히려 다른 DB나 JDBC명세에 없는 동작으로 인한 혼란만 불러 일으킨다고 생각됩니다.

그래서 결론은 Cubrid에서 개발자의 의도보다 Statement를 먼저 해제시킬 수 있는 기능을 안 쓰려면 STATEMENT_POOLING과 KEEP_CONNECTION속성을 ON으로 하면 된다는 것입니다.