<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>빛을 담고 세상 넓히기 &#187; Java</title>
	<atom:link href="http://fantazic.com/archives/category/%ec%8b%9c%ea%b3%a8%ec%83%9d%ed%99%a9/feed" rel="self" type="application/rss+xml" />
	<link>http://fantazic.com</link>
	<description>마음의 빛으로 넓은 세상을 비추고 싶다. JAVA, 고양이, 사진들이 있는 곳.</description>
	<lastBuildDate>Tue, 27 Jul 2010 04:38:45 +0000</lastBuildDate>
	
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Java에서 한글을 정규표현식으로 찾기</title>
		<link>http://fantazic.com/archives/513</link>
		<comments>http://fantazic.com/archives/513#comments</comments>
		<pubDate>Tue, 20 Jul 2010 01:38:33 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[정규표현식]]></category>
		<category><![CDATA[한글]]></category>

		<guid isPermaLink="false">http://fantazic.com/?p=513</guid>
		<description><![CDATA[문서를 읽어서 한글을 제외한 나머지 문자를 제거하기 위해 정규표현식을 사용했다. 이 과정에서 이해할 수 없는 경험들을 하게 되어 이곳에 정리한다.
처음에는  [^가-힣ㄱ-ㅎㅏ-ㅣ]  형태로 한글을 범위로 해서 찾았는데, 이렇게 하니 윈도우 기반 로컬 머신에서는 잘 동작하던 모듈이 linux 머신에서는 동작하지 않았다. 다른점을 살펴보니 linux 머신은 utf-8 기반이였고, 한글 정규표현식이 다르게 동작하는 것으로 보였다.
그래서 현재는 [^\\u3131-\\u318E\\uAC00-\\uD7A3] 와 같이 [...]]]></description>
			<content:encoded><![CDATA[<p>문서를 읽어서 한글을 제외한 나머지 문자를 제거하기 위해 정규표현식을 사용했다. 이 과정에서 이해할 수 없는 경험들을 하게 되어 이곳에 정리한다.</p>
<p>처음에는  [^가-힣ㄱ-ㅎㅏ-ㅣ]  형태로 한글을 범위로 해서 찾았는데, 이렇게 하니 윈도우 기반 로컬 머신에서는 잘 동작하던 모듈이 linux 머신에서는 동작하지 않았다. 다른점을 살펴보니 linux 머신은 utf-8 기반이였고, 한글 정규표현식이 다르게 동작하는 것으로 보였다.</p>
<p>그래서 현재는 [^\\u3131-\\u318E\\uAC00-\\uD7A3] 와 같이 유니코드로 범위를 표시해서 한글 이외의 문자를 제거하고 있다. Java가 내부적으로 UTF-16을 사용하는 것으로 알고 있는데 정규표현식에서 서로 다르게 동작할 수 있다는 사실을 처음 알게 됐고, 이렇게 해결하긴 했지만 동작 원리를 정확히 이해하지는 못하고 있다.</p>
<p>(?u)[^가-힣ㄱ-ㅎㅏ-ㅣ] 이렇게 유티코드 옵션을 주고 실행해도 정상 동작하지 않는다. <a href="http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=f738da6fb66a12421f628d5423?bug_id=6487160">찾아보니 UNICODE_CASE 일 때 범위 표현은 사용할 수 없다고 한다.</a></p>
<p>또 하나 고민은 (&lt;/?[bB][rR][ ]*/?&gt;|&lt;/?[pP]/?&gt;|&lt;/?[dD][iI][vV]/?&gt;|&lt;/?[lL][iI]/?&gt;|&lt;/?[tT][dD]/?&gt;) 와 같은 형태로 html 태그 중에서 단락을 구분지어 주는 태그를 약속된 구분자로 치환하려는데 이때 2가지 문제가 발생한다.</p>
<p>첫번째는 분석하려는 본문이 길 때 치환에 시간이 많이 걸린다는 것이고, 또 하나는 가끔 StackOverflowError가 발생한다<a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6337993">. 검색을 해보니 복잡한 정규표현식이나 분석하려는 대상이 길 때 발생할 수 있고 해결되지 않은 문제</a>라고 한다. 정규표현식을 좀 더 명확하고 단순하게  개선하면 에러 발생을 줄일 수 있다는데 어떻게 하면 될까?</p>
<p>우선은 (?i)(&lt;/?br */?&gt;|&lt;/?p/?&gt;|&lt;/?div/?&gt;|&lt;/?li/?&gt;|&lt;/?td/?&gt;) 대소문자 구분을 하지 않게 하고 필요 없는 표현을 제거했다. 실행 속도는 거의 변화가 없고 StackOverflowError가 줄어들지는 지켜봐야 한다.</p>
<p>(회사 동료분의 힌트로 다시 한번 수정. 현재는 (?i)&lt;/?(br *|p|div|li|td)/?&gt; 형태로 더 간단해졌다.)</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/513/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>톰캣 한글 인코딩 문제와 관련된 코드들</title>
		<link>http://fantazic.com/archives/255</link>
		<comments>http://fantazic.com/archives/255#comments</comments>
		<pubDate>Fri, 10 Jul 2009 06:03:17 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[인코딩]]></category>
		<category><![CDATA[톰캣]]></category>
		<category><![CDATA[한글]]></category>

		<guid isPermaLink="false">http://fantazic.com/?p=255</guid>
		<description><![CDATA[Java로 6년째 개발을 하면서 한글 문제는 더 이상 새로운 것이 없겠지 생각을 하지만 매번 알 수 없는 상황에 부딪히게 된다.
EUC-KR 기반의 아파치-톰캣 시스템에서 브라우져 주소창의 한글을 처리하려고 할 때 다음과 같은 문제가 발생했다.
주소창에 &#8216;http://v.daum.net/search?q=한글&#8217;을 직접 입력하는 경우 크게 3가지 경우가 발생했다.
1) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;EUC-KR&#8217;로 URLEncoding 되서 넘어오는 경우
2) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;UTF-8&#8242;로 URLEncoding 되서 [...]]]></description>
			<content:encoded><![CDATA[<p>Java로 6년째 개발을 하면서 한글 문제는 더 이상 새로운 것이 없겠지 생각을 하지만 매번 알 수 없는 상황에 부딪히게 된다.</p>
<p>EUC-KR 기반의 아파치-톰캣 시스템에서 브라우져 주소창의 한글을 처리하려고 할 때 다음과 같은 문제가 발생했다.</p>
<p>주소창에 &#8216;http://v.daum.net/search?q=한글&#8217;을 직접 입력하는 경우 크게 3가지 경우가 발생했다.</p>
<p>1) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;EUC-KR&#8217;로 URLEncoding 되서 넘어오는 경우<br />
2) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;UTF-8&#8242;로 URLEncoding 되서 넘어오는 경우<br />
3) &#8216;한글&#8217;이 bytes로 변환되서 넘어오는 경우</p>
<p>이와 유사한 문제가 아파치 rewrite module을 사용할 때도 발생했다. 예를 들어 &#8216;http://v.daum.net/한글&#8217;을 &#8216;http://v.daum.net/search?q=한글&#8217;로 재작성하는 경우 &#8216;한글&#8217;이 bytes로 변환된다.</p>
<p>이에 대한 해결방법은 여러가지가 있을 수 있는데, 다음과 같은 아이디어를 기반으로 문제를 해결했다.</p>
<p>1. URLEncoding된 문자열의 헥사값을 bytes로 변환할 수 있다.<br />
2. bytes의 인코딩을 예측할 수 있다.</p>
<p>(구현된 코드는 톰캣 내부의 파라미터 처리 부분과 Daum 검색개발팀의 황재석님의 코드에 도움을 받았습니다.)</p>
<p>&#8216;getParameterSafely(request.getQueryString(), &#8220;q&#8221;);&#8217;와 같이 사용하면 됩니다. 단, 파라미터에 &#8216;%&#8217; 문자가 포함된 경우 오작동하게 됩니다.</p>
<pre>
/* public query parsing method */
public static String getParamterSafely(String queryString, String key) {
  if (StringUtils.isEmpty(queryString)) {
    return "";
  }

  try {
    String safeQueryString = urlDecodeSafely(queryString);
    return getParameter(safeQueryString, key);
  } catch (UnsupportedEncodingException e) {
    //ignore
    e.printStackTrace();
  }
  return "";
}

