레이블이 웹서비스인 게시물을 표시합니다. 모든 게시물 표시
레이블이 웹서비스인 게시물을 표시합니다. 모든 게시물 표시

2005년 2월 13일 일요일

[펌] Log4j 사용법...

출처:http://network.hanbitbook.co.kr/view_news.htm?serial=565

 

Log4J

Log4J 의 문서에도 있듯이, log4j 객체를 생성해서 main 메소드가 있는 클래스에 public static으로 선언해 놓고 사용하는 것이 가장 보편적이다. 우선적으로 log4j에는 기본적인 3가지 Logger, Appender, Layout 클래스가 있는데 각 클래스들의 연관관계는 아래와 같이 간단하게 이야기할 수 있다.

"Logger 는 Appender 가 지정한 곳에 Layout 형식으로 로그를 기록한다"
Logger

Log4j 패키지의 핵심클래스이다. 설정을 제외한 대부분의 로깅작업을 수행한다. 로깅작업은 Category에 있는 debug, info, warn, error, fatal의 다섯 가지 메소드를 이용해서 수행할 수 있다. Log4j의 최신문서를 보면 Logger의 parent class인 Category는 deprecated 되었고 Logger 클래스로 대치되었으며 호환성을 위해 2003년 중반까지는 API에서 제거하지 않는다고 되어 있으나 앞으로 없어질 클래스이므로 Category를 사용하지 말고 Logger 클래스의 메소드들을 사용하기 바란다. (이전에 나온, 혹은 최근에 나온 몇몇 how-to 문서들을 보면 여전히 Category를 쓰는 코드를 볼 수 있는데 빠른 시일 내에 모두 수정되어야 할 것이다)

로그레벨 지정하기

앞에서도 언급했듯이 log4j에서는 기본적으로 debug, info, warn, error, fatal의 다섯 가지 로그레벨이 있다. 각각은 메소드 debug(), info(), warn(), error(), fatal()라는 5가지 메소드를 이용해서 로그를 남길 수 있다. 다만 이때 Logger의 setLevel에서 지정된 로그레벨이 있다면 지정된 로그레벨 이하의 로깅이벤트는 무시된다. 따라서 로그도 남지 않는다. 즉, 아래와 같이
logger.setLevel(Level.INFO); 
코드내에 지정되어 있다면, 다음의 세 코드 중
logger.debug("debug 로그");logger.info("info 로그");logger.warn("warning 로그");
debug 로그는 남지 않고 info 와 warn 로그만 남는다. 자바에서는 C와 같이 전처리기의 기능이 없기 때문에 #ifdef DEBUG와 같은 형태와 같이 디버깅 때와 릴리즈 때의 디버깅코드를 각각 별도로 생성할 수가 없다. 따라서 log4j의 이러한 기능은 로그관리에 있어서 상당히 편리하다.

Appender

log4j 의 api를 보면 실로 다양한 로그방식을 지원한다. 가장 단순한 Console부터 시작해서 파일, SMTP, 기타 등등… 의 방식들을 지원하는데 이들은 log4j 객체에 Appender의 객체로서 할당된다.
  • ConsoleAppender: 콘솔화면으로 출력하기 위한 appender이다.
  • FileAppender: FileAppender는 로깅을 파일에 하고 싶을 때 사용한다.
  • RollingFileAppender: FileAppender는 지정한 파일에 로그가 계속 남으므로 한 파일의 크기가 지나치게 커질 수 있으며, 계획적인 로그관리가 불가능해진다. RollingFileAppender는 파일의 크기 또는 파일백업인덱스 등의 지정을 통해서 특정크기 이상 파일의 크기가 커지게 되면 기존파일을 백업파일로 바꾸고, 다시 처음부터 로깅을 시작한다.
  • DailyRollingFileAppender: 설정한 날짜 또는 조건에 맞춰 로깅을 수행한다. 생성자 DailyRollingFileAppender(Layout layout, String filename, String datePattern);를 이용해서 객체의 생성과 함께 log가 roll 되는 시간을 지정해줄 수도 있고 기본 생성자로 생성 후 setDatePattern()을 이용해서 지정해 줄 수도 있다. DatePattern의 몇 가지 포맷은 다음과 같다.
    '.'yyyy-MM: 매달 첫번째날에 로그파일을 변경한다.'.'yyyy-ww: 매주의 시작싱 로그파일을 변경한다.'.'yyyy-MM-dd: 매일 자정에 로그파일을 변경한다. (필자는 주로 이 옵션을 이용한다)'.'yyyy-MM-dd-a: 자정과 정오에 로그파일을 변경한다.'.'yyyy-MM-dd-HH: 매 시간의 시작마다 로그파일을 변경한다.'.'yyyy-MM-dd-HH-mm: 매분마다 로그파일을 변경한다.
    보다 자세한 내용은 아파치 api 문서를 참고하기 바란다.
Layout

로그를 어떤 포맷의 형태로 남길 것인가? 단순히 메시지 외에도 현재 로그하는 대상의 스레드명, 로그시간 등등 많은 정보를 조합할 수 있다. Layout에는 HTMLLayout, PatternLayout, SimpleLayout, XMLLayout 등이 있다. SimpleLayout과 XMLLayout 등도 많이 사용할 수 있겠지만, 아무래도 자신이 원하는 스타일의 로그메세지를 남기기 불편한 면이 있다. 필자가 자주 사용하는 레이아웃은 PatternLayout으로서, c 함수의 printf처럼 다양한 로그 메시지 조합을 만들어 낼 수가 있다.

%p : debug, info, warn, error, fatal 등의 priority 가 출력된다.
%m : debug(), info(), warn(), error(), fatal() 등의 함수로 지정한 로그내용이 출력된다.
%d : 로깅 이벤트가 발생한 시간을 기록한다. 출력포멧은 %d후의 브레이스내에 지정된 형태를 따른다. %d{HH:mm:ss, SSS}라든가 %d{yyyy MMM dd HH:mm:ss, SSS}와 같은 형태로 사용하면 된다. Java 의 SimpleDateFormat 의 형식대로 사용하면 된다.
%t : 로그이벤트가 발생된 쓰레드의 이름이 출력된다.
%% : % 표시를 출력하기 위해 사용한다.
%n : 플랫폼 종속적인 개행문자가 출력된다. \r\n 또는 \n 일것이다.


기타 자세한 내용은 log4j 홈페이지 또는 api 문서를 참고하길 바란다.

예제

다음은 log4j를 활용하는 간단한 예이다. 별도의 configuration 파일을 이용해서 log4j의 값을 세팅하는 방법도 있으나 지면 관계상 실행가능한 간단한 예제 하나로 대신할까 한다.

아래 예제는 DailyRollingFileAppender를 이용해서 분당 하나의 로깅파일을 만들어내도록 했으며, 5초마다 Hello, DEBUG와 Hello, INFO 그리고 Hello, WARN을 출력하도록 되어 있다. 그리고 화면으로도 로그결과를 확인하기 위해서 ConsoleAppender도 추가하였다. 기본적인 priority 모드는 INFO로 설정되어 있다. 각각 어떤 값들이 로그에 남고 DailyRollingFileAppender가 어떻게 동작하는지, 그리고 각각의 파라미터들을 어떻게 세팅하였는지 등을 중심으로 코드를 보기 바란다.

