Cubrid에서 commit시에 Statement가 닫히는 현상

다음의 코드는 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으로 하면 된다는 것입니다.

Spring batch 국내 자료링크, 그리고 KSUG 연재

(수정이력: 2008.01.19 - 스프링배치 관련 링크들은 http://www.ksug.org/94로 옮기고, 최근 자료를 포함시켰습니다.)

7월31일에 Spring Batch 1.1.1이 나왔군요. 버그수정 외에 특별한 기능의 update 사안은 없기는 합니다만, 진도가 빠른 느낌입니다. ( http://www.springframework.org/node/722 )

Spring batch에 대한 국내 자료도 점점 많이 생겨나고 있습니다. KSUG(한국 스프링사용자모임)에서 만난 분들 중 Spring batch에 관심을 가지고 계신 분들이 몇 분 계셔서, 새로운 자료들도 속속 올라올 것 같군요. 그런 분들과 정보를 주고 받을 수 있으니, 포럼(http://forum.ksug.org) 개설 등 최근 KSUG의 변화에 따른 수혜를 제가 많이 받고 있다고 느껴집니다.

스프링배치 국내 자료 모음 - http://www.ksug.org/94

그리고 제가 마이크로소프트웨어지에 연재하고 있는 글도 시일이 좀 지난 기사에 한해서 담당기자분의 허락을 얻어 KSUG의 블로그에 올리고 있습니다.

이미 잡지에 나왔던 내용이지만, 인쇄되어 나올 원고를 쓸 때와는 달리 링크와 인용을 더 편하게 할 수 있기에 웹사이트에 올릴 수 있는 기회가 온 것을 반가워하고 있습니다. 연재용 원고를 쓸 때는 참조자료의 URL이 들어가면 긴 주소를 독자들이 직접 쳐서 웹사이트에 들어가 볼까 하는 의구심도 들고, 주소가 차지하는 지면 공간도 많고 해서 사소한 것까지 웹사이트 주소를 쓰기가 망설여 지더군요. 웹에 올릴 때는 API문서 등 사소한 링크도 다 넣을 수 있어서 속이 후련했습니다. 그리고 티스토리에서 제공되는 '미주’를 다는 기능도 정말 마음에 듭니다. 개인블로그도 이글루스에서 이사가고 싶은 마음이 생길 정도입니다;

위의 연재 내용을 포함한 스프링에 대한 모든 질문과 토론은 http://forum.ksug.org을 통해서 하시면 됩니다~!

Apache commons DbUtils 활용하기

개발을 하다보면 간단한 화면 1~2개만 독립적으로 돌아가는 웹어플리케이션을 만들 때도 있습니다. 예를 들면 로그조회 프로그램 같은 것들이죠.

그런 곳에는 Hibernate나 iBatis를 쓰기에는 너무 거창하다는 느낌이 들기도 합니다. 그렇다고 JDBC로 날코딩하기는 성가실때, 이럴 때는 Apache Commons DbUtils를 써볼만 합니다.

JDBC에서 Connection, Statement,ResultSet의 close 글에 나온 것처럼 Connection을 닫는 번거로운 처리가 DbUtils.closeQuietly(con);로 끝나는 것만 해도 상당히 편합니다.

아래 예제는 DBUtils + JSTL로 간단한 조회화면을 만들어 본 것입니다.

(Connection은 실무에서는 DataSource를 통해 얻어와야합니다.)

<%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>
<%@ page import = "java.sql.*" %>
<%@ page import = "java.util.Properties" %>
<%@ page import = "org.apache.commons.dbutils.DbUtils" %>
<%@ page import = "org.apache.commons.dbutils.QueryRunner" %>
<%@ page import = " org.apache.commons.dbutils.handlers.MapListHandler" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%!
  private static final String SELECT_STMT =
                  "SELECT id, name, email, cell_phone_number FROM quiz_user";
%>
<%
 String url = "jdbc:hsqldb:hsql://localhost/sampledb";
 Properties prop = new Properties();
 prop.put("user","sa");
 prop.put("password","");
 Connection con =  null;
 try\{
  Class.forName ("org.hsqldb.jdbcDriver");
  con = DriverManager.getConnection(url,prop);
        QueryRunner runner = new QueryRunner();
        Object resultList = runner.query(con,SELECT_STMT, new MapListHandler());
        request.setAttribute("list",resultList);
   } catch (SQLException ex) \{

      throw new RuntimeException(ex);
   } finally \{
     DbUtils.closeQuietly(con);
   }
 %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
<title>사용자</title>
</head>
<body>
  <h1>사용자  조회</h1>
  <h2>사용자  목록</h2>
  <table class="list">
   <tr>
    <th>id</th><th>이름</th><th>전화번호</th> <th>이메일</th>
 </tr>
    <c:forEach var="item" items="$\{list}" varStatus="status">
 <tr>
  <td>$\{item.id}</td>
  <td align="center">$\{item.name}</td>
  <td>$\{item.cell_phone_number}</td>
  <td align="center">$\{item.email}</td>
 </tr>
 </c:forEach>
  </table>
</body>
</html>

몇 년전에 DbUtils와 비슷한 클래스를 만든 적이 있었는데, 그때도 좀 찾아볼 걸 그랬나봅니다. 그러고 보면 저도 Apache Commons에 이미 있는 것을 많이도 만들어본 삽질의 시간들을 겪었었습니다. 신입 때 commons beanutils하고 commons io에 포함된 것 비슷한 유틸리티 만들어 놓고 혼자서 뿌듯해 했었죠 -_-;