/* URLDecoding 수행 */
private static String urlDecodeSafely(String queryString)
  throws UnsupportedEncodingException {
  if (StringUtils.isEmpty(queryString))
    return "";
  byte[] src = queryString.getBytes("ISO-8859-1");
  byte[] bytes = urlDecode(src);
  <del datetime="2009-07-17T07:26:45+00:00">String charset = guessCharset(bytes);</del>
  String charset = CharsetDetector.detect(bytes);
  return new String(bytes, charset);
}

/* 헥사코드를 bytes로 변환 */
private static byte[] urlDecode(byte[] data)
  throws UnsupportedEncodingException {
  int ix = 0;
  int ox = 0;
  while (ix < data.length) {
    byte c = data[ix++];
    switch ((char) c) {
      case '+':
        data[ox++] = (byte) ' ';
        break;
      case '%':
        data[ox++] = (byte) ((convertHexDigit(data[ix++]) << 4)
          + convertHexDigit(data[ix++]));
        break;
      default:
        data[ox++] = c;
    }
  }

  return ArrayUtils.subarray(data, 0, ox);
}

/* 헥사코드 변환 */
private static byte convertHexDigit(byte b) {
  if ((b >= '0') &#038;&#038; (b <= '9'))
    return (byte) (b - '0');
  if ((b >= 'a') &#038;&#038; (b <= 'f'))
    return (byte) (b - 'a' + 10);
  if ((b >= 'A') &#038;&#038; (b <= 'F'))
    return (byte) (b - 'A' + 10);
  return 0;
}

<del datetime="2009-07-17T07:26:45+00:00">/* 재석님의 캐릭터셋 추측하는 메소드 */
private static String guessCharset(byte[] bytes) {
  try {
    CharsetDecoder decoder = Charset.forName("MS949").newDecoder();
    ByteBuffer bb = ByteBuffer.wrap(bytes);
    decoder.decode(bb);
    bb.clear();
    return "MS949";
  } catch (Exception e) {
    return "UTF-8";
  }
}</del>

/* 쿼리 문자열에서 특정 키의 값을 가져온다. */
private static String getParameter(String safeQueryString, String key) {
  String parameters[] = safeQueryString.split("&#038;");
  if (parameters != null) {
    for (String parameter : parameters) {
      if (parameter.startsWith(key + "=")) {
        return parameter.replaceFirst(key + "=", "");
      }
    }
  }
  return "";
}
</pre>
<p><a href="http://dmlim.egloos.com/3966342">임동문님의 블로그</a>를 참고해서 캐릭터셋 판별 부분을 보완했습니다.</p>
<pre>
public class CharsetDetector {
  private static final String DEFAULT_CHARSET = "MS949";
  private static final int MS949_NORMAL = 0;
  private static final int MS949_2BYTE = 1;
  private static final int MS949_KSC_2BYTE = 2;
  private static final int UTF8_NORMAL = 0;
  private static final int UTF8_2BYTE = 1;
  private static final int UTF8_3BYTE = 2;
  private static final int UTF8_4BYTE = 3;

  public static String detect(byte[] bytes) {
    if (isUTF8(bytes))
      return "UTF-8";
    if (isMS949(bytes))
      return "MS949";
    return DEFAULT_CHARSET;
  }

  public static boolean isMS949(byte[] bytes) {
    int status = MS949_NORMAL;
    for (int ch : bytes) {
      if (status < 0)
        return false;

      switch (status) {
        case MS949_NORMAL:
          if (ch < 0x80)
            status = MS949_NORMAL;
          else if (0x81 <= ch &#038;&#038; ch <= 0xc5)
            status = MS949_2BYTE;
          else if (0xc5 < ch &#038;&#038; ch <= 0xfe)
            status = MS949_KSC_2BYTE;
          else
            status = -1;
          break;
        case MS949_2BYTE:
          if ((0x41 <= ch &#038;&#038; ch <= 0x5a) || (0x61 <= ch &#038;&#038; ch <= 0x7a)
            || (0x81 <= ch &#038;&#038; ch <= 0xfe))
            status = MS949_NORMAL;
          else
            status = -1;
          break;
        case MS949_KSC_2BYTE:
          if (0xa1 <= ch &#038;&#038; ch <= 0xfe)
            status = MS949_NORMAL;
          else
            status = -1;
          break;
        default:
          break;
      }
    }
    return true;
  }

  public static boolean isUTF8(byte[] bytes) {
    int status = UTF8_NORMAL;

    for (int ch : bytes) {
      if (status < 0)
        return false;

      switch (status) {
        case UTF8_NORMAL:
          if ((ch &#038; 0x80) == 0)
            status = UTF8_NORMAL;
          else if (((ch &#038; 0xe0) ^ 0xc0) == 0)
            status = UTF8_2BYTE;
          else if (((ch &#038; 0xf0) ^ 0xe0) == 0)
            status = UTF8_3BYTE;
          else if (((ch &#038; 0xf8) ^ 0xf0) == 0)
            status = UTF8_4BYTE;
          else
            status = -1;
          break;
        case UTF8_2BYTE:
          if (((ch &#038; 0xc0) ^ 0x80) == 0)
            status = UTF8_NORMAL;
          else
            status = -1;
          break;
        case UTF8_3BYTE:
          if (((ch &#038; 0xc0) ^ 0x80) == 0)
            status = UTF8_2BYTE;
          else
            status = -1;
          break;
        case UTF8_4BYTE:
          if (((ch &#038; 0xc0) ^ 0x80) == 0)
            status = UTF8_3BYTE;
          else
            status = -1;
          break;
        default:
          break;
      }
    }
    return true;
  }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/255/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>iBatis에서 batch 기능 활용하기</title>
		<link>http://fantazic.com/archives/228</link>
		<comments>http://fantazic.com/archives/228#comments</comments>
		<pubDate>Tue, 07 Apr 2009 00:19:40 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[batch]]></category>
		<category><![CDATA[iBatis]]></category>

		<guid isPermaLink="false">http://fantazic.com/?p=228</guid>
		<description><![CDATA[iBatis는 아래와 같은 방법으로 batch 처리가 가능하다.  iBatis 내부 코드를 확인해 본 바로는 PreparedStatement.addBatch()를 사용하고 있고, 동일한 쿼리가 반복해서 들어올 때 하나의 batch로 처리해준다.

try {
  SqlMapClient.startTransaction();
  SqlMapClient.startBatch();
  while (...) {
    SqlMapClient.insert(query, params);
  }
  SqlMapClient.executeBatch();
  SqlMapClient.commitTransaction();
} catch (Exception e) {
  log.error(e, e);
} finally {
  [...]]]></description>
			<content:encoded><![CDATA[<p>iBatis는 아래와 같은 방법으로 batch 처리가 가능하다.  iBatis 내부 코드를 확인해 본 바로는 PreparedStatement.addBatch()를 사용하고 있고, 동일한 쿼리가 반복해서 들어올 때 하나의 batch로 처리해준다.</p>
<pre>
try {
  SqlMapClient.startTransaction();
  SqlMapClient.startBatch();
  while (...) {
    SqlMapClient.insert(query, params);
  }
  SqlMapClient.executeBatch();
  SqlMapClient.commitTransaction();
} catch (Exception e) {
  log.error(e, e);
} finally {
  SqlMapClient.endTransaction();
}
</pre>
<p>이 기능을 활용해서 BatchManager를 만들어서 사용하고 있는데, 사용자의 로그인 시간을 기록하거나 게시물의 조회수를 늘리는 등 <strong>빈번하게 동일한 update가 발생하는 서비스에 사용하면 효과</strong>가 있다.</p>
<p>사용법은 기존의 서비스 코드 수정을 최소화하는 방법으로 고안했다. SqlMapClient.insert(query, params)를 BatchManager.insert(query, params)로 수정하면 된다.</p>
<p><strong>관련 코드)</strong><br />
BatchManager.java</p>
<pre>
public class BatchManager {
  private static BatchWorker worker = BatchWorker.getInstance();
  static {
    Runtime.getRuntime().addShutdownHook(new Thread() {
      public void run() {
        try {
          worker.flushAll();
          worker.stop();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }

  public static void insert(String query, Object params) {
    worker.put(new BatchQuery(INSERT, query, params));
  }

  public static void update(String query, Object params) {
    worker.put(new BatchQuery(UPDATE, query, params));
  }
}
</pre>
<p>BatchWorker.java</p>
<pre>
public class BatchWorker {
  public static final int HEARTBEAT = 1000;
  public static int MAX_WAIT = 30000;
  public static final int SIZE_OF_ONE_BATCH = 200;
  public static final int MAX_SIZE = 100;
  private long lastTime;
  private final Timer timer;
  private Vector<BatchQuery> queue;
  private static BatchWorker singletonWorker;

  private BatchWorker() {
    lastTime = System.currentTimeMillis();
    timer = new Timer(true);
    queue = new Vector<BatchQuery>();
    startWorker();
  }

  public static synchronized BatchWorker getInstance() {
    if (singletonWorker == null)
      singletonWorker = new BatchWorker();
    return singletonWorker;
  }

  private void startWorker() {
    timer.scheduleAtFixedRate(new TimerTask() {
      @Override
      public void run() {
        try {
          doBatch();
        } catch (Exception e) {
          // ignore
          e.printStackTrace();
        }
      }
    }, 0, HEARTBEAT);
  }

  private void doBatch() {
    if (System.currentTimeMillis() - lastTime > MAX_WAIT
      || queue.size() >= MAX_SIZE) {
      executeQuery();
      lastTime = System.currentTimeMillis();
    }
  }

  private synchronized void executeQuery() {
    if (queue.size() == 0)
      return;
    try {
      int cnt = 0;
      SqlMapClient.startTransaction();
      SqlMapClient.startBatch();
      while (cnt++ < SIZE_OF_ONE_BATCH) {
        if (queue.size() == 0)
          break;
        BatchQuery batchQuery = queue.remove(0);
        switch (batchQuery.type) {
          case INSERT:
            SqlMapClient.insert(batchQuery.query, batchQuery.params);
            break;
          case UPDATE:
            SqlMapClient.update(batchQuery.query, batchQuery.params);
            break;
          default:
            break;
        }
      }
      SqlMapClient.executeBatch();
      SqlMapClient.commitTransaction();
    } catch (Exception e) {
      log.error(e, e);
    } finally {
      SqlMapClient.endTransaction();
    }
  }

  public int size() {
    return queue.size();
  }

  public void put(BatchQuery query) {
    queue.add(query);
  }

  public void flushAll() {
    while (queue.size() > 0)
      executeQuery();
  }

  public void stop() {
    timer.cancel();
  }
}
</pre>
<p>BatchQuery.java</p>
<pre>
public class BatchQuery {
  public enum QueryType {
    INSERT, UPDATE
  }

  public String query;
  public Object params;
  public QueryType type;

  public BatchQuery(QueryType type, String query, Object params) {
    this.type = type;
    this.query = query;
    this.params = params;
  }
}
</pre>
<p><strong>참고)</strong></p>
<ul>
<li>Oracle10g 환경에서는 batch로 처리할 경우 쿼리 수행은 빨라지나 batch 처리마다 쿼리 파싱이 발생해서 CPU 비용은 증가하는 경우도 보였다.</li>
<li>예전에 찾아본 바로는 한번에 만건 이상도 batch 처리가 가능하다고 한다. 환경에 따라 가장 효율적인 batch 크기를 결정해야 한다.</li>
<li>batch 처리할 경우 수행속도가 빨라지는 장점이 있고 transaction lock이 적게 잡혀 DB 부담을 줄여주는 효과도 있다.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/228/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>iBatis 환경에서 DB 유닛 테스트를 위한 팁</title>
		<link>http://fantazic.com/archives/222</link>
		<comments>http://fantazic.com/archives/222#comments</comments>
		<pubDate>Tue, 24 Mar 2009 11:21:48 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[iBatis]]></category>
		<category><![CDATA[TestCase]]></category>

		<guid isPermaLink="false">http://fantazic.com/?p=222</guid>
		<description><![CDATA[최근에는 새로운 기능을 개발할 때 test case를 먼저 작성하는 경우가 많다. 특히 DB와 연동되는 기능을 개발할 경우 다양한 종류의 실수로 작업이 지연될 수 있기 때문에 unit test를 꼭 먼저 작성하고 개발을 시작한다.
하지만 일반적인 개발환경에서 테스트 데이터가 DB에 직접 쌓이게 되면 데이터가 꼬이는 경우가 발생할 수 있어서 이를 해결할 방법이 필요하다. 예를 들어 게시판에 글을 쓰고, [...]]]></description>
			<content:encoded><![CDATA[<p>최근에는 새로운 기능을 개발할 때 test case를 먼저 작성하는 경우가 많다. 특히 DB와 연동되는 기능을 개발할 경우 다양한 종류의 실수로 작업이 지연될 수 있기 때문에 unit test를 꼭 먼저 작성하고 개발을 시작한다.</p>
<p>하지만 일반적인 개발환경에서 테스트 데이터가 DB에 직접 쌓이게 되면 데이터가 꼬이는 경우가 발생할 수 있어서 이를 해결할 방법이 필요하다. 예를 들어 게시판에 글을 쓰고, 수정하고, 지우는 기능을 테스트하면서 오류로 글이 지워지지 않고 남는 경우가 있을 수 있고, 새로운 데이터를 입력하는 테스트인 경우 unique key 제한에 걸려 매번 테스트 데이터를 수정해야 하는 불편함이 따를 수 있다.</p>
<p>이 문제를 피해가기 위해 여러가지 방법을 활용할 수 있는데 Spring에서는 <a href="http://whiteship.tistory.com/170">AbstractTransactionalSpringContextTests</a>와 같은 방식으로 이를 해결하고 있다. iBatis 환경에서는 다음과 같이 JUnit4의 기능을 활용해서 DB unit test를 편하게 할 수 있다.</p>
<pre>
public class TransactionalTestCase {

  @BeforeClass
  public static void create() {
    SqlMapClient.startTransaction();
  }

  @AfterClass
  public static void destroy() {
    SqlMapClient.endTransaction();
  }

  protected ResultSet executeQuery(String sql) throws SQLException {
    Connection conn = SqlMapClient.getConnection();
    Statement st = conn.createStatement();
    return st.executeQuery(sql);
  }

  protected int executeUpdate(String sql) throws SQLException {
    Connection conn = SqlMapClient.getConnection();
    Statement st = conn.createStatement();
    return st.executeUpdate(sql);
  }

  protected int countTableRows(String tableName) throws SQLException {
    int count = 0;
    ResultSet rs = executeQuery("select count(*) from " + tableName);
    while (rs.next()) {
      count = rs.getInt(1);
    }
    return count;
  }

  protected void deleteTable(String tableName) throws SQLException {
    executeUpdate("delete from " + tableName);
  }
}
</pre>
<p>기본적으로 클래스의 시작과 끝에 Transaction을 걸어주고 있고, 데이터 초기화 기능 및 간단한 테이블 조회 기능을 추가했다. 각자의 환경에 따라 세부적인 코드는 변할 수 있겠지만 이 코드를 바탕으로 쉽게 DB unit test를 할 수 있는 기회가 될 수 있기를 바란다.</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/222/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Spring Dependency Injection 정리</title>
		<link>http://fantazic.com/archives/117</link>
		<comments>http://fantazic.com/archives/117#comments</comments>
		<pubDate>Tue, 25 Mar 2008 06:13:06 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[DI]]></category>
		<category><![CDATA[Spring]]></category>

		<guid isPermaLink="false">http://fantazic.com/archives/117</guid>
		<description><![CDATA[오늘부터 아침 스터디를 시작했다. 업무 시작 1시간 전에 나와서, 30분간 각자 책을 읽거나 인터넷에서 관심있는 아티클을 읽고 나머지 30분간 각자가 배운 내용을 공유하는 방식으로 진행하기로 했다.
오늘 공부할 주제로 &#8220;Spring Dependency Injection &#038; Java 5&#8243; 아티클을 선택했다. 최근 Spring 관련 정보를 너무 멀리 한 것 같아서 SpringSource 팀 블로그에서 최근 글 리스트를 살펴봤고, 이 글에 끌려서 [...]]]></description>
			<content:encoded><![CDATA[<p>오늘부터 아침 스터디를 시작했다. 업무 시작 1시간 전에 나와서, 30분간 각자 책을 읽거나 인터넷에서 관심있는 아티클을 읽고 나머지 30분간 각자가 배운 내용을 공유하는 방식으로 진행하기로 했다.</p>
<p>오늘 공부할 주제로 <a href="http://blog.springsource.com/main/2008/03/18/spring-dependency-injection-java-5-including-slides-and-code/">&#8220;Spring Dependency Injection &#038; Java 5&#8243;</a> 아티클을 선택했다. 최근 Spring 관련 정보를 너무 멀리 한 것 같아서 <a href="http://blog.springsource.com/main/">SpringSource 팀 블로그</a>에서 최근 글 리스트를 살펴봤고, 이 글에 끌려서 읽기 시작했다.</p>
<p>간단히 내용을 요약하자면, Spring에서 DI를 구현할 수 있는 3가지 방법에 대해 설명이 되어 있고 각각의 장단점에 대해 잘 설명이 되어 있다.</p>
<p>가장 기본적이면서 많이 쓰이고 있는 방식은 XML 설정 파일을 통해 의존성을 설정해 주는 방식이다. <strong>XML 방식은 의존성 설정 파일을 한 곳에 모아서 관리할 수 있기 때문에 코드와 설정이 분리되어 있고, 중앙에서 전체 구조를 파악하기 좋은 장점이 있으나 Type Safety 체크가 어렵고 리팩토링 친화적이지 않은 단점 때문에 다른 대안을 필요로 한다.</strong></p>
<p>XML 방식에 대한 대안으로 annotation 기반으로 코드에 의존성을 선언할 수 있는 방식이 Spring 2.5에서 소개되었다. <strong>코드에 선언된 @Autowired annotation과 @Qualifier annotation을 사용해서 의존성을 삽입할 수 있다.</strong> 이 방식은 코드와 설정 파일이 한 곳에 있어서 의존성 파악이 쉬운 장점이 있지만 반대로 의존성 설정이 코드에 흩어져 있어서 전체적인 구조 파악을 방해할 수도 있다. XML 방식보다는 Type Safety를 더 정확히 보장해 주지만 복잡한 설정은 XML을 추가로 사용해야 하는 단점이 있다.</p>
<pre>
// In file OrderServiceImpl.java:

public class OrderServiceImpl implements OrderService {
    private OrderRepository orderRepository;

    @Autowired
    public JdbcOrderServiceImpl(OrderRepository orderRepo) {
        this.orderRepository = orderRepo;
    }

    // ...
}

// In file JdbcOrderRepositoryImpl.java:

public class JdbcOrderRepositoryImpl implements OrderRepository {

    @Autowired
    @Qualifier("myDataSource")
    private DataSource orderDataSource;

    // ...
}
</pre>
<p>code from <a href="http://www.theserverside.com/tt/articles/article.tss?l=IntrotoSpring25">&#8216;Introduction to the Spring Framework 2.5&#8242;</a> by Rod Johnson</p>
<p>마지막으로 현재 개발 중에 있는 JavaConfig 방식이 있다. <strong>XML로 설정하던 설정 파일을 Plain Java 코드로 구현하는 방식으로 완벽하게 Type Safety를 보장해 주며 코드와 설정 파일을 분리시켜 주는 장점을 그대로 가지고 있는 방식이다.</strong> RubyOnRails에서 설정 파일을 Ruby 파일로 구현하는 것과 같은 방식이라 할 수 있다. </p>
<pre>
@Configuration
public class MyConfig {
   @Bean
   public Person rod() {
      return new Person("Rod Johnson");
   } 

   @Bean(scope = Scope.PROTOTYPE)
   public Book book() {
      Book book = new Book("Expert One-on-One J2EE Design and Development");
      book.setAuthor(rod());  // rod() method is actually a bean reference !
      return book;
   }
}
</pre>
<p>code from <a href="http://blog.springsource.com/main/2006/11/28/a-java-configuration-option-for-spring/">&#8216;A Java configuration option for Spring&#8217;</a> by Rod Johnson&#8217;</p>
<p><strong>결론적으로 세 가지 방식을 통해 DI를 구현할 수 있기 때문에 개발자가 상황에 맞게 적절한 DI 구현 방식을 선택할 수 있는 기회가 생겼다고 할 수 있다.</strong> 기존의 XML 방식은 복잡한 구조의 대형 프로젝트에서 여전히 유용하게 사용될 수 있을 것이며, 간단한 프로젝트에서는 annotation 방식을 통해 번거로운 XML 작업 없이 프로젝트 개발이 가능해졌다. 그리고 앞으로 발표된 JavaConfig 방식은 IDE와 연동해서 리팩토링에 친화적이며 Type Safety를 보장해 주는 이점이 극대화될 수 있을 것 같다.</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/117/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>언제 코드에 주석을 달아야 할까?</title>
		<link>http://fantazic.com/archives/113</link>
		<comments>http://fantazic.com/archives/113#comments</comments>
		<pubDate>Fri, 15 Feb 2008 06:13:08 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[원칙]]></category>
		<category><![CDATA[주석]]></category>

		<guid isPermaLink="false">http://fantazic.com/archives/113</guid>
		<description><![CDATA[코드에 주석을 어떻게 달아야 하는지 질문이 있어서 나만의 원칙을 정리해 보았다.

fantazic 코드 주석 원칙

주석보다는 테스트 케이스를 작성하자.
주석보다는 의미있는 이름을 사용하자.
필요하다면, 클래스와 메소드의 역할에 대한 주석을 남기자.
API를 작성하는 경우, 주석을 충분히 자세하게 남기자.


최근에는 코드를 작성하는 시간이 줄어들었다. 실질적인 개발을 하고 싶지만, 기술 지원, 코칭, 설계 등 코드와는 동떨어진 업무에 더 많은 시간을 사용하고 있다.
그래서 배우고 생각한 [...]]]></description>
			<content:encoded><![CDATA[<p>코드에 주석을 어떻게 달아야 하는지 질문이 있어서 나만의 원칙을 정리해 보았다.</p>
<blockquote><p>
<strong>fantazic 코드 주석 원칙</strong></p>
<ol>
<li>주석보다는 테스트 케이스를 작성하자.</li>
<li>주석보다는 의미있는 이름을 사용하자.</li>
<li>필요하다면, 클래스와 메소드의 역할에 대한 주석을 남기자.</li>
<li>API를 작성하는 경우, 주석을 충분히 자세하게 남기자.</li>
</ol>
</blockquote>
<p>최근에는 코드를 작성하는 시간이 줄어들었다. 실질적인 개발을 하고 싶지만, 기술 지원, 코칭, 설계 등 코드와는 동떨어진 업무에 더 많은 시간을 사용하고 있다.</p>
<p>그래서 배우고 생각한 것이 있어도 바로 실천으로 옮기지 못하는 경우가 많다. &#8216;주석을 다는 원칙&#8217;도 프로젝트에 직접 적용해 보면서 개선해 나가야 하는데 원칙만 있을 뿐 경험이 따라주지 못하고 있다.</p>
<p>최근 작성한 코드를 돌이켜 보니, 주석을 너무 무시했던 것 같다. 공통 모듈을 개발하면서 주석이 부족했으니 사용하는 개발자가 쉽게 가져다 쓰기 힘들었을 것이다. 무엇이든지 한번에 잘 되는 것은 없기 때문에 빠르게 자주 개선시켜 나가야겠다.</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/113/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>JCO 컨퍼런스 후기</title>
		<link>http://fantazic.com/archives/103</link>
		<comments>http://fantazic.com/archives/103#comments</comments>
		<pubDate>Sun, 14 Oct 2007 08:33:54 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JCO]]></category>
		<category><![CDATA[오픈소스]]></category>

		<guid isPermaLink="false">http://fantazic.com/archives/103</guid>
		<description><![CDATA[올해 초에 진행됐던 Java 개발자 컨퍼런스에 이어서 이번에도 오픈소스를 주제로 컨퍼런스가 열렸다. 오픈소스 프로젝트 개발 경험, 오픈소스 제품 활용 방법, 오픈소스 지원 정책, 라이센스 문제 등 다양한 주제의 발표가 있었다.
가장 중점적으로 다뤄진 부분은 아무래도 Java 자체를 오픈소스화 하기로 결정한 이후 변화된 부분과 앞으로의 발전 방향에 관한 이야기였다. 이전에는 Sun이 중심이 되서 스펙을 정하고 Java를 만들었다면 [...]]]></description>
			<content:encoded><![CDATA[<p>올해 초에 진행됐던 Java 개발자 컨퍼런스에 이어서 이번에도 오픈소스를 주제로 컨퍼런스가 열렸다. 오픈소스 프로젝트 개발 경험, 오픈소스 제품 활용 방법, 오픈소스 지원 정책, 라이센스 문제 등 다양한 주제의 발표가 있었다.</p>
<p>가장 중점적으로 다뤄진 부분은 아무래도 Java 자체를 오픈소스화 하기로 결정한 이후 변화된 부분과 앞으로의 발전 방향에 관한 이야기였다. 이전에는 Sun이 중심이 되서 스펙을 정하고 Java를 만들었다면 앞으로는 오픈소스 커뮤니티를 중심으로 Java가 발전될 것이다. 특히 <a href="http://openjdk.java.net">OpenJDK</a> 등의 커뮤니티에 직접 참가해서 자신에게 필요한 기능과 스펙을 만들어갈 필요가 커졌다. Sun에서 운영하는 오픈소스 프로젝트가 <a href="https://glassfish.dev.java.net">glassfish</a>를 비롯해서 여러 가지가 있기 때문에 개인적으로 관심을 갖고 참여할 생각이다.</p>
<p><a href="http://channy.creation.net">Channy</a>님이 발표한 모질자 그룹에 관한 이야기도 많은 도움이 됐다. FireFox 이전에 많은 실패가 있었지만 현재는 기술과 개념적인 성공 이후로 개발 커뮤니티 자체가 많이 발전한 상황이다. 특히 사용자들을 참여시키는 구전 마케팅과 javascript, css, xul(XML)만으로 개발되는 front-end 제품군은 정말 인상적이였다. 특히 사용자가 직접 플러그인을 제작할 수 있는 환경과 이 개발 방식이 Desktop Application에도 그대로 적용될 수 있기 때문에 활용성이 매우 컸고, 상용 프로젝트가 경제적 이유로 제품이 개발되고 개발이 중단된다면 오픈소스 프로젝트은 커뮤니티의 힘으로 지속적으로 새로운 제품을 만들어내고 진화해가기 때문에 매우 매력적이였다. (개인적으로 Channy님이 다음을 대표할 때보다 모질라를 대표할 때 편하게 느껴진다.)</p>
<p>그리고 <a href="http://blog.gleamynode.net">이희승</a>님이 발표한 JBoss 서버와 Remoting 프로젝트 관련 정보는 좀 더 넓은 세상에 대해 생각해볼 기회를 주었다. 사실 구글에 지원했다 실패한 후 새로운 도전을 못하고 있었는데 더 폭넓게 세상을 볼 수 있다면 여전히 기회는 무궁무진하다는 것을 알게됐다. 국내에서의 레퍼런스는 큰 도움이 안되겠지만 앞으로 좀 더 적극적으로 내 자신의 모습을 발전시켜 간다면 내가 원하고 좋아하는 일을 찾을 수 있다고 믿는다. 그리고 JBoss 내부 통신에 MINA 프로젝트가 적극적으로 사용된다고 하니 오픈소스의 힘을 다시 한번 느낄 수 있었다. (어제는 &#8216;Java work at home&#8217;으로 구글에서 채용 정보를 검색해 보았다. ㅋㅋ)</p>
<p>마지막으로 오픈소스에 관한 주제 속에서도 다음과 NHN이라는 포털 라이벌의 개발 환경이 각자의 개성을 발휘했던 것 같다. 사실 <a href="http://javajigi.net">박재성</a>님이 블로그 개발팀에 도입하고 있는 Agile 개발 문화가 NHN 전체의 모습은 아니지만 개발자가 바꿔갈 수 있는 NHN의 자발적인 힘을 충분히 보여준 것 같고, <a href="http://www.likejazz.com">박상길</a>님의 발표한 역동적이고 매우 적극적인 다음의 개발자 문화 또한 상당히 인상적이였다. 구글과 MS와 같이 다음과 NHN이 앞으로도 선의의 경쟁을 통해 개발자의 천국으로 거듭날 수 있는 라이벌 관계로 남기를 바란다. (특히 박재성님 발표에 나온 것 중 몇 가지는 바로 실에 적용해볼까 한다.)</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/103/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>JCO 오픈소스 컨퍼런스 참가 예정</title>
		<link>http://fantazic.com/archives/99</link>
		<comments>http://fantazic.com/archives/99#comments</comments>
		<pubDate>Sat, 29 Sep 2007 15:05:22 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JCO]]></category>

		<guid isPermaLink="false">http://fantazic.com/archives/99</guid>
		<description><![CDATA[10월 13일에 열리는 JCO 오픈소스 컨퍼런스에 참가할 예정이다.
원래는 계획에 없었는데 &#8220;Integration of Open Source Components into Large Software Systems&#8221; OOPSLA 워크샵에 참가할 예정이기 때문에 이번 컨퍼런스가 많은 도움이 될 것이라 생각되서 참가하기로 결정했다.
매번 Paper 제출 없이 그냥 맨몸으로 워크샵에 참가하고 있는데 적어도 사전 준비라도 철저히 하고 참가해야 워크샵 취지에 맞을 것 같다. 적어도 작년처럼 갑작스럽게 [...]]]></description>
			<content:encoded><![CDATA[<p>10월 13일에 열리는 <a href="http://jco.or.kr/c?mc=conferenceOpenSource&#038;sc=summary">JCO 오픈소스 컨퍼런스</a>에 참가할 예정이다.</p>
<p>원래는 계획에 없었는데 &#8220;<a href="http://www.oopsla.org/oopsla2007/index.php?page=sub/&#038;id=156">Integration of Open Source Components into Large Software Systems</a>&#8221; OOPSLA 워크샵에 참가할 예정이기 때문에 이번 컨퍼런스가 많은 도움이 될 것이라 생각되서 참가하기로 결정했다.</p>
<p>매번 Paper 제출 없이 그냥 맨몸으로 워크샵에 참가하고 있는데 적어도 사전 준비라도 철저히 하고 참가해야 워크샵 취지에 맞을 것 같다. 적어도 작년처럼 갑작스럽게 가는 경우는 아니니까 충분히 준비해서 더 많은 것을 얻고 나누고 싶다.</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/99/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Java Singleton Pattern 구현</title>
		<link>http://fantazic.com/archives/92</link>
		<comments>http://fantazic.com/archives/92#comments</comments>
		<pubDate>Fri, 29 Jun 2007 08:07:35 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[singleton]]></category>
		<category><![CDATA[패턴]]></category>

		<guid isPermaLink="false">http://fantazic.com/archives/92</guid>
		<description><![CDATA[최근 singleton 패턴 구현에 대한 질문을 받은 적이 있다. singleton 패턴은 인스턴스를 하나만 생성해서 그 객체를 공유해서 사용하는 패턴이다. 일반적으로 DB Pool과 같이 전체 시스템에서 하나의 자원을 공유해서 사용할 때 이 패턴을 활용할 수 있다.
이 패턴을 구현하는 방법은 synchronized getInstance(), double-checked getInstance(), static final instance 등 다양한데, 내 경우는 Singleton 클래스를 구현하기 위해 static initializer를 [...]]]></description>
			<content:encoded><![CDATA[<p>최근 singleton 패턴 구현에 대한 질문을 받은 적이 있다. singleton 패턴은 인스턴스를 하나만 생성해서 그 객체를 공유해서 사용하는 패턴이다. 일반적으로 DB Pool과 같이 전체 시스템에서 하나의 자원을 공유해서 사용할 때 이 패턴을 활용할 수 있다.</p>
<p>이 패턴을 구현하는 방법은 synchronized getInstance(), double-checked getInstance(), static final instance 등 다양한데, 내 경우는 Singleton 클래스를 구현하기 위해 static initializer를 활용한 방법을 제안했다.</p>
<pre>
public class Singleton {
  private static Singleton _instance;

  static {
    _instance = new Singleton();
  }

  private Singleton() {}

  public static Singleton getInstance() {
    return _instance;
  }

}
</pre>
<p>이렇게 구현한 경우 클래스가 로딩되면서 객체가 생성되기 때문에 객체가 이중으로 생성될 수 없고, 가장 확실한 방법인 synchronized getInstance()에 비해서 매번 메소드 호출시에 동기화 작업을 안해줘도 되는 장점이 있다. (synchronized 키워드가 있는 경우 메소드 실행이 약 100배 정도 늦어진다고 한다.)</p>
<p>하지만 오늘 <a href="http://wiki.java.net/bin/view/Javapedia/Singleton">관련 정보</a>를 찾아보니, 글 작성자는 명백히 이 방법보다는 synchronized getInstance()가 좋다고 한다.</p>
<p>getInstance()에서 객체를 생성할 때 장점은 lazy initialization(객체가 사용될 때까지 객체 생성을 연기)이 가능하다는 점과 내부 구현을 Pooling 등으로 변경할 때 쉽게 변경이 가능하다는 것이다. (이 부분은 static initializer도 가능하다.)</p>
<p>다만 Singleton 패턴을 사용해도 ClassLoader 마다 개별적으로 인스턴스가 생성되기 때문에 이 부분은 주의를 기울여야 한다. 특히 Servlet Container는 여러개의 ClassLoader를 사용할 수 있으니 이 점을 인지하고 있어야 한다.</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/92/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Effective Java Reloaded</title>
		<link>http://fantazic.com/archives/90</link>
		<comments>http://fantazic.com/archives/90#comments</comments>
		<pubDate>Mon, 18 Jun 2007 06:59:19 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Effective Java]]></category>

		<guid isPermaLink="false">http://fantazic.com/archives/90</guid>
		<description><![CDATA[최근에 읽은 책 중에서 Java에 대한 통찰력을 키워준 책으로는 Joshua Bloch의 &#8216;Effective Java&#8217;을 꼽을 수 있다. 특히 공통 모듈이나 API를 개발한다면 꼭 읽어볼 가치가 있다.
이 책을 통해 다양한 Java 설계 기법과 기본 Object 사용법에 대한 지식들을 얻을 수 있는데, Java 1.4 기반이여서 Java5.0에서 새롭게 소개된 다양한 기법에 대한 설명이 없는 점이 아쉬웠다.
그런데 이번 JavaOne 2007 [...]]]></description>
			<content:encoded><![CDATA[<p>최근에 읽은 책 중에서 Java에 대한 통찰력을 키워준 책으로는 Joshua Bloch의 &#8216;Effective Java&#8217;을 꼽을 수 있다. 특히 공통 모듈이나 API를 개발한다면 꼭 읽어볼 가치가 있다.</p>
<p>이 책을 통해 다양한 Java 설계 기법과 기본 Object 사용법에 대한 지식들을 얻을 수 있는데, Java 1.4 기반이여서 Java5.0에서 새롭게 소개된 다양한 기법에 대한 설명이 없는 점이 아쉬웠다.</p>
<p>그런데 이번 JavaOne 2007 &#8216;<a href="http://developers.sun.com/learning/javaoneonline/2007/pdf/TS-2689.pdf">Effective Java Reloaded: This Time it&#8217;s for Real</a>&#8216; 발표를 통해 몇 가지 추가된 내용이 소개 됐다. 이 발표에는 Generic을 비롯한 Builder Pattern 등 많은 내용은 아니지만 알찬 정보가 담겨 있다. (Builder chain은 <a href="/archives/75">JMock</a>에서와 같이 앞으로 자주 사용되는 패턴이 될 것 같다.)</p>
<p>이 외에도 재미 있는 발표가 많은데 &#8216;<a href="http://developers.sun.com/learning/javaoneonline/2007/pdf/TS-2707.pdf">Java Puzzlers</a>&#8216;는 좌절만 안겨줬다. 역시 Java는 어렵다. Java에 자신이 있다면 한 번 도전해 보기를&#8230;</p>
<p>그런데 Bloch 소속을 보니 Google로 되어 있다. 언제 Sun에서 Google로 갔을까? Bloch는 같은 내용으로 정말 많은 곳에서 발표를 하는 것 같다. &#8216;<a href="http://www.infoq.com/presentations/effective-api-design">How to Design a Good API &#038; Why it Matters</a>&#8216;는 작년 <a href="/archives/53">OOPSLA</a>에서도 들었는데, &#8216;Effective Java Reloaded&#8217;도 내용만 조금씩 바꿔가면서 이런 저런 컨퍼런스에서 발표하고 있는 듯.</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/90/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