Log4j 역시 다운로드받은 후 ant를 이용해서 빌드해서 사용해야 한다.
import java.io.*;import org.apache.log4j.*;public class Test {    public Test(){        initLog();    }        public void initLog(){        /////////////////////////////////////////        // log4j 설정        logger.setLevel(Level.INFO);        DailyRollingFileAppender appender = null;        Appender consoleAppender = null;                try {            String logPattern = "%r [%t] %-5p %c %x - %m %d{yyyy MMM dd HH:mm:ss, SSS} \n";            appender = new DailyRollingFileAppender( new PatternLayout(logPattern), "syslog.log", "'.'yyyy-MM-dd-HH-mm" );            consoleAppender = new ConsoleAppender(new PatternLayout(logPattern));        }        catch(IOException e){            e.printStackTrace();            System.out.println("로그설정에 문제가 있습니다. 실행권한 체크등을 해보길 바랍니다.");            System.exit(-1);        }                logger.addAppender(appender);        logger.addAppender(consoleAppender);        logger = Logger.getRootLogger();                logger.debug("LBSGateway 객체 생성");        /////////////////////////////////////////    }        public static Logger logger = Logger.getRootLogger();        public void go(){        for ( int I = 0; I < 3; I++)            new testThread().start();    }        public static void main(String[] args){        Test t = new Test();        t.go();    }}class testThread extends Thread {    public void run(){        while ( true )            {Test.logger.debug("Hello, DEBUG");            Test.logger.info("Hello, INFO");            Test.logger.warn("Hello, WARN");                        Thread.sleep(5000);        }    }}
참고자료

Log4J 프로젝트 홈페이지는 http://jakarta.apache.org/log4j이다.

[펌] log4j를 웹로직에서 쓰자.

출처 : http://eclipse.new21.org/phpBB2/viewtopic.php?t=146

 

본론으로 들어가자.
1. log4j를 웹로직에서 쓰자.

웹로직에서의 log4j를 쓰는 방법에 대해서 많이 생각을 해봤다.
좀처럼 쉽지 않았음 T.T
원리는 간단한데 말이다.
일단 log4j의 특성을 알아야 한다.
이 log를 적기 위해 별별 opensoure를 뒤져야 했다.
그 결론을 나름대로 내려보면

1. jdk1.4의 logging 기능을 함께 쓰는방법
2. apache common의 project를 이용해서 섞어쓰는 방법
3. log4j의 특성만 이용해서 사용하는 방법
위의 경우의 수중 내가 선택한방법은 3번이다.
그럼 3번을 쓰는 방법을 알아보자.



2. 환경설정

1. C:\bea\weblogic700\common\lib 아래에 log4j.jar 를 복사해 넣는다.
2. C:\bea\user_projects\mydomain 에서
set JAVA_OPTIONS=-Dlog4j.config=log4j.xml -Dweblogic.security.SSL.trustedCAKeyStore=C:\bea2\weblogic700\server\lib\cacerts 와 같이 JAVA_OPTIONS에 추가를 한다.
3. 이로서 환경 설정은 끝이다.
참고 : 만일 log4j.config파일을 사용하려면 위의 2의 Dlog4j.config=log4j.xml 을 Dlog4j.config=log4j.config 로 수정만 하면 된다.
이 예제에서는 xml를 사용하기로 했으니..xml예제를 한개 올린다.

log4j.xml
코드:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

   <appender name="cad" class="org.apache.log4j.RollingFileAppender">
      <!--<param name="File"   value="Logging4J.xml" />-->
      <param name="File"   value="Logging4J.log" />
      <param name="Append" value="true" />       
      <param name="MaxFileSize" value="1000"/>
      <param name="MaxBackupIndex" value="3"/>
     
     
      <!--<layout class="org.apache.log4j.xml.XMLLayout">
      </layout>-->
     
      <layout class="org.apache.log4j.PatternLayout">
         <param name="ConversionPattern"
                value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
      </layout>           
       
   </appender>

   <root>
      <priority value ="debug" />
      <appender-ref ref="cad"/>
   </root>

</log4j:configuration>




3. 헉 정말로 저게 끝이단 말인가?

그져 코딩에(jsp, servlet, ejb 등등...) 아래의 예처럼 코딩만하면 된다.

코드:
package org.new21.lovelazur.test;

import org.apache.log4j.Logger;

public class LoveLazurLoggerTest
{
  private static Logger logger =
    Logger.getLogger(LoveLazurLoggerTest.class.getName());

  public void logerTest()
  {
      logger.debug("test logger ... best log system log4j!!");
  }

}



4. web.xml의 수정
환경 설정이 이것 뿐이라는 말은 t.t
하나 더 있기는 하다.
우선 web.xml을 열어 이렇게 수정한다.
코드:
<?xml version="1.0" ?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>

  <!-- Loggin configuration -->
  <servlet>
    <servlet-name>Log4jInit</servlet-name>
    <servlet-class>org.new21.lovelazur.conf.Log4jInit</servlet-class>
    <init-param>
      <param-name>debug</param-name>
      <param-value>0</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

</web-app>


Twisted Evil Twisted Evil Twisted Evil
이건 뭐란말인가?
이것의 정체를 알려면 이전에 log에서 DomConfigurator를 찾아보기 바란다.
이것은 properties를 메모리에 로딩해서 범용적으로 쓰기 위함이다.
이제 이것의 servlet을 보도록 하자.

4. Config용 Servlet작성
org.new21.lovelazur.conf.Log4jInit

코드:
package org.new21.lovelazur.conf;

import javax.servlet.http.HttpServlet;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.xml.DOMConfigurator;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * <p>Controls Log4j logging level.  Implemented as a servlet so
 * that logging levels can be adjusted by redeploying the webapp.
 * Log file is determined by "log4j.config" set as a system
 * property in the server startup file.</p>
 *
 * @author Copyright (c) 2003 by BEA Systems. All Rights Reserved.
 */
public class Log4jInit extends HttpServlet {

  public void init() {

    int debug = 0;
    String value;
    String logFile;

    logFile = System.getProperty("log4j.config");
    System.out.println("############### Log4J log file ##############" + logFile);
    value = getServletConfig().getInitParameter("debug");

    try {
       debug = Integer.parseInt(value);
    } catch (Throwable t) {
        debug = 0;
    }

    if (debug >= 1) {
      SimpleDateFormat formatter = new SimpleDateFormat("MMM d, yyyy H:mm:ss a z");
      Date today = new Date();
      String output = "<"+formatter.format(today)+"> <Debug> <love lazur>";
      System.out.println(output+" love lazur app log4j prop file: "+logFile);
    }

   
    if(logFile != null) {
      if (logFile.toString().toLowerCase().endsWith(".xml")) {
         System.out.println("############### Log4J DOMConfigurator configure ##############" );
         DOMConfigurator.configure(logFile);
      } else {
         System.out.println("############### Log4JPropertyConfigurator configure ##############" );   
         PropertyConfigurator.configure(logFile);
      }
    }
   
  }
}


이것을 compile해서 WEB-INF 아래에 넣어주면 된다. Twisted Evil Twisted Evil Twisted Evil Twisted Evil Twisted Evil


5. log확인
이것을 잘 따라했다면...아래와 같은 로그를 볼수 있다.
로그 파일 이름은 log4j.xml에 기술한 Logging4J.log 이다. 그리고 이 로그는 주기적으로 backup을 받으므로 착오없길 바란다.
2004-02-26 13:31:29,578 DEBUG [ExecuteThread: '11' for queue: 'default'] test.LoveLazurLoggerTest (LoveLazurLoggerTest.java:12) - test logger ... best log system log4j!!
2004-02-26 13:31:29,609 DEBUG [ExecuteThread: '11' for queue: 'default'] test.LoveLazurLoggerTest (LoveLazurLoggerTest.java:12) - test logger ... best log system log4j!!
2004-02-26 13:31:29,640 DEBUG [ExecuteThread: '11' for queue: 'default'] test.LoveLazurLoggerTest (LoveLazurLoggerTest.java:12) - test logger ... best log system log4j!!
2004-02-26 14:05:06,656 DEBUG [ExecuteThread: '12' for queue: 'default'] test.LoveLazurLoggerTest (LoveLazurLoggerTest.java:12) - test logger ... best log system log4j!!
2004-02-26 15:10:03,453 DEBUG [ExecuteThread: '12' for queue: 'default'] calculation.BonusCalculatorBean (BonusCalculatorBean.java:21) - <<<<<<< test EJB logger ... best log system log4j!! >>>>>>>



LOG4J의 Weblogic에서의 설정을 마치고 사용법도 익혔다.

만일 tomcat및 다른 container에서 사용하려면 class path(tomcat의 경우는 xxx/yyy/lib 인데 기억안나지만 그곳에 log4j.jar를 넣어두면 된다.
그리고 web.xml및 Log4jInit.class 을 적절한 위치에 넣어두면 된다.
아마 다 될것이라고 믿는다.
jsp의 경우 로그 프로그램 코딩을 넣는게 부담스럽기도 하다.
이런경우 custom tag를 이용해 간단하게 작성, 사용하는게 바람직할것으로 본다.
차후 시간이 나면 jsp custom tag도 만들어 볼것이다.

[펌] Velocity Template Engine 시작하기

[첨 언] : 이 문서는 JavaWorld의 문서를 번역한 것입니다. 따라서 라이센스는 
아마도 JavaWorld와 원저작자가 가지고 있지 않을까 싶습니다. 편의상 경어는 
생략합니다. [번 역] : 최 호필(hops) 
E-mail : hops@bcline.com Homepage : http://hops.x-y.net 
[번역일] : 2003년 4월 30일 
[원 문] : http://www.javaworld.com/javaworld/jw-12-2001/jw-1228-velocity_p.html  
Start up Velocity Template Engine 
[참 고] : 모든 예제는 J2SE v1.4.1_01, Velocity v1.3에서 테스트되었습니다. 
 

Velocity Template Engine 시작하기

자바 기반의 웹 컨텐트 생성도구, Open Source Velocity

Summary
Velocity Template Engine은 여러분들이 어플리케이션이나 서블릿 내에서 데이터를 표현하게 만들어 준다. 주로 서블릿 기반의 웹사이트들과 같은 다이나믹한 개발에 사용되는데 Velocity의 템플릿과 자바 코드간의 깔끔한 분리는 모델 2 스타일 Model-View-Controller(MVC) 웹 개발에 대하여 이상적으로 만들어 줄 수 있다. 일반적인 템플릿 엔진들처럼 Velocity는 코드생성, XML 생성과 변환, 그리고 텍스트 스트림 프로세싱과 같은 많은 다른 목적들에도 적합하다. 이 아티클은 Velocity Template Language(VTL)을 소개하고 Velocity엔진에서 어떻게 사용하는지 예제를 제공한다. 또한 자바 서블릿 환경에서 어떻게 웹 컨텐트를 생성하는지도 포함한다.(3,000 글자정도; 2001년 12월 28일)
Geir Magnusson Jr. 작성


Velocity는 국제적으로 자발적 참여 커뮤니티에 의해 개발된 오픈소스 템플릿 도구이고, Apache Software Foundation의 Jakarta Project에 의해 주관된다. 무료로 이용가능한 소스코드를 다운받을 수 있는 웹사이트인 Jakarta Velocity Project에 속해 있는 사용자들이 질문에 대한 대답들을 해 줄 준비가 되어 있고 일반적인 템플릿 문제들에 대한 솔루션들을 제공해 줄 준비가 되어있다. Velocity는 WebMacro 프로젝트로부터 영감을 받았다.

이 아티클에서 Velocity Template Engine과 템플릿 랭귀지인 Velocity Template Language(VTL)에 대한 간략한 입문서를 제공한다. 또한 몇가지 예제를 통해서 어떤식으로 Velocity를 사용하는지 데모를 보여줄 것이다.

당연히 Hello World
아무런 설명도 필요없는 프로그래밍 관련 주제는 Hello World 예제외에는 없을 것이다. Velocity에서 사용하는 어떠한 어플리케이션이라도 두 부분을 필요로 한다. 첫번째는 템플릿으로써, 이 예제는 아래에 있다. 이 파일명은 helloworld.vm이 된다.


  Hello $name!  Welcome to Velocity!

두번째는 HelloWorld.java로 명명된 위의 템플릿에 대응되는 자바 프로그램이다.


import java.io.StringWriter;

import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;


public class HelloWorld
{
    public static void main( String[] args )
        throws Exception
    {
        /*  먼저 엔진을 얻고 초기화를 한다.  */

        VelocityEngine ve = new VelocityEngine();
        ve.init();

        /*  다음으로 템플릿을 얻어낸다.  */

        Template t = ve.getTemplate( "helloworld.vm" );

        /*  컨텍스트를 생성하고 데이터를 추가한다. */

        VelocityContext context = new VelocityContext();

        context.put("name", "World");

        /* 이제 StringWriter 안으로 템플릿을 표현한다. */

        StringWriter writer = new StringWriter();

        t.merge( context, writer );

        /* World를 보여준다. */

        System.out.println( writer.toString() );    
    }
}

이 프로그램을 컴파일하고 실행을 하면 아래의 결과를 볼 수 있다.
[역자 주]: Velocity 1.3의 경우 컴파일과 Run을 하기 위해서는 클래스패스에 velocity-dep-1.3.jar가 잡혀 있어야 한다.


  Hello World!  Welcome to Velocity!

이것은 사소한 예제이긴 하지만, Velocity templating은 어떤 것이라는 사상을 제공해 준다는 결정적인 부분을 포함하고 있다.

왜 이것을 사용해야 하는가?
일반적인 템플릿 도구들이 사용하기 쉽도록 디자인된 것과 같이 Velocity는 데이터를 formatting하고 표현할 수 있게 요구되는 어떠한 자바 어플리케이션 영역에서도 유용하게 사용된다. 여러분들은 다음과 같은 이유때문에 Velocity를 사용해야만 한다.

  • 다양한 어플리케이션 영역들에 적용된다.
  • 템플릿 디자이너에게 단순하고 명확한 신택스(syntax)를 제공한다.
  • 개발자에게 간단한 프로그래밍 모델을 제공한다.
  • 템플릿과 코드가 분리되기 때문에 서로 독립적으로 개발하고 유지보수 할 수 있다.
  • Velocity 엔진은 어떠한 자바 어플리케이션 환경, 특히 서블릿과 쉽게 통합된다.
  • Velocity는 템플릿이 컨텍스트 내에 있는 데이터 객체의 어떠한 public 메소드들에게 엑세스하는 것을 가능하게 만들어준다.

마지막 포인트는 매우 중요하다 -- 이것은 여러분들이 이미 존재하는 여러분들의 클래스를 재사용할 수 있다는 것을 의미한다. 따라서 여러분들의 템플릿에서 사용하길 원하는 객체들은 특정 방식, 예를 들어 자바빈즈나 아니면 특별한 I/O를 구현(implement) 한다던지, JSP(JavaServer Pages taglibs와 같은 lifecycle 방식으로 재구성되어질 필요가 없다. 단 하나의 필요사항은 메소드들이 public으로 선언되어야 한다는 것뿐이다. 우리가 자세하게 template language를 다룰때 이것에 대해서 좀 더 알아볼 수 있을 것이다.

Velocity의 장점 중의 한가지는 어플리케이션 내에서 기능적인 책임의 분리는 강력하게 강제화시키고 있다는 것이다. 이것은 템플릿이 어플리케이션 코드가 본질적으로 사용가능하게 만드는 객체들에 대해서 엑세스하는 것을 제한함으로써 수행된다. 이것은 Model-View-Controller(MVC) 개발에서 디자이너들이 배타적으로 데이터 표현(the view)에 촛점을 맞출 수 있도록 해 주고, 어플리케이션 프로그래머들은 어플리케이션 컨트롤(the controller)과 비즈니스 로직과 데이터 관리(the model)에만 촛점을 맞출 수 있도록 해 준다. MVC는 정교한 어플리케이션들의 개발과 지속적인 유지보수 두가지 모두를 단순화시키는 것이 잘 적용된 개발 패턴이다.

어느 곳에서 이것을 사용하는가?
Velocity는 아래와 같은 곳에서 성공적으로 사용될 수 있다.

  • 서블릿 기반의 웹 어플리케이션
  • 자바와 SQL 코드 생성(Generation)
  • XML processing과 transformation
  • RTF 파일 생성과 같은 Text processing

Velocity는 대다수 일반적으로 자바 서블릿 기반의 웹 어플리케이션 개발에 있어 JSP와 다른 렌더링 기술들과 결합 또는 대체하는 렌더링 엔진으로써 사용된다. 템플릿 syntax가 유지보수하기 쉽다는 것을 제외하고라도, Velocity의 template language는 조작하기 쉽고, 데이터를 생성하지 않고 데이터를 표현할 수 있기 때문에 웹 개발에서 사용된다. 이것은 템플릿 내에서 programming하는 것을 지양하게 만든다. 이것은 아주 좋은 것이다. 자바 코드의 비즈니스와 어플리케이션 로직이 포함되어야 할 곳에서만 존재하게 된다. J2EE(Java 2 Platform, Enterprise Edition) platform이 JSP보다 다른 output 기술들에 적합되어 있기 때문에, Velocity는 J2EE 웹 개발에 적합하다. 비록 JSP가 J2EE 스펙에 포함되어 있기는 하지만, J2EE는 JSP의 사용을 필요로 하지는 않는다.

어떻게 동작하는가?
어떠한 어플리케이션에서 하는 것처럼 Velocity 기반 어플리케이션을 생성하는데 똑같은 일반적인 프로세스를 사용한다. 이제는 위에 있는 Hello World 어플리케이션보다 좀 더 흥미있는 예제를 생각해 보자. 여러분들은 애완동물 가게를 운영하고 판매 홍보를 하기 위해 대량의 email을 생성해서 발송하길 원한다고 가정해 보자. 먼저 여러분들은 email을 다자인해야 한다. 그리고나서 그 디자인을 기반으로 해서 템플릿과 코드를 개발해야만 한다.

디자인시 고려할 문제들
디자인을 위해 3가지 항목을 고려해야 할 필요가 있다.

  • email 안에 포함되어야할 데이터는 어떤 것인가?
  • 데이터 항목은 어떤 형식을 가져야만 하는가?(예를 들어 list, Map 또는 String)
  • 무엇이 그러한 데이터 항목들을 호출하는가?

예를 들어, 각기 다르게 홍보된 가격을 가진 3가지의 애완동물을 판매하기로 결정했다고 가정해 보자. 여러분들은 각 애완동물 이름과 그 가격을 매칭시켜놓은 map을 사용하기로 결정하고 그런 다음에 list 안에 3개의 map을 모두 저장하기로 결정한다. 이 list를 petList로 호출하고, map 안의 애완동물 명을 name, 가격을 price란 이름으로 호출한다. 이제 여러분들은 관련된 데이터, 형식, 이름을 식별했기에 코드를 작성하고 템플릿을 디자인할 수 있다.

코드 작성과 템플릿 디자인
일단 여러분들이 데이터 명세에 동의한다면, Velocity는 여러분들이 코드를 작성하고 템플릿을 디자인하는 것을 병렬적으로 할 수 있도록 만들어준다. 디자이너는 템플릿 안의 nondata presentation content(image들, text 등과 같은)와 데이터를 통합시킨다. 이러한 경우 우리는 간단하게 email body를 아래처럼 작성한다.


  $petList.size() Pets on Sale!

  We are proud to offer these fine pets
  at these amazing prices.  This month only,
  choose from:

  #foreach( $pet in $petList )
    $pet.name for only $pet.price
  #end

   Call Today!

프로그래머로써 여러분들은:

  • 데이터 소스들 -- JDBC(Java Database Connectivity)를 통한 데이터베이스, 파일, 또는 그냥 계산된 어떤 값 -- 로부터 모든 데이터를 가져온다.
  • 약속된 이름들을 이용하여 컨텍스트 안으로 그 데이터를 넣는다.
  • 결과값을 만들기 위해 컨텍스를 가지고 템플릿을 렌더링한다.

여러분들은 내가 VelocityContext 클래스를 context으로 언급했던 Hello World 예제로부터 다시 생각해 볼 수 있을 것이다. java.util.Map로 형상화되었기에 컨텍스트는 템플릿이 엑세스하는 어플리케이션이나 서블릿에 의해 제공되는 데이터를 담고 있는 객체이다.

이 예제에서 우리는 우리의 데이터 소스들(여기서는 직접 코드에다가 넣었지만)로부터 모든 데이터를 얻고, 구성하고 컨텍스트에 추가한다.


   /* create our list of maps  */

   ArrayList list = new ArrayList();
   Map map = new HashMap();

   map.put("name", "horse");
   map.put("price", "$100.00");
   list.add( map );

   map = new HashMap();
   map.put("name", "dog");
   map.put("price", "$59.99");
   list.add( map );

   map = new HashMap();
   map.put("name", "bear");
   map.put("price", "$3.99");
   list.add( map );

   /*  add that list to a VelocityContext  */

   VelocityContext context = new VelocityContext();

   context.put("petList", list);

이제 컨텍스트 안에 구성되어서 위치된 데이터와 준비된 템플릿을 가지고 우리는 컨텍스트에 대해서 템플릿을 렌더링할 수 있다. 여기에 그 코드가 있다.


import java.io.StringWriter;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

public class PetStoreEmail
{
    public static void main( String[] args )
        throws Exception
    {
        /*  first, get and initialize an engine  */

        VelocityEngine ve = new VelocityEngine();
        ve.init();

        /*  organize our data  */

        ArrayList list = new ArrayList();
        Map map = new HashMap();

        map.put("name", "horse");
        map.put("price", "$100.00");
        list.add( map );

        map = new HashMap();
        map.put("name", "dog");
        map.put("price", "$59.99");
        list.add( map );

        map = new HashMap();
        map.put("name", "bear");
        map.put("price", "$3.99");
        list.add( map );

        /*  add that list to a VelocityContext  */

        VelocityContext context = new VelocityContext();
        context.put("petList", list);

        /*  get the Template  */

        Template t = ve.getTemplate( "petstoreemail.vm" );
        //[역자 주] 한글을 사용한다면 Template t = ve.getTemplate("petstoreemail.vm","KSC5601");을 사용한다.

        /*  now render the template into a Writer  */

        StringWriter writer = new StringWriter();

        t.merge( context, writer );

        /* use the output in your email body */

      sendEmail( writer.toString() );
    }
}

이 완성된 프로그램은 여러분들의 email body를 생성한다. Velocity는 Writer 안으로 template들을 표현하기 때문에, 여러분들은 손쉽게 결과물을 관리(manage)할 수 있다. 이 경우, 표현된 결과물은 StringWriter를 통해 String으로 보냈으나, 파일이나 browser, 또는 데이터베이스의 BLOB(binary large object)으로 손쉽게 보낼 수 있다. 이것이 바로 왜 Velocity가 자바 어플리케이션들과 그럽게 쉽게 통합될 수 있는가에 대한 이유들 중 하나이다.

프로그램의 output (여러분들의 email body)는 아래와 같이 된다.


  3 Pets on Sale!

  We are proud to offer these fine pets
  at these amazing prices.  This month only,
  choose from:

    horse for only $100.00
    dog for only $59.99
    bear for only $3.99

   Call Today!

Velocity Template Language
나는 두개의 다른 예제들을 통해 Velocity 템블릿들을 보여주었지만, 두 개의 케이스 모두 특별한 마크업이 무엇이었는지에 대해서는 아무런 설명을 하지 않았다(비록 여러분들이 아마도 추측했었겠지만).

Velocity Tempalte Language(VTL)은 두 파트를 제공하는 아주 간단한 문법이다. 컨텍스트 내에서 엑세스가 되는 객체들에 대한 형식인 references와 control과 action에 사용되는 문장들의 집합인 directives이다. "표준 비즈니스 카드에 충분히 맞추어져 있는 형태 집합을 가진 언어 정의"(Jim Jagielski의 "Getting Up to Speed with Velocity"를 참고하라)로 설명되었듯이 VTL은 커뮤니티에 의해 계획적으로 간결하고 작게 유지되어져 왔다.

References
템플릿 안의 References는 데이터를 엑세스한다. 이것들은 템플릿의 non-VTL 컨텐트와 자유롭게 섞인다. 형식적으로 정의된 것은, reference는 '$'문자로 시작하고 그리고 컨텍스트 내의 어떤 것을 참조하는 템플릿 내의 어떤 것이다. 만일 대응되는 데이터 객체가 컨텍스트 내에 존재하지 않는다면, 템플릿은 일반 텍스트로 reference를 취급하고 그것을 그대로 output stream에 보내서 표현한다.

여기에 non-VTL 컨텐트와 함께 섞여 있는 간단한 reference를 포함하고 있는 짧은 템플릿이 있다.


    Hello $name!  Welcome to Velocity!

여기에서 reference는 $name가 된다. Hello World 예제에서처럼, Velocity는 템플릿 안에 있는 $namename 키로써 컨텍스트에 존재하는 것의 toString() 리턴 값으로 대체한다.


    Hello World!  Welcome to Velocity!

Velocity 레퍼런스는 어떠한 객체의 public 메소드를 엑세스하는 것을 허용하게 만들어 주고, 템플릿의 syntax는 Java code 내에서 사용했던 것과 동일하게 되어 있다. 여기에 몇가지 예제들이 있다.


   There are $myBean.getSize() elements.

   $myObject.anotherMethod( 1, "more data ")

  
   $foo.getBar().barMethod("hello", $moredata )

  
   $foo.myMethod( $bar.callThis() )

여러분들은 아마 애완동물(Pet Store) email 예제로부터 다시 생각해 낼 수도 있을 것이다. 그 예제에서 우리는 java.util.Map에다가 이름(name)과 가격(price) 정보를 저장했었고, nameprice의 두 토큰을 사용해서 데이터를 접근했었다.


   $pet.name for only $pet.price

이러한 것은 Velocity가 JavaBean과 같이 introspection 메커니즘을 사용했기 때문에 동작하게 된다. 그렇게 됨으로써 property notation을 사용한 레퍼런스들로 메소드를 엑세스할 수 있도록 만들어 주게된다. 애완동물 예제 템플릿에서, Velocity의 introspection 기능은 nameprice의 키를 가지고 Mappublic get(String) 메소드를 찾고 호출(invoke)하게 만들어 준다. 우리는 템플릿 내에서 get(String) 메소드를 직접 호출하는 다른 방식을 사용해서 똑같은 데이터를 엑세스할 수도 있다.


   $pet.get('name') for only $pet.get('price')

이 방법은 똑같은 결과를 산출해 낸다. 그리고 실질적으로 무엇이 발생되었는지를 좀 더 확실하게 보여준다. 그렇지만 프로퍼티 노테이션을 사용하는 나머지 다른 한 방법이 좀 더 읽기 쉽고, 여러분의 템플릿과 데이터 클래스의 세부적인 구현과의 의존성을 줄일 수 있는 방법이다. 예를 들면, 여러분들은 public 메소드들인 getName()getPrice()를 가진 클래스로 List 안의 Map을 대체한다 하더라도 원래 예제 템플릿에서 쓰였던 아래 부분들은 계속해서 잘 동작하게 된다.


  $pet.name for only $pet.price

이것이 바로 Velocity가 여러분의 어플리케이션의 모델-컨트롤러 부분으로부터 view에 대한 커플링을 줄일 수 있도록 해 주는 한가지 방법이다. Velocity의 레퍼런스를 이용한 데이터 엑세스 지원은 강력하면서도 유연한 기능이다. 여러분들의 템플릿 레퍼런스는 퍼블릭하게 엑세스할 수 있는 메소드들에 대응하기 위해서 또는 Velocity의 프로퍼티 형식을 통해서 엑세스하는 것이 가능하게 하기 위해서 필요하다.

지시자(Directives)
지시자(Driective)는 VTL의 나머지 중요한 부분이다. 레퍼런스들처럼 그것들도 템플릿 내에서 다른 non-VTL 컨텐트와 자유롭게 혼용될 수 있다. VTL 내의 지시자(directive)는 다음과 같다.


    #set()
    #if()
    #else
    #elseif()
    #end
    #foreach()
    #include()
    #parse()
    #macro()

#set() 지시자는 템플릿 내에서 레퍼런스 값을 셋팅하게 해 준다. 이것은 컨텍스트 내에 기존에 존재하는 객체를 대체할 수 있거나 새로운 객체로 생성할 수 있다. 프리젠테이션 로직에 있어 디자이너에게 아주 유용한 점은 #set()는 정수에 대한 일반적인 수학적 연산(+,-,/*,%)을 허용할 뿐만 아니라 참/거짓도 평가할 수 있다는 것이다.


    #set( $startrow = 0)
    #set( $count = $current + 1 )
    #set( $isReady = ( $isOn && $isOpen) )

지시자의 #if() #else #elseif() #end 셋트는 많은 프로그래밍과 스크립트 언어에서와 마찬가지로 일반적인 if/then/else 기능을 제공해준다. #if()와 #elseif()는 Boolean 값으로 처리할 수 있는 논리적 연산자인 &&, ||, !, ==, >, >=, <, <=를 사용할 수 있는 표현식을 가지게 된다.


    #if( $value > 5 )
      bigger
    #elseif( $value < 5 )
      smaller
    #else
      just right
    #end

#foreach() 반복 지시자는 두개의 아규먼트를 갖는다. 각각의 값이 할당되는 변수에 대한 레퍼런스와 반복을 수행하게 될 컬렉션 값이 있다. 거기에 약간의 "문법적으로 설탕" 역할을 하는 in 단어가 포함된다.


    #foreach( $item in $itemList )
      My $item.
    #end

#foreach()가 지원하는 컬렉션의 타입으로는 다음과 같은 것들이 있다.

  • Object[]
  • java.util.Collection
  • java.util.Map (iterates over the mapped values)
  • java.util.Iterator
  • java.util.Enumeration

#include()#parse() 지시자는 둘 다 아규먼트로써 템플릿이나 스태틱 리소스를 가지고 템플릿이나 리소스를 적절한 장소에 포함해서 output stream으로 렌더링한다는 점에서 비슷하다. 차이점은 #include()는 어떠한 처리과정 없이 특정 리소스의 내용을 포함(include)하고 #parse()는 특정 리소스를 템플릿처럼 취급하여 현재 컨텍스트에 대하여 모든 지시자들과 레퍼런스들을 처리하게 된다.

여러분들은 이 두개의 지시자를 동적으로 결과물을 생성하는데 사용할 수 있다. 예를 들면, 웹 어플리케이션에서 여러분은 서로 다른 네이게이션 레이아웃이나 컬러/디자인 스키마를 가진 최상위 템플릿들과 같은 여러분의 페이지를 위한 골격을 세울 수 있다. 그런 다음에 동적/정적 컨텐트 엘리먼트들이 사용자와 어플리케이션 상태에 근거하여 런타임시에 #parse()나 (그리고) #include()된다.


    <table>
      <tr><td> #parse( $header ) </td></tr>
      <tr><td> #parse( $body ) </td></tr>
      <tr><td> #parse( $footer ) </td></tr>
    </table>

#macro() 지시자는 디자이너로 하여금 지시자 같이 호출될 수 있는 Velocimacro라고 불리는 파라미터화된 VTL code를 생성할 수 있도록 해 준다. 서브루틴과 비슷한 이러한 형태는 여러분들이 재사용할 수 있는 더 쉬운 사용, 가독성, 유지보수성을 위한 명시적 파라미터들을 가지고 있는 VTL 조각들을 생성할 수 있도록 해 준다. Velocimacro는 훌륭하면서도 강력한 툴이고, 아래에 나오는 예제는 최상의 예제이다.

Velocimacro 예제
여러분들이 디자이너가 <select> 양식을 만들기 위해 사용해야 하는 VTL을 캡슐화시키길 원한다고 가정을 해 보자. 여러분들은 regular VTL code 안에 Velocimacro를 정의할 수 있다.


  #macro( select $name $displayname $choicelist )
    <SELECT name=$name>
    <option value="">$displayname</option>
    #foreach( $choice in $choicelist )
      <option value=$choice>$choice</option>
    #end
    </select>
  #end

이렇게 되면, 여러분들이 HTML <select> 형식이 필요로 하는 어느때라도 여러분들은 단순히 지시자처럼 Velocimarco를 호출하기만 하면 된다.


  Please choose:  #select( "size" "--SIZE--" $sizelist )

렌더링 될 때, Velocity는 아규먼트들을 넘김으로써 Velocimacro를 자동적으로 확장한다.

Velocimacro는 recursion 능력, private local context를 지원, 그리고 심지어 private, template-specific namespace들로 분할 되어지는 능력과 같은 많은 흥미로운 형태를 가지고 있다. 더 많은 정보를 원한다면, Velocity 웹 사이트에 있는 사용자와 개발자 문서를 참조하길 바란다.

VTL 잡동사니
몇가지 다른 VTL 형태를 언급해야만 할 거 같다. VTL은 여러분들이 템플릿 내에서 integer, string 문자열, Boolean 값, 객체의 배열, 연속적인 integer의 배열들을 생성할 수 있도록 허용한다.


  #set( $myint = 5 )
  #set( $mystring = 'Hello There')
  #set( $mybool = true )
  #set( $objarr = [ "a", $myint, $mystring, false ] )
  #set( $intrangearr = [1..10] )

VTL은 또한 참조값(지시자와 Velocimacro들을 포함해서)들을 삽입할 수 있는 쌍따옴표(")를 사용해서 string 문자열을 가지게 만들수도 있다.


  #set( $foo = 'bar')
  #set( $interp = "The value is '$foo'")

$interp의 결과 값은 문자열 The value is 'bar'가 된다.

Velocity 웹 사이트에 완성된 VTL 문서를 찾을 수 있다.

똑같은 데이타, 다른 양식(템플릿)
이제 여러분들은 레퍼런스와 지시자들을 알았으니 우리의 애완동물가게 email 예제를 계속해 보자.

여러분들의 email 메시지를 HTML로 전환한다고 가정해 보자. Velocity를 이용하면 여러분들은 템플릿을 변경함으로써 손쉽게 전환하는 것을 할 수 있다. 여러분들의 코드는 변하지 않고 고스란히 남아있게 할 수 있다. 여기에 똑같은 데이터로부터 HTML을 생성해 내는 다른 템플릿이 있다.


  <HTML>
    <HEAD>
      <TITLE>Pet Store Sale!</TITLE>
    </HEAD>


    <BODY>
      <CENTER>
      <B>$petList.size() Pets on Sale!</B>

      <BR/>
      We are proud to offer these fine pets
      at these amazing prices.  
      <BR/>
      This month only, choose from:
      #set( $count = 1 )  
      <TABLE>
        #foreach( $pet in $petList )
          <TR>
            <TD>$count)</TD>
            <TD>$pet.name</TD>
            <TD>$pet.price</TD>
          </TR>
          #set( $count = $count + 1 )
        #end
      </TABLE>
      <BR/>

      <I>Call Today!</I>
      </CENTER>

    </BODY>
  </HTML>

이 템플릿을 사용하면, 여러분의 email body는 이제 아래처럼 보이게 된다.


  <HTML>
    <HEAD>
      <TITLE>Pet Store Sale!</TITLE>
    </HEAD>


    <BODY>
      <CENTER>
      <B>3 Pets on Sale!</B>

      <BR/>
      We are proud to offer these fine pets
      at these amazing prices.  
      <BR/>
      This month only, choose from:
      <TABLE>
          <TR>
            <TD>1)</TD>
            <TD>horse</TD>
            <TD>$100</TD>
          </TR>
          <TR>
            <TD>2)</TD>
            <TD>dog</TD>
            <TD>$59.99</TD>
          </TR>
          <TR>
            <TD>3)</TD>
            <TD>bear</TD>
            <TD>$3.99</TD>
          </TR>
      </TABLE>
      <BR/>

      <I>Call Today!</I>
      </CENTER>

    </BODY>
  </HTML>

이 예제는 어떻게 Velocity가 여러분들로 하여금 데이터와 코드로부터 프리젠테이션을 분리할 수 있도록 만들어 주는지를 잘 보여 주고 있다. 우리는 다른 템플릿을 가지고 똑같은 데이터와 코드를 사용했다.

XML template
마지막 애완동물 가게 예제로써 XML 문서를 생성해 내는 똑같은 프로그램을 이용하자. 다시 한번 우리는 템플릿을 변경한다.


<?xml version="1.0"?>

<salelist>
#foreach( $pet in $petList )
  <pet>
    <name>$pet.name</name>
    <price>$pet.price</price>
  </pet>
#end
</salelist>


똑같은 프로그램과 이 템플릿을 이용하여 아래의 결과를 얻을 수 있다.


<?xml version="1.0"?>

<salelist>
  <pet>
    <name>horse</name>
    <price>$100.00</price>
  </pet>
  <pet>
    <name>dog</name>
    <price>$59.99</price>
  </pet>
  <pet>
    <name>bear</name>
    <price>$3.99</price>
  </pet>
</salelist>

Velocity와 Servlet를 이용한 프로그래밍
마지막으로 서블릿과 함께 Velocity를 사용해 보자. Velocity 배포본은 org.apache.velocity.servlet.VelocityServlet라는 서블릿 기반 클래스를 포함하고 있다. 이 org.apache.velocity.servlet.VelocityServlet 클래스는 모든 Velocity와 Servlet API를 조절(handle)할 수 있게 함을로써 여러분들이 여러분들의 어플리케이션에만 신경을 쓸 수 있게 만들어준다. (대부분 아주 간단하다). 최소한 여러분들은 컨텍스트 안에 데이터를 집어 넣고, 템플릿을 리턴받는 메소드만은 구현해야만 한다. 아래의 클래스는 request를 핸들링하고 Velocity를 사용하여 템플릿을 렌더링한다(실제 에러핸들링 없이).


import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.apache.velocity.servlet.VelocityServlet;

import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.ParseErrorException;


public class Sample extends VelocityServlet
{
    public Template handleRequest( HttpServletRequest request,
         HttpServletResponse response, Context context )
    {        
        /* organize our data  */

        ArrayList list = new ArrayList();
        Map map = new HashMap();

        map.put("name", "horse");
        map.put("price", "$100.00");
        list.add( map );

        map = new HashMap();
        map.put("name", "dog");
        map.put("price", "$59.99");
        list.add( map );

        map = new HashMap();
        map.put("name", "bear");
        map.put("price", "$3.99");
        list.add( map );

        /* add that list to the context  */

        context.put("petList", list);

        /* get the template */

        Template template = null;
      
        try
        {
            template =  getTemplate("petstoreemail.vm");
        }
        catch( Exception e )

        {
            System.out.println("Error " + e);
        }

        return template;
    }}

환경설정과 디플로이가 적절하게 되면, 이것은 HTML을 생성하는 애완동물 가게 예제와 같이 똑같은 결과를 생성해 내고 서블릿 엔진이 클라이언트 브라우져로 전달하게 될 것이다.

템플릿, deployment information과 추가적인 설정 소스 코드를 포함하여 완벽한 작업 예제를 원한다면 Velocity Project에 의해 제공되는 배포본 안의 서블릿 예제를 보길 바란다.

이제는 여러분들의 차례이다.
Velocity는 아래와 같은 특징을 가지고 있다.

  • 디자이너와 프로그래머 둘다 사용하기 쉽다.
  • 코드로부터 프리젠테이션 형식을 분리해 준다.
  • 기존에 존재하는 클래스를 사용할 수 있게 만들어준다.
  • 어떠한 자바 어플리케이션과 같이 작업할 수 있다.
  • 무료이고 business-friendly open source license인 Apache

    Velocity를 사용하는 것에 관하여 흥미로운 점들 중 한가지는 템플릿 모델의 간결함이 "올바른 방법"으로 수행할 수 있도록 만들어 줄 수 있다는 것이다.

    여러분들은 이 주장을 확인할 수 있다. 이제 여러분들은 Velocity를 가지고 시작하는 방법을 알았고, 진입장벽이 꽤나 낮다는것을 확신할 수 있었을 것이다. 예제들은 작성하기 쉽고 서블릿이나 standalone 프로그램 둘다 적용가능 했다. 여러분들은 Jakarta Velocity Project 웹 사이트로부터 배포본을 다운받음으로써 Velocity와 여러분 자신이 친숙해질 수 있다. 이것은 기본적인 형태나 많은 향상된 형태들을 다룰 수 있는 standalone 어플리케이션과 서블릿 예제들을 가지고 있다. 문서는 잘 정리되어 있고 supportive 커뮤니티는 여러분들을 충분히 도와 줄 수 있다. 메일링 리스트에 등록해서 자유를 느끼기 바란다. 여러분들은 지금 당장은 Velocity에 대한 효용성을 가지지 못할 수도 있다. 그러나 매일 반복되는 프로그래밍 작업에서 이것이 얼마나 자주 문제들을 해결해 줄 수 있는지 놀라게 될 것이다.


    저자에 대해
    Geir Magnusson Jr.는 software개발, 시스템 아키텍쳐, 실시간 데이터 전송에 전문적인 독립 컨설턴트이다. 그는 Velocity Project에 대한 공헌자로 노력하고 있으며, Jakarta Project의 관리 커뮤니티에서 봉사하고 있다. 그 역시 Aapache Software Foundation의 멤버이다.

[펌] SWT 예제들 3.0 API 추가 포함

출처 : http://www.jlab.net

 

Snippets There have been a significant number of small example code fragments posted to the newsgroup which provide insights into some aspect of SWT programming. This section is an attempt to capture these for future reference. Most of these have been written by one of the SWT committers, but in some cases they were created by modifying examples provided by other readers of the newsgroup. For simplicity, no attempt has been made to attribute these to a particular author. If you see something you wrote here, and want that to be noted, please email platform-swt-dev@eclipse.org and a by-line will be added.
You should also remember that examples from the newsgroup are often edited for brevity rather than completeness. They are intended to guide the reader towards the correct solution, rather than be finished products. NOTE: Snippets are tested against the HEAD stream and may reference new API or require bug fixes from there.
Note also that the best way to report an SWT bug is to write your own snippet showing the problem and paste it into the bug report. For a snippet template, see: "What is a snippet?" in the SWT FAQ.
If you are new to SWT and need help compiling and running snippets, click here for instructions.

[펌] iBatis SQL Maps 2.0에 대한 소개

iBATIS SQL Maps 2.0에 대한 소개

 

번역 허태명

 

iBatis SQL Maps는 여러분의 자바 객체와 관계형 데이터베이스 사이에서 이동하는데 매우 간단하고 유연한 수단을 제공합니다. 단 한 줄의 JDBC 코드 없이 진정한 SQL의 완전한 능력을 사용하세요!

 

SQL Maps 프레임워크는 일반적으로 관계형 데이터베이스에 접근하기 위해 필요로 하는 자바 코드의 양을 현저하게 줄여주는데 도움을 줍니다. 이 프레임워크는 매우 간단한 형식의 XML을 사용하여 자바 Bean을 SQL 문장에 매핑합니다. 단순성은 다른 프레임워크와 ORM 툴들에 비해 SQL Maps의 가장 큰 장점입니다. SQL Maps를 사용하기 위해 여러분은 단지 자바 Bean과 XML, SQL에만 친숙하면 됩니다. 새로이 배워야 할 것은 거의 없습니다. 테이블을 조인하거나 복잡한 쿼리를 실행하기 위해 필요로 하는 복잡한 설계는 없습니다. SQL Maps를 사용함으로써 여러분은 진정한 SQL의 완전한 능력을 당장 사용할 수 있습니다. SQL Maps 프레임워크는 대부분의 데이터베이스를 어떤 객체 모델로도 매핑할 수 있고 레거시 설계, 심지어 그것이 나쁜 설계라도 매우 유연하게 대처할 수 있습니다. 이것은 특정한 데이터베이스 테이블의 생성이나 객체, 코드 생성없이 모두 이루어 집니다.

궁금한가요? 여기에 왜 여러분이 iBATIS SQL Maps를 좋아할 수 있는지 10가지 이유가 있습니다.

왜 iBATIS SQL Maps를 사용하나요?

10

JDBC 드라이버를 가지고 있는 어떤 데이터베이스에서도 사용할 수

있습니다.(플러그-인이 필요없습니다.)

9설정가능한 캐쉬 (의존성 포함)
8Local 과 Global 트랜잭션 지원과 관리(JTA)
7간단한 XML 매핑 문서 구조
6Map, Collection, List와 기본형의 래퍼(Integer, String 등)을 지원합니다.
5자바 Bean 스타일의 클래스를 지원합니다.(get/set 메소드)
4복잡한 객체 매핑을 지원합니다. (list와 복잡한 객체 모델 등의 생성)
3객체 모델은 결코 완벽하지 않습니다. (변경이 필요없습니다!)
2데이터베이스 디자인은 결코 완벽하지 않습니다 (변경이 필요없습니다!)
1여러분은 SQL을 이미 알고 있습니다. 왜 다른 것을 배우는데 시간을 낭비합니까?

무엇보다 가장 좋은 것은, 100% Open Source 프리웨어입니다!

예제는 어떤가요? 실제 코드에서 MappedStatement가 어떻게 사용되는지 확인해보세요.

<!-- 다음은 간단한 SQL Map의 예제입니다. 이 쿼리는 DB 컬럼의알리어스를 자동적으로 자바 Bean 프로퍼티에 매핑해주는 SQL Map프레임워크의 기능을 사용합니다. Address 클래스가 다음과 같은프로퍼티를 가지고 있다고 생각해 보세요: id (int), description (String),street (String), city (String), province (String), and postalCode (String).SQL 문장(표준 SQL 기능)에서 컬럼 알리어스를 사용하고 result-class어트리뷰트에서 Address 클래스를 명명함으로써, 컬럼의 값들은 자동적으로자바 Bean에 매핑됩니다! 이것은 SQL Map 프레임워크를 사용하여 SQL 쿼리에서 객체(자바 Bean)을얻는 단지 여러가지 방법 중의 하나입니다. --><select id="getAddress" parameterClass="int"                         resultClass="examples.domain.Address">
select ADR_ID as id,
ADR_DESCRIPTION as description,
ADR_STREET as street,
ADR_CITY as city,
ADR_PROVINCE as province,
ADR_POSTAL_CODE as postalCode
from ADDRESS
where ADR_ID = #value#</select>

어떻게 자바에서 이것을 실행하나요?

/* 다음 간단한 몇 줄의 코드는 위의 문장의 #value# 파라미터에 전달할 키로써 * integer 5를 사용하여 위의 매핑된 문장을 실행합니다.  */Integer pk = new Integer(5);Address address = (Address)sqlMap.queryForObject("getAddress", pk);
아래에 더 많은 예제가 있습니다.
<!-- SQL Maps은 단지 select 쿼리에서만 사용되지 않습니다, 여기에 어떻게새로운 Address 클래스가 insert되는지 보여주고 있습니다. --><insert id="insertAddress" parameterClass="examples.domain.Address">
insert into ADDRESS ( ADR_ID,
ADR_DESCRIPTION,
ADR_STREET,
ADR_CITY,
ADR_PROVINCE,
ADR_POSTAL_CODE) values ( #id#, #description#, #street#,
#city#,
#province#,
#postalCode#)
</insert>// 이 insert 문장을 실행하는 것은 select 쿼리와 마찬가지로 쉽습니다.// executeUpdate 메소드는 insert, update, delete를 위해 사용됩니다.Address address = new Address();address.setId(15);address.setDescription("Bob's Comic Book Store");address.setStreet ("16 Somestreet");...sqlMap.insert ("insertAddress", address);<!-- 여기에 하나 이상의 결과를 리턴하는 예제가 있습니다. --> <select id="getProductByCategory" parameterClass="string" resultClass="examples.domain.Product"> select PRD_ID as id, PRD_CATEGORY as category, PRD_DESCRIPTION as description, PRD_RETAIL as retail, PRD_QUANTITY as quantity from PRODUCT where PRD_CATEGORY = #value#</select>// "개" 카테고리의 제품 리스트를 위해 위의 문장을 실행하는// 것은 다음과 같이 간단합니다.String category = "dog";List productList = sqlMap.queryForList("getProductByCategory", category);// 결과가 너무 많은가요? 자바 Bean에 호환되고 JSP에서 쉽게// 사용할 수 있는, 완전히 탐색가능하고 lazy-loading 기능과// 페이징이 가능한 제품의 list 는 어떤가요?// 페이지당 10개면 괜찮을까요?PaginatedList productList = sqlMap.queryForList("getProductByCategory", category, 10);productList.nextPage();productList.previousPage();productList.isNextPageAvailable();// etc.// 동일한 쿼리에서 단지 Product 클래스의 ID와 Description의 Map만 필요하나요?Map descriptionMap = sqlMap.queryForMap ("getProductByCategory", category, "id", "description");<!-- 레거시 시스템의 복잡한 전용 쿼리? 예를 들어, 아마 여러분은 오라클을사용하여 복잡한 parent/child의 트리를 구할 때? --><select id="getEmployeesByManagerRecursively" parameterClass="int" resultClass="examples.domain.Employee"> SELECT emp_id as id, emp_number as employeeNumber, emp_first_name as firstName, emp_last_name as lastName, emp_dept_code as departmentCode FROM employee START WITH emp_id = #value# CONNECT BY PRIOR emp_manager_id = emp_id;</select>Integer managerId = new Integer(15);List employeeList = sqlMap.queryForList("getEmployeesByManagerRecursively", managerId);

더 많은 예제를 보고 싶으세요?

JPetStore 4를 살펴보세요. JPetStore4는 최신의 기능을 많이 사용하는 완전한 어플리케이션의 훌륭한 예입니다.
그리고 또한 포괄적인 완전한 문서의 Developer Guide 가 있습니다.

[펌] 하이버네이트(Hibernate) 입문

 

 

출처 : http://network.hanbitbook.co.kr/view.php?bi_id=670

 

하이버네이트(Hibernate) 입문
hanbit (2003-11-27 13:49:52)

저자: 닉 휴데커(Nick Heudecker), 역 김진회

데이터베이스로부터 객체를 얻어오거나, 저장하는 퍼시스턴스 계층을 만들거나, 유지 보수하는 작업에 엔터프라이즈 애플리케이션 개발의 상당 부분이 소요된다. 만약 데이터베이스의 스키마가 바뀌는 경우에는 애플리케이션 나머지 부분도 크게 변경해야 한다. 하이버네이트(Hibernate)는 자바 애플리케이션에 적합하기 때문에 사용하기 쉽고, 강력한 객체 관련 퍼시스턴스(Object-Relational Persistence) 프레임워크를 제공한다. 따라서 퍼시스턴스 계층과 나머지 계층 간의 독립성을 유지하도록 해준다.

하이버네이트는 복합 타입은 물론, 컬렉션(Collection)과 객체(Object) 간의 관계에 대한 지원을 한다. 객체에 대한 퍼시스턴스 제공과 동시에, 효율적인 캐싱 계층과 JMX(Java Manage Extensions)를 지원하고, 데이터베이스로부터 객체를 얻어올 수 있는 퀴리 언어까지 제공한다. 사용자 정의 데이터 타입과 동적인 빈(Bean) 객체 역시 지원한다.

하이버네이트는 Lesser GNU Public License 하에 출시되고 있다. 따라서 상업용 애플리케이션은 물론이고 오픈 소스 애플리케이션에도 충분히 사용할 수 있다. 하이버네이트는 오라클, DB2, PostgreSQL, MySQL 등을 비롯한 수많은 데이터베이스를 지원한다. 또한, 하이버네이트를 확장하기 위한 지원 및 툴을 제공, 좀더 쉽게 사용할 수 있는 방법 등은 활발하게 운영되고 있는 사용자 커뮤니티를 통해 도움을 받을 수 있다.

본 기사가 작성된 시점인 2003.6.17 현재 Hibernate 2.0.1가 출시되었다.
(†역자 주: 2003.11.5부로 Hibernate 2.1 beta 6 released가 출시되었다.)

작동 방법(How Hibernate Works)

바이트 코드 프로세싱이나 코드 생성이 아니라, 하이버네이트는 클래스의 퍼시스턴스 프로퍼티를 얻어오기 위해서 런타임 리플렉션을 이용한다. 저장되는 객체는 퍼시스턴스 필드와 필드 간의 관계에 대한 정보를 제공하는 매핑(mapping) 문서에 정의가 된다. 매핑 문서는 애플리케이션이 동작하면 컴파일되고, 클래스에 필요한 정보를 가진 프레임워크를 제공한다. 뿐만 아니라 매핑 문서는 데이터베이스 스키마 또는 Stub 자바 소스 파일생성 등의 기능을 이용할 때 사용된다.

매핑 문서의 컴파일된 컬렉션으로부터 SessionFactory 객체가 생성된다. SessionFactorySession 인터페이스로 퍼시스턴스 클래스를 관리하기 위한 매커니즘을 제공한다. Session 클래스는 퍼시스턴스 데이터 저장과 애플리케이션 간의 인터페이스를 제공한다. Session 인터페이스는 JDBC 연결을 래핑(wrapping)하고 있다. 이 JDBC 연결은 사용자에 혹은, 하이버네이트로부터 관리될 수 있다. 그리고 오직 하나의 애플리케이션 쓰레드에 의해서만 사용되도록 관리된다.

매핑 문서(Mapping Documents)

아래 매핑 문서는 Team, Player라는 클래스에 관한 내용을 담고 있다.
<hibernate-mapping><class name="example.Team" table="teams">  <id name="id" column="team id" type="long" unsaved-value="null">    <generator class="hilo"/>  </id>  <progerty name="name" culumn="team_name" type="string"length="15" not-mull="true"/>  <progerty name="city" culumn="city" type="string"length="15" not-mull="true"/>  <set name="players" cascade="all" inverse="true" lazy="true">    <key column="team id"/>    <one-to-many class="example.Player"/>  </set></class></hibernate-mapping>[SourceCode 1] example.Team 매핑 문서
<hibernate-mapping><class name="example.Player" table="players">  <id name="id" column="player id" type="long" unsaved-value="null">    <generator class="hilo"/>  </id>  <property name="firstName" column="first_name" type="string"length="12" not-null="true"/>  <property name="lastName" column="last_name" type="string"length="15" not-null="true"/>  <property name="draftDate" column="draft date" type="date"/>  <property name="annualSalary" column="salary" type="float"/>  <property name="jerseyNumber" column="jersey_number" type="integer" length="2" not-null="true"/>  <many-to-one name="team" class="example.Team" column="team_id"/></class></hibernate-mapping>[SourceCode 2] example.Player 매핑 문서
상당히 이해하기 쉬운 편이지만 설명이 필요한 부분도 있다. id 요소는 퍼시스턴스 클래스에 의해서 사용되는 주요키를 뜻하는 것으로 id 요소의 속성은 다음과 같다.
  • name: 퍼시스턴스 클래스에 의해서 사용되는 프로퍼티(† 역자 주: 자바 클래스에 정의된 프로퍼티의 이름)
  • column: 주요 키의 컬럼명
  • type: 자바 데이터 타입
  • unsaved-value: 이 값은 클래스가 데이터베이스에 저장이 되었는지 알기 위해서 사용된다. 예를 들어 id 속성이 널(null)이라면 하이버네이트는 이 객체가 저장이 안된 것으로 인식한다. 이 속성은 나중에 saveOrUpdate()라는 메소드를 호출할 때 중요하며, 자세한 설명은 나중에 하겠다.
generator는 주요 키를 생성하는데 사용되는 메소드를 명시한다. 여기서는 hilo generator가 사용되었지만, 하이버네이트는 10여 가지 key generation 메소드를 제공한다. 그뿐만 아니라, Composite Primary Key를 포함하여 개발자 자신만의 매커니즘을 이용하는 것도 가능하다.

property는 표준 자바 속성을 정의하고, 스키마에서 이것들이 컬럼으로 매핑되는 방법을 정의하게 된다. 속성은 컬럼의 길이, SQL 타입, 널 값 사용 가능 유무를 명시할 수 있다. property에는 column이라는 child element를 제공한다. 이 element는 컬럼에 대한 인덱스 명이나 특정 컬럼 타입 같은 추가적인 특징을 명시하기 위해 사용된다. br>
Team 클래스는 다음과 같은 element 블록을 가지고 있다. 이것은 Team에 속한 Players의 컬렉션을 나타낸다.
<set name="player" cascade="all" inverse="true" lazy="true">	<key column="team_id"/>	<one-to-many class="example.Player"/></set>[SourceCode 3] example.Team 컬렉션 정의
[SourceCode 3]은 Player 클래스에 정의된 양방향 매핑을 사용하여 PlayersTeam에 매핑된 것을 정의한다. key element로는 foreign key가 정의가 된다. one-to-many element는 정의된 집합 안에 어떤 객체가 담겨 있는지 정의한다. 예를 들어, Players라는 컬렉션은 Teameam_id라는 컬럼을 foreign key로 참조하며, 컬렉션 안에는 example.Player 객체를 담게 된다.

set element에 있는 lazyinverse라는 속성에 주목해야 한다. 컬렉션을 lazy="true"라 표기하는 것은 컬렉션을 포함하는 객체를 데이터베이스로부터 얻어올 때, 컬렉션이 자동으로 생성되지 않는다는 것을 의미한다. 예를 들어, 데이터베이스로부터 Team 객체를 얻어온다면 Players 객체는 애플리케이션이 접근할 때까지 생성하지 않을 것이다.

이렇듯, inverse element는 양방향성을 가진다는 것을 의미한다.

하이버네이트 프로퍼티(Hibernate Properties)

하이버네이트가 데이터베이스와 접속하거나 스키마를 생성하기 위해서 사용하는 프로퍼티는 hibernate.properties에 작성된다. (†역자 주: 원문에서는 Postgresql 데이터베이스에 접속하는 예제를 보여줬지만 본 기사에서는 역자가 테스트한 환경을 제시하겠다.)
hibernate.connection.username=webfx (데이터베이스에 접속할 수 있는 계정)hibernate.connection.password=webfx (데이터베이스에 접속하기 위해서 필요한 패스워드)hibernate.connection.url=jdbc:oracle:thin:@localhost:1521:SHINHWAhibernate.connection.driver_class=oracle.jdbc.driver.OracleDriverhibernate.dialect=net.sf.hibernate.dialect.Oracle9Dialect(사용하고자 하는 데이터베이스 종류에 따라서 하이버네이트가 제공한다.여기서는 Oracle 9i를 사용했으므로, Oracle9Dialect를 사용했다.)[SourceCode 4]
위의 4가지 속성은 JDBC 연결에서 많이 접해봤을 것이라 생각하므로 자세한 설명은 생략하겠다. hibernate.dialect 속성은 하이버네이트에서 제공하는 Hibernate Query Language(HQL)을 SQL로 변환하기 위해 필요한 SQL dialect를 지정한다. 이 속성 덕분에 애플리케이션의 Data Access Layer는 특정 데이터베이스에 종속되지 않게 할 수 있다.

SessionFactory 생성하기

SessionFactory는 팩토리가 생성될 때 지정한 컴파일된 매핑문서을 저장하고 있다. SessionFactory의 구성 변경 작업은 아주 직관적이다. 모든 매핑 정보는 net.sf.hibernate.cfg.Configuration의 인스턴스에 추가된다. 이것은 나중에 SessionFactory 객체를 생성할 때 사용하게 된다.
Configuration configuration = new Configuration().configure();Configuration configuration = new Configuration().configure("매핑 파일의 위치");[SourceCode 5]
위와 같이 매핑 정보를 가지고 있는 Configuration을 생성하는 방법에는 두 가지가 있다. 첫번째 방식처럼 매핑 파일의 위치를 지정하지 않으면 기본적으로 지정된 클래스 패스를 찾게 된다(이 경우는 개인적으로 추천하지 않는다). 외부에서 제공되는 모듈을 사용하는 경우, 각 모듈마다 설정 파일을 가지고 있을 경우가 있다. 이런 경우에는 설정파일을 한 곳에 모아놓고 관리하는 것이 유지 보수하기도 편리할 것이므로, 매핑 파일의 위치를 지정할 수 있는 두 번째 방식을 추천한다.
SessionFactory factory = congiruation.buildSessionFactory();[SourceCode 6]
Configuration 클래스는 SessionFactory의 생성시에만 필요하다. 그리고 팩토리가 생성된 후에는 버려진다. Session 인스턴스는 SessionFactory.openSession()을 호출함으로써 얻어지며 Session 인스턴스의 논리적인 lifecycle은 데이터베이스 트랜잭션의 lifecycle과 일치한다.

SessionFactory는 또한 XML 매핑 파일을 이용하여 설정할 수 있다. 매핑 파일은 지정한 클래스 패스의 루트 안에 위치시키면 자동적으로 인식되며, 직접 지정할 수도 있다. (개인적으로는 직접 지정하는 방식을 추천하지만, 패스를 직접 하드 코딩하는 얘기는 아니다. 구체적인 방법은 여기서는 다루지 않겠다.)

※주의사항: 하이버네이트의 프로퍼티를 설정하는 방법은 Properties 파일을 이용하는 방법과 XML 파일을 이용하는 방법이 있다. 이때 둘 중, 하나만 이용하도록 하자. 둘 다 지정된 클래스 패스에 있는 경우, 에러가 발생할 위험이 있다.

Persistent 클래스 생성 및 업데이트

하이버네이트를 이용하는 경우, 객체는 Transient 혹은 Persistent와 같이 두 가지 부류로 나누어진다. Transient 객체는 데이터베이스에 저장되지 않은 객체를 말하는 것으로 Transient 객체를 Persistent하게 만들기 위해서는 Session 클래스를 이용해서 데이터베이스에 저장해야 한다.
Player player = new Player(); // ... player 객체에 대한 처리 -> 이때 까지는 player 객체는 Transient 한 상태이다.Session session = SessionFactory.openSession();Session.saveOrUpdate(play);-> 비로서 player 객체는 Persistent하게 된다.[SourceCode 7]
saveOrUpdate라는 메소드는 id 프로퍼티가 null인 경우에는 객체를 저장(데이터베이스에 SQL INSERT)하고, null이 아닌 경우에는 업데이트 작업을 실행(데이터베이스에 SQL UPDATE)하게 된다. 위에서 언급된 unsaved-value 속성을 기억하는가? 이와 관련된 내용이다.

할당된 Players를 포함하고 있는 Team 객체를 생성하고 저장하고 싶다면, 다음과 같이 작업하면 된다.
Team team = new Team();team.setCity ("Detroit");team.setName ("Pistons");// add a player to the team.Player player = new Player();player.setFirstName ("Chauncey");player.setLastName ("Billups");player.setJerseyNumber (1);player.setAnnualSalary (4000000f);Set players = new HashSet();players.add(player);team.setPlayers(players);// open a session and save the teamSession session = SessionFactory.openSession();Session.saveOrUpdate(team);[SourceCode 8] Persisting 객체
Team 객체와 Set 컬렉션에 저장된 Player 객체가 저장된다.

Persistent 클래스 검색하기

데이터베이스로부터 얻어오고자 하는 객체의 주요 키 값을 알고 있다면, 아래와 같이 Session.load()를 이용하여 그 객체를 얻어올 수 있다.
// method 1: loading a persistent instanceSession session = SessionFactory.createSession();Player player = session.load(Player.class, playerId);// method 2: loading the Player's statePlayer player = new Player();Session.load(player, playerId);[SourceCode 9] Loading Persistent 인스턴스
주요 키 값을 알지 못한 상태에서 Persistent 객체를 얻어오기 위해서는 Session.find() 메소드를 사용해야 한다. find() 메소드는 HQL(Hibernate Query Language) 구문을 이용해서 java.util.List 형으로 객체들을 얻어올 수 있도록 한다. HQL에 대한 내용은 참고문헌을 찾아보도록 하자.

†역자 주: 혹시, HQL을 이용해야 한다고 듣자마자 "뭐야? SQL을 여기서도 쓰잖아?"라고 대꾸하는 사람이 있을 것으로 생각한다. 역자도 처음에는 그렇게 생각했다. 그렇지만 HQL은 하이버네이트 내에서 표준으로 쓰이는 쿼리 언어이므로 데이터베이스와 독립적으로 쓰일 수 있다. 그리고 상당히 객체 중심이므로 익히기도 편하다.

Persistent 클래스 삭제하기

Persistent 객체를 Transient 객체로 만드는 것은 Session.delete() 메소드를 이용하면 된다. 이 메소드는 특정 객체에 대해서 직접 delete() 메소드를 호출하거나, HQL을 이용하여 특정 조건에 만족하는 객체를 삭제할 수 있다.
// method 1 - deleting the Player loaded in SourceCode 8session.delete(player);// method 2 - deleting all of the Players with a//            salary greater than 4 millionsession.delete("from player in class example.Player where player.annualSalary> 4000000");[SourceCode 10] Persistent 객체 삭제
여기서 데이터베이스에서는 객체가 삭제되지만, 애플리케이션 상에서는 아직 참조하고 있다는 사실에 주목해야 한다. 객체의 컬렉션을 가지고 있는 객체를 삭제할 경우에는 매핑 파일에 cascade="delete"라고 지정해야 컬렉션에 있는 자식 객체까지 모두 삭제가 된다.

Persistence 클래스 대체안 마무리하면서

본 기사에서는 하이버네이트를 아주 간단하게 소개만 했다. 하지만 하이버네이트는 높은 성능을 제공할 뿐만 아니라 다른 오픈 소스나 상용 제품들과 비교해보았을 때에도 결코 뒤지지 않은 오픈 소스 퍼시스턴스 프레임워크를 제공한다. 하이버네이트에 대해 더 자세한 내용을 알고 싶다면 우선 http://www.hibernate.org를 방문하길 바란다.