JUnit Tutorial

JMF_JUnit 2006.11.30 01:48
JUnit Tutorial

XUnit

XUnit의 X는 변수이다. 자바에는 JUnit이 있고 C++에는 CppUnit이 있으며 Python에는 PyUnit이라는 것이있다. 모두 각 언어에서 유닛테스트를 쉽게 해 줄 수 있는 도구이다. 필자는 이 곳의 예제를 가장 대중적인 언어인 자바로 선정했고 이곳에서 JUnit에 대해 잠시 살펴보고 넘어가도록 하자.

JUnit

JUnit은 자바 프로그래밍 시 Unit테스트를 쉽게 해주는 프레임 워크로 TDD의 창시자라고도 할 수 있는 Kent Beck과 디자인 패턴 책의 저자인 Erich Gamma에 의해서 작성되었다.

JUnit은 단 하나의 jar파일로 구성되어 있으며 사용법이 매우 간단한 것이 그 특징이라고 할 수 있겠다. 이 곳에서는 JUnit의 기본적인 사용법과 요새 자바 IDE로 크게 인기를 끌고 있는 Eclipse에서의 JUnit사용법을 함께 알아보자.

우선 실제로 JUnit을 어떻게 사용하는지 간단한 예제를 통해서 알아보자.

junit.jar파일은http://www.junit.org에서 다운로드 할 수 있으며, 만약 이클립스 사용자라면 plugin디렉토리에 디폴트로 설치가 되어 있는 것을 볼 수 있을 것이다.

이전에 작성했던 피보나치 수열을 Junit을 이용하여 재구성하면 다음과 같은 코드가 만들어지게 된다.

FiboTest.java

importjunit.framework.TestCase;classFibo{publicintget(intn){if(n==1||n==2)return1;returnget(n-2)+get(n-1);}}publicclassFiboTestextendsTestCase{publicstaticvoidmain(String[]args){junit.textui.TestRunner.run(FiboTest.class);}publicvoidtestFibo(){Fibo fibo=newFibo();assertEquals(1,fibo.get(1));assertEquals(1,fibo.get(2));assertEquals(fibo.get(1)+fibo.get(2),fibo.get(3));assertEquals(fibo.get(2)+fibo.get(3),fibo.get(4));assertEquals(55,fibo.get(10));}}

FiboTest클래스는 main메써드의 junit.textui.TestRunner.run(FiboTest.class)를 호출함으로써 테스트가 진행된다. Junit은 FiboTest라는 클래스의 메써드중 test로 시작하는 이름의 메써드는 테스트 메써드로 자동인식하고 자동으로 실행을 시킨다. (java의 reflection을 이용한 방법이다.) 따라서 test로 시작하는 메써드가 10개라면 10개의 테스트 메써드가 실행될 것이다.

우리는 이전에 Fibo라는 클래스에 assertSame이라는 메써드를 직접 만들어서 테스트시 사용했었다. 하지만 junit을 이용하면 우리가 작성했던 assertSame과 동일한 역할을 하는 assertEquals라는 메써드(TestCase클래스의 메써드)가 존재한다. assertEquals메써드 역시 기대값과 결과값이 일치하는지를 조사해주는 역할을 담당한다.

정상적으로 junit.jar를 클래스패스에 등록해주고 위 프로그램을 실행하면 에러없이 테스트가 수행되는 것을 확인할 수 있다. 만약 테스트를 일부러 실패하도록 다음과 같이 수정하고 프로그램을 실행하면,

assertEquals(2, fibo.get(1));

다음과 같은 상세한 Trace를 구경할 수 있다.

junit.framework.AssertionFailedError: expected:<2> but was:<1> at junit.framework.Assert.fail(Assert.java:47) at junit.framework.Assert.failNotEquals(Assert.java:282) at junit.framework.Assert.assertEquals(Assert.java:64) at junit.framework.Assert.assertEquals(Assert.java:201) at junit.framework.Assert.assertEquals(Assert.java:207) at FiboTest.testFibo(FiboTest.java:18) … 이하생략

우리가 이전에 만들었던 assertSame메써드와 마찬가지로 expected : <2> but was : <1>라는 실패 원인에 대해서 친절하게 알려주고 있다.

이클립스에서 junit을 사용하기 위해서는 다음과 같은 절차를 밟아야 한다.

  1. 프로젝트 생성
  2. 프로젝트 Properties선택
  3. Java Build Path선택
  4. Add External JARs선택
  5. plugins / org.junit_3.8.1 / junit.jar 선택
  6. OK 선택

다음은 위와 같은 순서를 진행한 후의 필자의 이클립스 Package Explorer의 모양이다.

사용자 삽입 이미지

Java Project명은 tdd로 했고 junit.jar가 포함되어 있는 것을 확인할 수 있다. 새로운 TestCase(FiboTest.java)를 추가하기 위해서는

사용자 삽입 이미지

이클립스 메뉴의 위 버튼을 클릭하고 Junit TestCase를 선택하면 된다. 보통 테스트 클래스명은 테스트할 클래스명+Test로 하는 것이 일반적이다. 우리는 Fibo클래스를 테스트 할 것이므로 FiboTest로 하였다. (위 FiboTest.java참조)

새로운 FiboTest클래스를 생성하였다면 FiboTest.java를 위와 같이 타이핑하고 실행해보자. 실행은 이클립스의 다음 버튼을 누르고

사용자 삽입 이미지

Run As a Junit Test를 선택하면 된다. (한번 실행 후 단축키 Ctrl-F11을 눌러서 재실행할 수 있다.)

테스트를 실행하면 다음과 같은 결과를 볼 수 있을 것이다.

사용자 삽입 이미지

소요된 시간은 0.01 seconds이며 총 1개의 테스트중 1개가 실행되었고 Error는 0, Failures는 0임을 알려준다. 그리고 진행바는 초록막대기로 표시가 된다. 초록막대기의 의미는 테스트가 성공했음을 알려주는 표시이다.

만약 테스트를 일부러 실패하도록 코드를 다음과 같이 수정하고 테스트를 수행하면

assertEquals(2, fibo.get(1));

테스트는 실패하게 되고 다음과 같은 결과를 볼 수 있다.

사용자 삽입 이미지

제일 먼저 눈에띄는 것은 빨간 막대기로 이것은 테스트가 실패했음을 알려준다. 자세히 보면 Failures가 1로 바뀌었음을 알 수 있다. 또한 실패한 테스트 메써드명(testFibo)이 무엇인지 알려주고 있다.

Junit에서 Failure와 Error의 의미는 다음과 같이 구별된다.

Failure : 테스트의 기대값과 결과값이 틀린경우Error   : 테스트 수행시 오류발생, NullPointerException과 같은 RuntimeError일 경우 발생한다.

테스트가 실패한 경우에는 빨간 막대기가 있는 화면의 하단부분에 실패에 대한 Trace정보가 아래와 같이 표시된다.

사용자 삽입 이미지

이클립스는 Junit에 대한 준비가 잘 되어있는 훌륭한 IDE로 많은 자바 프로그래머들의 사랑을 받고 있다.

우리는 지금껏 junit의 TestCase 메써드중 assertEquals만을 살펴 보았는데 assertEquals외에도 여러 유용한 메써드들이 많이 있다. 이중에서도 가장 많이 사용되는 메써드들을 간단하게 알아보도록 하자.

assertEquals(A, B)

assertEquals는 A와 B가 일치하는지를 조사한다. A나 B에는 Object, int, float, long, char,boolean,,,등의 모든 자료형이 들어갈 수 있다. 단 A,B의 타입은 언제나 같아야만 한다.

assertTrue(X)

X가 참인지를 조사한다. X는 boolean형태의 값이어야 한다.

assertFalse(X)

X가 거짓인지를 조사한다. assertTrue와 정 반대의 메써드라 보면 되겠다. 역시 X는 boolean형태의 값이어야 한다.

fail(message)

테스트가 위 문장을 만나면 message를 출력하고 무조건 실패하도록 한다. 위 메써드는 주로 예외상황을 테스트하거나 아직 테스트가 끝나지 않았음을 명시적으로 나타내주기 위해 자주 사용되곤 한다.

[예외상황 테스트의 예]

try{userMethods.run(parameter.bad());fail("should not reach here!");}catch(UserException e){assertEquals(-1,e.getErrorCode());}

userMethods.run이라는 메써드에 임의로 비정상적인 파라미터를 입력했을 때 UserException이 꼭 발생해야 한다는 것을 의도하는 테스트이다. 만약 UserException이 발생하지 않는다면 fail문 때문에 테스트가 실패하게 되는 것이다.

assertNotNull(Object X)

X가 Null이 아닌지를 조사한다. 만약 Null이라면 assertionFailedError가 발생한다.

assertNull(Object X)

X가 Null인지를 조사한다. 만약 Null이 아니라면 assertionFailedError가 발생한다.

assertSame(Object A, Object B)

A와 B가 같은 객체인지를 조사한다. (주의: 우리가 Fibo클래스에서 직접 만들었던 assertSame과는 전혀 다른의미임)

이정도가 junit으로 테스트코드를 만들 때 가장 많이 사용하게 될 메써드가 될 것이다.

setUp & tearDown

이제 곧 여러분도 경험하게 되겠지만 테스트를 작성하다 보면 하나의 메써드로 모든걸 테스트할 수는 없게된다. 따라서 테스트 메써드의 숫자도 계속해서 증가해 나갈수 밖에 없는데 각각의 테스트 메써드가 공통적으로 사용하는 것을 매번 중복해서 적고 있는 자신을 발견하게 될 것이다.

setUp, tearDown메써드는 test로 시작하는 메써드와 마찬가지로 junit에서 자동으로 인식하는 메써드명이다. setUp메써드는 test로 시작하는 메써드가 수행되기 직전에 호출되고 tearDown메써드는 test로 시작하는 메써드가 종료된 직후에 호출된다.

사용자 삽입 이미지

setUp과 tearDown메써드를 적절히 활용하면 test로 시작하는 메써드들간의 중복을 제거할 수 있을 뿐만 아니라 각각의 테스트의 독립성을 보장할 수 있게 된다. 테스트의 독립성은 매우 중요한 이슈인데 하나의 테스트는 다른 테스트에 의해서 영향을 받지 않아야 함을 뜻한다. 만약 testB라는 메써드가 testA라는 메써드가 수행된 이후에 수행되어야 한다면 그 테스트는 벌써 독립성이 깨져버린 불안한 테스트가 되어 버리는 것이다.

[setUp메써드의 예]

importjunit.framework.TestCase;classFibo{publicintget(intn){if(n==1||n==2)return1;returnget(n-2)+get(n-1);}}publicclassFiboTestextendsTestCase{Fibo fibo;publicstaticvoidmain(String[]args){junit.textui.TestRunner.run(FiboTest.class);}publicvoidsetUp(){fibo=newFibo();}publicvoidtestFibo(){assertEquals(1,fibo.get(1));assertEquals(1,fibo.get(2));}publicvoidtestFibo2(){assertEquals(fibo.get(1)+fibo.get(2),fibo.get(3));assertEquals(fibo.get(2)+fibo.get(3),fibo.get(4));assertEquals(55,fibo.get(10));}}

위에서 보았던 FiboTest클래스를 위와 같이 구성하여도 동일한 결과가 나온다. fibo객체를 setUp메써드에서 미리 생성해주고 그 이후에 testFibo, testFibo2메써드가 수행되도록 한 것이다. 위와같이 setUp메써드를 구성하면 test로 시작하는 각각의 메써드에서 fibo객체를 만들 필요가 없다. setUp에서 이미 생성되기 때문이다.(물론 testFibo에서 사용했던 fibo객체와 testFibo2에서 사용한 fibo객체는 다른 것이다.)

 

 

http://wiki.tdd.or.kr/wiki.py?TddTutorial.JunitTutorial

 

신고
Posted by The.민군

Cloudscape와 Ajax - 예제 (한글)

임베디드 데이터베이스와 웹 서비스 애플리케이션

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
문서 옵션
사용자 삽입 이미지이 페이지를 이메일로 보내기');// :badtag -->
사용자 삽입 이미지사용자 삽입 이미지

이 페이지를 이메일로 보내기

사용자 삽입 이미지
사용자 삽입 이미지

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

사용자 삽입 이미지사용자 삽입 이미지

토론

사용자 삽입 이미지사용자 삽입 이미지

샘플 코드


제안 및 의견
사용자 삽입 이미지피드백
사용자 삽입 이미지

난이도 : 중급

Susan L. Cline, Cloudscape Engineer, IBM

2006 년 9 월 11 일
2006 년 10 월 23 일 수정

Cloudscape와 Derby는 Ajax 애플리케이션을 위한 훌륭한 데이터베이스 서버입니다. 특히, 클라이언트와 서버가 같은 호스트에 있을 때 관리 조건이나 기능들이 필요가 없습니다. 이 글에서는 임베디드 데이터베이스와 웹 서버 애플리케이션을 만드는 단계와 조건들을 설명합니다. 소스 코드와 애플리케이션은 zip 파일 형태로 다운로드 받을 수 있습니다. Derby나 Cloudscape 데이터베이스는 데이터 리파지토리로서 작동합니다. Jetty 웹 서버나 서블릿 컨테이너는 HTTP 요청을 관리하고, Ajax 기술은 클라이언트의 표현과 반응성을 향상시킬 수 있습니다.

머리말

Ajax는 웹 애플리케이션 개발자들의 사랑을 받고 있다. Google Maps, Yahoo email, 기타 인터랙티브 웹 사이트는 Ajax를 사용하여 웹 브라우저를 통해 인터랙션을 강화하고 있다.

Ajax는 스팩도, 프레임웍도 API도 아니다. JavaScript, XML, the Document Object Model (DOM), Cascading Style Sheets (CSS), 웹 서버나 서블릿 컨테이너에 대한 비동기식 HTTP 요청 등으로 구성된 기존 기술 세트라고 보면 된다. Ajax를 통해 웹 애플리케이션에서 할 수 있는 것은 클라이언트에 있는 데이터의 추가적인 프로세싱, 조작, 포맷팅이다. 같은 호스트 상에 클라이언트로서 존재할 수 있는 서버는 웹 서버나 서블릿 컨테이너 그리고 데이터 스토어로 구성된다. 데이터는 플랫 파일이나 데이터베이스에 존재한다. 이 글에서 논의되는 샘플 애플리케이션인 My Address Book에서 데이터는 Derby 데이터베이스에 속해있다.

사용자 삽입 이미지
Derby와 Cloudscape 이름에 대하여
Cloudscape는 근본적으로 관리가 필요 없는(zero-admin) 삽입 가능한 백 퍼센트 자바 관계형 데이터베이스로서 1996년에 시장에 진입했다. 2004년 8월, IBM은 Cloudscape 10.0 관계형 데이터베이스 제품의 카피인 Derby를 Apache Software Foundation (ASF)에 기여를 하여 데이터 중심의 자바 애플리케이션을 혁신시켰다. IBM은 계속하여 Cloudscape 상용 오퍼링을 무료 다운로드로 제공하여 Derby 코어 엔진에 기능들을 추가하고 있다. 최신 릴리스는 Cloudscape 10.1이고 여기에는 Apache Derby 10.1이 포함된다.

다음은 Derby의 특징들이다:

  • Cloudscape 10.2에서는 XML 지원이 가능하다.
  • Java Database Connectivity (JDBC) Callable 문을 사용하여 스토어드 프로시저를 생성 및 호출한다.
  • EmbeddedDataSource를 사용하여 연결한다.
  • 이미지 파일을 BLOBS로서 데이터베이스에 저장한다.

이 글에서는 Cloudscape와 Ajax를 사용하여 간단한 Address Book 웹 애플리케이션을 구현하는 방법을 설명한다. 이 글을 충분히 이해하려면 데이터베이스와 Ajax 기술에 대한 기본적인 지식이 필요하다.참고자료섹션에서 Cloudscape와 Ajax를 공부하기 바란다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


임베디드 애플리케이션

"임베디드(embedded)" 란 단어는 애플리케이션이라는 정황 속에서, 엔드 유저가 관리에 책임을 지지 않고 기반 하위 시스템들을 모르는 상태에서 애플리케이션에 서비스와 기능을 제공하는 컴포넌트를 의미한다. 애플리케이션은 "그저 작동할 뿐이다."Wiktionary에서 찾은 "임베디드" 라는 단어의 일반적인 정의는 "어떤 것의 부분이거나, 단단하게 또는 안전하게 둘러싸인; 어딘가에 단단하게 머무르는"으로 되어있다.

이 샘플 애플리케이션에는 데이터베이스 엔진인 Derby와 서블릿 컨테이너인 Jetty가 애플리케이션 안에 삽입(embedded)되어 있다. Derby와 Jetty 모두 백 퍼센트 자바 구현이며 사용자가 추가 프로세스나 애플리케이션을 시작 또는 중지할 필요 없이 애플리케이션에 의해 시작된다.

Derby 데이터베이스 엔진과 JDBC 드라이버는 jar 파일인 derby.jar에 포함되어 있다. Jetty 웹 서버 역시 jar 파일인 org.mortbay.jetty.jar에 패키지 되어 있다. 하지만 Jetty를 서블릿 컨테이너로서 사용하고 Jetty의 로깅 조건을 완성하려면 추가 jar 파일이 필요하다.

다음 섹션에서는 애플리케이션의 아키텍처를 설명하고, 특정 웹 애플리케이션 유스 케이스에 어떻게 임베디드 솔루션이 맞는지를 설명하겠다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


My Address Book - 아키텍처

My Address Book 애플리케이션은 다음과 같은 기능을 갖춘 웹 애플리케이션이다.

  • 개인별 연락처 정보를 추가한다.
  • 연락처를 변경(편집)한다.
  • 연락처를 삭제한다.
  • 사진을 업로드 한다.
  • 이름, 성 별로 연락처 리스트를 정렬하거나 데이터베이스에 저장된 사진을 가진 연락처만 보여준다.

Model View Controller (MVC) 방식이 웹 애플리케이션 디자인에 사용된다. 뷰는 HTML 페이지가 제공하고 JavaScript와 CSS를 활용한다. 컨트롤러는 서블릿이 제공하고 이 서블릿은 임베디드 Jetty 웹 서버에서 실행되며, Derby 데이터베이스는 백엔드로서 작동하고 애플리케이션의 모델 레이어를 나타낸다.

이 아키텍처는 여느 MVC 애플리케이션과 다를 바 없다. 애플릿, 모델, 컨트롤러를 사용하고 같은 자바 가상 머신(JVM)과 같은 호스트에서 실행된다.

그림 1은 이 애플리케이션의 전체적인 디자인이고 다음 섹션에서는 상세히 다루고자 한다.


그림 1. My Address Book 아키텍처
사용자 삽입 이미지


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


웹 서버와 데이터베이스를 실행하고 있는 JVM을 호스팅하는 브라우저?

화살표 옆에 있는 숫자들은 애플리케이션의 흐름과 웹 서버와 데이터베이스 엔진을 부팅하는 순서이다.

  1. HTML 페이지는 파일 시스템에 있는 브라우저에서 요청을 받는다.
  2. startapplet.html 페이지가 브라우저에서 열린다. 이것은 Firefox 브라우저에서 JVM 내에서 실행되는 애플릿을 시작한다. 완벽한 자바 환경을 갖춘 애플릿은 임베디드 Jetty 웹 서버를 시작한다.
  3. Start Cloudscape Ajax Demo버튼이 눌리면 요청이 웹 서버로 가고 다음 페이지인 firstpage.html을 리턴한다.
  4. firstpage.html은 HTTP 응답을 통해 브라우저에 제공된다.
  5. 사용자 이름과 패스워드가 firstpage.html의 폼에 제출되면 요청이 Jetty로 보내진다.
  6. Jetty는 임베디드 Derby 엔진을 부팅하는 애플리케이션 클래스의 인스턴스인DerbyDatabase를 만들고, 데이터베이스를 만들고(단 한번), 두 개의 테이블을 만들고, 몇 개의 행(row)을 로딩한다.
  7. 데이터베이스는CONTACTS테이블에서 행을 선택하고 이 값을 웹 서버로 전달한다.
  8. 웹 서버는 HTML 페이지인 AddressBook.html의 형태로 클라이언트에 결과를 제공하고 프로세스에 JavaScript와 CSS를 활용한다.

잠깐만이라도 위 단계를 이미지로 그려보면 이전 섹션에서 "임베디드"라는 단어를 왜 그렇게 강조했는지 알 수 있을 것이다. 하나의 JVM이 Firefox 브라우저에서 실행된다. JVM은 애플릿이 임베디드 자바 웹 서버를 시작하도록 하고 이것은 임베디드 자바 데이터베이스 엔진인 Derby를 부팅한다. 모두가 하나의 JVM안에서 실행된다.

왜 이렇게 해야 하는가? 그 질문에는 자세히 답을 해야겠지만 여기에서는 간단한 이유만 설명하겠다:

  • 임베디드 애플리케이션은 전통적인 클라이언트/서버 애플리케이션 보다 관리가 더 쉽다.
  • 클라이언트(Ajax의 순수한 요소 중 하나)에서 서버로 비동기식 데이터 요청에는 HTTP 프로토콜이 필요하다. 따라서 비동기식 데이터 요청을 하려면(이 경우 데이터베이스 안에 있는 데이터) HTTP 서버가 필요하다.

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


애플리케이션의 컴포넌트

샘플 애플리케이션은 다음과 같은 소프트웨어 컴포넌트와 기술을 사용한다:

  • Derby 데이터베이스 엔진과 JDBC 드라이버
    • "서버" 측의 데이터 리파지토리
  • Jetty 웹 서버와 서블릿 컨테이너
    • 데이터베이스 요청에 대해 서블릿을 통해 HTML 페이지를 제공하고 컨트롤러로서 작동한다.
  • Firefox 브라우저와 자바 런타임 환경 (JRE)
    • Mozilla Firefox version 1.5.x는 Java 1.5.x JRE를 사용하여 JVM을 시작한다.
  • JavaScript
    • 웹 서버로 비동기식 요청을 하여 컨트롤러 서블릿을 통해 데이터를 가져온다. DOMParser를 통해 리턴된 데이터를 조작한다. 클라이언트에 있는 데이터를 정렬한다.
  • CSS
    • HTML 페이지의 내용을 포맷팅 한다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


소프트웨어 조건

소프트웨어는 무료로 다운로드 할 수 있고 샘플 웹 애플리케이션을 실행 및 설치하기 전에 설치되어야 한다.

  • Mozilla Firefox 1.5.0.x 또는 이후 버전(참고자료)
  • Sun JRE, Version 1.5.0.x 또는 이후 버전(참고자료)
  • Windows (이 애플리케이션은 리눅스와는 호환되지 않는다.)

JRE와 Firefox 확인하기:

  • Firefox와 JRE를 다운로드 및 설치한 후에JAVA SOFTWARE for Your Computer를 방문하여 브라우저에 JRE가 설치되었는지를 확인한다.
  • 정확히 설치되었을 경우 다음과 같은 아웃풋이 생긴다.

    We detected your Java environment as follows; Description Your Environment  Java Runtime Vendor: Sun Microsystems Inc. Java Runtime Version 1.5.0_06

  • 이렇게 확인을 하는 이유는 애플리케이션이 성공적으로 작동하도록 하기 위해서이다. 성공하지 못했다면, 아마도 올바른 버전의 JRE를 다운로드 및 설치했더라도 밸리데이션 테스트에 보고된 JRE의 버전이 1.5.x 또는 이후 버전이 아니다. 브라우저에 애플릿을 실행할 때 어떤 JRE가 사용될 것인지를 설정해야 한다.

    control panel을 열고Java아이콘을 시작한다. Java 탭을 선택하고 Java Applet Runtime Settings 섹션에서View버튼을 클릭한다. 위치가 실제로 유효한지를 확인해야 한다. 이 예제에서 JRE들 중 하나가 제거되었지만 여전히 무효 엔트리에 있다. 여러 JRE들을 설치했기 때문에 애플리케이션을 설치하기 전에 올바른 JRE가 사용되는지를 확인해야 한다.

  • 이 예제에 사용된 Java control panel JRE 애플릿은 JRE 밸리데이션 테스트가 version 1.5.0_06으로서 사용되고 있다는 것을 보고할 때 다음과 같이 보인다.

그림 2. Java control panel, 애플릿 설정
사용자 삽입 이미지

샘플 애플리케이션 zip 파일:

  • 파일 시스템에 Cloudscape_Ajax_Demo.zip 파일을 다운로드 한다. (다운로드) 모든 HTML, JavaScript, CSS, 자바 소스 파일들은 물론이고 Derby 데이터베이스 엔진과 Jetty 웹 서버 클래스 파일이 들어있다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


애플리케이션 설치하기

  1. 위에 나열된필수 소프트웨어를 설치한다.

  2. Firefox에 사용되는 JRE의 버전이소프트웨어 조건섹션의 "JRE와 Firefox 확인하기"에서 설명한 대로 정확한 것인지를 확인한다.

  3. Cloudscape_Ajax_Demo.zip의 압축을 푼다. 다음과 같은 하위 디렉토리와 파일들이 포함된 Cloudscape_Ajax_Demo가 생길 것이다:
    • src
      • 자바 소스 파일
    • licenses
      • 이 애플리케이션에 포함된 라이브러리용 라이센스 파일
    • cloudscape_ajax_webapp.zip

  4. Mozilla Firefox 홈 디렉토리에 cloudscape_ajax_webapp.zip 파일을 푼다. Jetty 웹 서버는 이 디렉토리에서 시작되기 때문에 HTML 페이지를 공급하는 "홈" 디렉토리로서 간주된다. 이 예제에 사용되는 머신에서 Firefox 실행파일이 상주하는 곳의 경로는 C:\projects\Ajax\Mozilla Firefox이다. 일단 압축이 풀리면 Mozilla Firefox 홈 디렉토리 밑에 webapps/Ajax 디렉토리가 생긴다. 이 디렉토리에는 애플릿 jar 파일인 cloudscape_demo.jar (모든 의존 라이브러리 포함), JavaScript, CSS, HTML 파일들이 들어있다.

    애플릿 jar 파일인 cloudscape_demo.jar에는 다음과 같은 라이브러리들이 포함되어 있다:

    • derby.jar --Derby 데이터베이스 엔진의 10.2.x 스냅샷 버전과 XML 기능을 제공하는 임베디드 JDBC 드라이버이다. Derby 스냅샷은 지원되지 않는다.
    • org.mortbay.jetty.jar --Jetty HTTP 서버와 서블릿 컨테이너
    • javax.servlet.jar --서블릿 API 클래스
    • commons-logging.jar --Jetty 웹 서버에 필요한 로깅 클래스
    • xercesImpl.jar --XML 지원을 위해 Derby에서 요구되는 Xerces 파서
    • cos.jar --이 애플리케이션에 사용되는 웹 서버로 파일을 업로딩 하는데 사용되는 O'Reilly 라이브러리 (jar 파일을 사용하는 것과 관련한 승인과 배포 규제에 대한 라이센스 디렉토리)
    • cloudscape.ajax package --EmbeddedDataSource와 XML 헬퍼 클래스를 통해 Derby 데이터베이스에 액세스 하는 애플릿, 컨트롤러 서블릿, 클래스를 포함하고 있는 애플리케이션 클래스들. (이 모든 클래스들에 대한 소스는 메인 zip 파일의 src 하위 디렉토리에 있다.)
    위 jar 파일에 포함된 클래스들은 추출되어 하나의 jar 파일인cloudscape_demo.jar에 저장된다. 초기 애플리케이션 클래스는 위 라이브러리에 포함된 Jetty와 로깅 클래스들로 액세스 해야 한다. 각 jar 파일 마다 서명을 해야 하는 애플릿 태그에 모든 jar 파일들을 리스팅 하는 대신 다른 jar 파일에서 클래스들을 추출하여 하나의 자가 서명된 jar 파일을 만들었다.

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


애플리케이션 검사

설정과 사전 조건이 완성되면 애플리케이션을 시작하고 어떤 일이 막후에서 발생하는지 살펴보자. 각 아이템들이 큰 그림 속에서 어떻게 맞춰지는지에 대한 이해를 돕기 위해그림 1의 아키텍처 다이어그램을 참조하겠다.

사용자 삽입 이미지
애플릿을 시작할 때 문제가 생기는가?
  1. cloudscape_ajax_webapp.zip이 정확한 디렉토리에서 압축이 풀리는지를 확인한다. Mozilla Firefox 브라우저의 "홈" 디렉토리에 압축이 풀려야 한다.
  2. Firefox와 함께 사용되는 JRE를 다시 검사한다. 올바른 버전의 JRE를 다운로드 했더라도 밸리데이션 테스트는 사용되고 있는 JRE의 버전을 보여주지 않을 수도 있다.소프트웨어 조건섹션의 "JRE와 Firefox 확인하기"를 참조하여 브라우저에 사용된 JRE 버전을 설정하라.

1.5.0 Firefox 브라우저를 시작하고 Firefox 설치 디렉토리 밑에 있는 webapps/Ajax 디렉토리에 있는startapplet.html파일을 연다. 이 예제에서, 이 파일의 전체 경로는 C:\projects\Ajax\Mozilla Firefox\webapps\Ajax\startapplet.html 이다.

cloudscape_ajax_webapp.zip 파일을 올바른 위치에 압축을 풀었다면 startapplet.html이 열릴 때 보안 창이 생길 것이다. (그림 3) Susan Cline for the StartJetty 애플리케이션(애플릿 클래스)에 디지털 서명을 하고 계속 진행한다. startapplet.html을 불러올 때 이러한 보안 경고를 피하려면Always trust content from this publisher체크박스를 클릭하고Run을 선택한다.

보안 경고에 대해서:이 애플리케이션은 클라이언트 상에서(이 경우 클라이언트와 서버는 같은 호스트이다.) 파일을 읽고 써야 하고 애플릿에 의해 시작된다. 애플릿과 관련된 보안 모델은 이를 허용하지 않는다. 애플릿을 서명해야지만 파일의 읽기와 쓰기 액세스가 허용된다. 이 애플리케이션은 이를 실행하기 위해 서명된 jar 파일을 사용한다.


그림 3. 서명된 애플릿에 대한 보안 창
사용자 삽입 이미지


보안 창에서Run을 선택하면 Firefox의 상태 바는Applet StartJetty started를 가리킨다. 첫 번째 페이지는 임베디드 Jetty 웹 서버를 시작하는 애플릿을 포함하고 있는 HTML 파일이다.

Applet

startapplet.html 에는 애플릿을 시작하는 HTMLAPPLET태그가 포함되어 있다. 이APPLET태그(Listing 1)는 애플릿을 포함하고 있는 자바 클래스의 이름과 리스팅 된 애플릿에 필요한 jar 파일을 필요로 한다. Firefox 브라우저 메뉴에서View를 선택하고Page Source를 선택하여 이 페이지의 HTML 소스를 본다.

Applet 클래스를 확장하는 모든 자바 클래스들이 오버라이드 해야 하는init메소드와startJetty메소드는 cloudscape.ajax.StartJetty 자바 소스 파일에서 발췌한 것이다. (Listing 2) zip 파일을 다운로드 하여 압축을 풀면 Cloudscape_Ajax_Demo/src/cloudscape/ajax 디렉토리에서 이 파일을 볼 수 있다.


Listing 1. startapplet.html의 Applet 태그
<APPLET code="cloudscape.ajax.StartJetty.class" codebase="."width="1" height="1" name="StartJetty" archive="cloudscape_demo.jar" alt=""></APPLET>


Listing 2. cloudscape.ajax.StartJetty를 시작하는 Applet 클래스
public void init() {  System.out.println("StartJetty: init() was called");  setBackground(Color.white);  startJetty();}private int startJetty() {  int startStatus = 0;  try {    // Create the server    server = new HttpServer();    // Create a port listener    SocketListener listener = new SocketListener();    listener.setHost("localhost");    listener.setPort(HTTP_SERVER_PORT);    listener.setMinThreads(5);    listener.setMaxThreads(250);    server.addListener(listener);    // Create a context     HttpContext context = new HttpContext();    context.setContextPath("/Ajax/*");    server.addContext(context);    // Create a servlet container    ServletHandler servlets = new ServletHandler();    context.addHandler(servlets);    // Map a servlet onto the container    servlets.addServlet("ControlServlet", "/ControlServlet/*",     "cloudscape.ajax.ControlServlet");    // Serve static content from the context    String home = System.getProperty("jetty.home", ".");    context.setResourceBase(home + "/webapps/Ajax/");    context.addHandler(new ResourceHandler());    // Start the http server    server.start();    startStatus = STARTED_OK;       } catch (java.net.BindException addressUsedExcept) {     System.out.println("Jetty server has already been started on port"+HTTP_SERVER_PORT);      startStatus = STARTED_ALREADY;    } catch (org.mortbay.util.MultiException multiServerExcept) {     System.out.println("Jetty server has already been started on port"+HTTP_SERVER_PORT);    startStatus = STARTED_ALREADY;    } catch (Exception e) {      e.printStackTrace();      startStatus = NOT_STARTED;  }  return startStatus;}

startJetty메소드는 HTTP 서버의 서버와 포트를 설정하고 서블릿 핸들러를 설치하며 애플리케이션의 콘텍스트를 만들고 애플리케이션용 서블릿을 등록하고, 마지막으로 웹 서버를 시작한다. 애플릿이 시작될 때 호출되는init메소드는startJetty메소드를 호출한다.

그림 1은 1단계와 2 단계를 보여준다.

startapplet.html 페이지에서Start Cloudscape Ajax Demo버튼을 클릭한다. 이것은 Jetty에서 firstpage.html 페이지를 보내고 (그림 1의 3, 4 단계) 사용자 이름과 패스워드를 띄운다.

여기에서 사용자 이름에는cloudscape를 패스워드에는ajax를 입력하여 로그인 하거나 새로운 사용자를 만든다.

firstpage.html에서Login to Address Book또는Create New User버튼을 클릭하여 다음을 수행한다. (그림 1의 5단계부터 8단계까지):

  • 사용자 이름과 패스워드를 Jetty 웹 서버에 있는 서블릿으로 보낸다. 서블릿은 Derby 데이터베이스 엔진을 부팅하고 Derby 데이터베이스를 만드는 애플리케이션 클래스의 인스턴스를 만든다. 두 개의 테이블USERS테이블과CONTACTS테이블이 만들어진다. 샘플 행이 이 두 테이블에 삽입된다. 애플리케이션을 호출하면 Derby 엔진이 부팅되지만 데이터베이스를 다시 만들지는 않는다.
  • 사용자 이름이 이미 존재하면 사용자는 인증을 받게 되고, 새로운 사용자 이름이라면 하나의 행이USERS테이블에 삽입된다. 다음 페이지는 애플리케이션의 메인 페이지인 AddressBook.html이다.

서블릿

서블릿 클래스, ControlServlet.java의init메소드를 보자. 이것 역시 다운로드 가능하다.

서블릿 컨테이너에 의해 처음으로 서블릿이 로딩될 때 호출되는 메소드가init메소드이다. 이 메소드는 애플리케이션 클래스인DerbyDatabase를 만들고 이것의initialize메소드를 호출한다. 앞서 설명한 것처럼 이 메소드는 데이터베이스 엔진을 부팅하고 이들이 없다면 데이터베이스와 테이블을 만든다.


Listing 3. 제어 서블릿의 init() 메소드, cloudscape.ajax.ControlServlet
public void init() throws ServletException {  derbyDB = new DerbyDatabase();  derbyDB.initialize();}

DerbyDatabase클래스의initialize메소드의 일부를 통해서 EmbeddedDataSource를 만들고,setCreateDatabase메소드를 사용하여 데이터베이스를 만들고, 데이터베이스 이름을 지정하고, 데이터베이스로 연결하는 방법을 볼 수 있다.


Listing 4. Derby EmbeddedDataSource, cloudscape.ajax.DerbyDatabase 생성하기
public boolean initialize() {  Connection conn = null;  boolean success = true;  if (isInitialized) {    return success;  }  if (ds == null) {    ds = new EmbeddedDataSource();    ds.setCreateDatabase("create");    ds.setDatabaseName("DerbyContacts");    Statement stmt = null;    try {      conn = ds.getConnection();    } catch (SQLWarning e) {       ...

지금, 우리는 어디에 와 있는가? 애플리케이션에 로그인 하여 사용자 이름과 패스워드를 확인하거나, 새로운 사용자 이름과 패스워드를 만들어 이를 Derby 데이터베이스에 삽입했다. 이 모든 과정을 성공하면 애플리케이션의 메인 페이지인 AddressBook.html은 다음과 같이 나타난다.


그림 4. 애플리케이션의 메인 페이지, AddressBook.html
사용자 삽입 이미지

이 페이지에서 연락처 추가, 편집 또는 삭제, 리스트 정렬 같은 다양한 작업들이 수행될 수 있다. 우선, 연락처를 추가해 보자. 아래 이미지는Add버튼이 클릭되고 필드가 채워져서 새로운 연락처를 추가한 후 나타나는 페이지 모습이다.


그림 5. 연락처 추가하기, AddContact.html
사용자 삽입 이미지

Add Contact버튼을 클릭한다. 모든 필드가 채워지면 JavaScript 팝업 박스가 이 연락처의 사진을 실을 것인지를 묻는다. 필드에 알맞은 값이 채워지지 않으면 JavaScript 함수가 경고하여 페이지를 제출하기 전에 모든 필드를 채우도록 한다.Add Contact버튼이 클릭되면 많은 일들이 막후에서 진행된다.

JavaScript

Add Contact버튼이 클릭되면 이 버튼은insertContact함수가 호출되도록 지정하여 JavaScript의 이벤트 핸들링 기능을 사용한다. 이 함수가 하는 첫 번째 일은 형식의 모든 필드들이 채워졌는지를 확인하는 것이다. 채워졌다면 변수가 만들어져서 이 폼에 포함된 모든 값을 포함시킨다.makeDerbyRequest라고 하는 또 다른 JavaScript 함수가 호출된다.makeDerbyRequest함수는 Jetty 웹 서버에 실행되는 서블릿으로 비동기식 요청을 하는 제어 포인트이다.

Listing 5Add Contact버튼의 HTML을,Listing 6은 JavaScript의insertContactmakeDerbyRequest함수 코드를 보여준다.


Listing 5. AddContact.html의 JavaScript 온클릭 이벤트 핸들러
<INPUT type="button" name="add_button" value="Add Contact"onclick="insertContact()" class="buttons">


Listing 6. Add Contact 버튼 클릭, address_book.js 처리하기
var request = false;   try {  request = new XMLHttpRequest();} catch (trymicrosoft) {  try {    request = new ActiveXObject("Msxml2.XMLHTTP");  } catch (othermicrosoft) {    try {      request = new ActiveXObject("Microsoft.XMLHTTP");    }     catch (failed) {     request = false;    }    }}function insertContact() {  var isFormValid = validateForm("add_contact_form");  if (!isFormValid) {    showErrors("add_contact_form");  }   else {    var parameters = "&firstname=" + document.add_contact_form.firstname.value ...    ...    makeDerbyRequest("insert", parameters);  }  return isFormValid;}function makeDerbyRequest(databaseAction, parameters, selectedOption) {  var url = "/Ajax/ControlServlet/?querytype=";  if (databaseAction == "show") {    url = url + "show";    request.open("GET", url, true);    request.onreadystatechange = updateContactList;    request.send(null);  }  else if (databaseAction == "login") {    url= url + "login" + parameters;    request.open("GET", url, true);    request.onreadystatechange = verifyLogin;    request.send(null);  }  else if (databaseAction=="insert") {   url = url + "insert"+ parameters;    request.open("GET", url, true);   request.onreadystatechange = showInsertResults;   request.send(null);  }    ...    ...

makeDerbyRequest함수는insertdatabaseAction매개변수와 이름 리스트와 함께 호출되거나 성과 이름 같이 연락처 애트리뷰트의 모든 것을 나타내는 값 쌍으로 호출된다.makeDerbyRequest함수에 있는request변수는 초기화 되고XMLHttpRequest객체를 나타낸다.XMLHttpRequest객체는 HTTP 서버에서 XML이나 텍스트를 비동기식으로 받을 때 Ajax에서 사용되는 웹 브라우저 DOM의 확장이다.XMLHttpRequest함수의onreadystatechange속성인showInsertResults의 콜백 함수는 나중에 설명하겠다.

서블릿 요청과 데이터베이스 액세스

http://localhost:8095/Ajax/ControlServlet/?querytype=insert에서XMLHttpRequest객체가 Jetty 웹 서버로 요청을 보내면 서버 측에서 어떤 일이 발생하는 지를 알아보고, 이에 더하여 새로운 연락처를 추가했을 때 폼 값에 상응하는 이름 값 쌍에 대해 알아보자.

ControlServlet클래스의doGet메소드는 인커밍 매개변수를 파싱하고쿼리타입매개변수가insert또는update로 설정되면 헬퍼 클래스인ContactXMLBean을 만든다. 이는 연락처 정보를 포함하고 있는 필수 JavaBean이다. 마지막으로ContactXMLBean객체는DerbyDatabase객체인insertContact메소드로 전달된다.


Listing 7. ControlServlet 클래스의 doGet 메소드
public void doGet(HttpServletRequest request, HttpServletResponse response)   throws ServletException, IOException {  String queryType = "";  String user = "";  String password = "";  String newuser = "";  queryType = request.getParameter("querytype");  ...  String returnXML = "";  ...  if (queryType != null) {    if (queryType.equals("show")) {      returnXML = derbyDB.getContacts();    }     else if (queryType.equals("insert") || queryType.equals("update")) {      String firstname = request.getParameter("firstname");      String lastname = request.getParameter("lastname");      String email = request.getParameter("email");      String phone1 = request.getParameter("phone1");      String address = request.getParameter("address");      String city = request.getParameter("city");      String state = request.getParameter("state");      String zip_code = request.getParameter("zip_code");      String phone2 = request.getParameter("phone2");      String bday = request.getParameter("bday");      ContactXMLBean myContact = new ContactXMLBean(address, bday, city, \        email, firstname, lastname, phone1, phone2, state, zip_code);      if (queryType.equals("insert")) {        returnXML = derbyDB.insertContact(myContact);      }      ...      response.setContentType("text/plain");      PrintWriter out = response.getWriter();      out.write(returnXML);      out.flush();      out.close();}

DerbyDatabase insertContact메소드를 시험하기 전에DerbyDatabase클래스의initialize메소드에서 Derby 엔진을 처음 부팅했을 때 만들어졌던CONTACTS테이블과 스토어드 프로시저용CREATE TABLEData Definition Language (DDL) 문을 봐야 한다.

CONTACTS테이블은 현재 버전의 Cloudscape에서 지원되지 않는 XML 데이터 유형을 사용하지만 10.2 릴리스에서는 지원될 것이다. 이 데모에 사용되는 연산은 XML 데이터를 읽고 이를 가져오는 것이다.

주:Cloudscape와 Derby의 공식 10.2 버전은 현재 사용할 수 없다. 하지만 알파 스냅샷 버전은 Apache Derby 웹 사이트에서 다운로드가 가능하다. 이 데모에 포함된 Derby 데이터베이스 클래스들은 알파 버전의 10.2이고 제품 환경에서 사용해서는 안된다.

Derby나 Cloudscape를 사용하여 애플리케이션을 구현하려면 IBM Cloudscape나 Apache Derby 에서 현재 버전인 10.1을 다운로드 한다. (참고자료)


Listing 8. XML datatype을 사용하여 테이블 문 만들기
CREATE TABLE APP.CONTACTS(ID INT CONSTRAINT CONTACT_PK PRIMARY KEY, XML_COL XML)

Derby에서 스토어드 프로시저를 사용하는 세 단계가 있다. 최소한 하나의 정적 메소드를 가진 퍼블릭 클래스를 만들고, SQL을 실행하여 스토어드 프로시저를 등록하고 이를 호출한다.DerbyProcedures클래스는 첫 번째 조건에는 부합한다. 이 메소드에 대해 기억해야 할 것은 디폴트 연결을 사용하고 autocommit 모드를 false로 설정한다. 기본적으로 이것은 true로 설정된다. 이 프로시저는 현재CONTACTS테이블에서ID칼럼의 최대 값을 가진다. 그런 다음 이 값을 늘리고 이를 두 장소의 새로운 행에 삽입한다. (ID칼럼과 XML columnXML_COL의 일부로서). XML 칼럼에 ID를 저장하는 이유는CONTACTS테이블의 선택이 이루어지고 내용이 클라이언트로 보내져서 JavaScript를 통해 파싱될 때 클라이언트에 리턴된 것을 확인하면 명확히 알 수 있다.

XMLPARSESQL 함수는 XML을 컬럼에 삽입하는데 사용되는 새로운 XML 연산자이다. Cloudscape의 10.2 릴리즈에 포함될 예정이다.


Listing 9. cloudscape.ajax.DerbyProcedures의 스토어드 프로시저용 정적 메소드
public class DerbyProcedures {  public static void insertContact(int[] rowNum, String myXMLContact) throws SQLException {    Connection conn = DriverManager.getConnection("jdbc:default:connection");    conn.setAutoCommit(false);    Statement stmt = conn.createStatement();    int currentValue = 0;    ResultSet rs = stmt.executeQuery("select max(id) from APP.CONTACTS");    while (rs.next()) {      currentValue = rs.getInt(1);    }    rs.close();    currentValue++;    stmt.close();    String sql = "insert into APP.CONTACTS (id, xml_col) values ("      + currentValue + ", xmlparse(document '<contact><id>"      + currentValue + "</id>" + myXMLContact + "</contact>' preserve whitespace))";    stmt = conn.createStatement();    int numRows = stmt.executeUpdate(sql);    if (numRows > 0) {      rowNum[0] = currentValue;    } else {      rowNum[0] = 0;    }    stmt.close();    conn.commit();    conn.close();  }}

스토어드 프로시저를 만드는 다음 단계는CREATE PROCEDUREDDL을 실행하여 데이터베이스에 프로시저를 등록하는 것이다. Derby에서 SQL 함수와 스토어드 프로시저의 차이점 중 하나는 함수가 데이터를 변경하지 못한다는 점이다. 또한 스토어드 프로시저는IN,OUT,INOUT매개변수들을 지원한다. 이 프로시저는 Java String으로서 나타난 새로운 연락처인IN매개변수를 취한다.OUTOUT 매개변수는 데이터베이스에 삽입된 행 넘버를 리턴한다.


Listing 10. 데이터베이스에서 스토어드 프로시저를 등록하는 SQL
create procedure InsertXMLContact(OUT rowNum integer, IN contact VARCHAR(500)) parameter style java language java external name'cloudscape.ajax.DerbyProcedures.insertContact'

이제, 스토어드 프로시저를 호출할 수 있다.DerbyDatabase클래스에 있는 이 메소드는 JDBCCallableStatement클래스와CALLSQL 신택스를 사용하여 이 단계를 수행한다. 이 메소드에서 리턴된 정수는 테이블에 삽입된id칼럼의 값에 상응한다.


Listing 11. JDBC를 통해서 스토어드 프로시저 호출하기
public String insertContact(ContactXMLBean myContact) {  // the id of the row just inserted  int idNum = 0;  try {    Connection conn = ds.getConnection();    CallableStatement cstmt = conn.prepareCall("call InsertXMLContact(?,?)");    cstmt.setString(2, myContact.toXMLString());    cstmt.registerOutParameter(1, Types.INTEGER);    cstmt.execute();    idNum = cstmt.getInt(1);    cstmt.close();    conn.close();  }   catch (SQLException sqlExcept) {    sqlExcept.printStackTrace();  }  if (idNum > 0) {    return String.valueOf(idNum);  } else {    return String.valueOf(0);  }}

Add Contact버튼을 누른 후에 발생하는 마지막 단계는 서블릿에서 클라이언트로 보내진 결과를 처리하는 일이다. 웹 페이지에Add Contact버튼을 누른 후에 많은 것들을 보여주었기 때문에 요약만 하겠다.

JavaScriptonclick()이벤트 핸들러는insertContact()JavaScript 함수를 호출했다. 이 함수는 처리하기 전에 모든 필드에 값이 포함되었는지를 확인한다. 여기에 값이 포함되있지 않으면 JavaScript 함수인makeDerbyRequest가 호출된다. 이 함수는 요청을 폼의 필드를 포함하고 있는 Jetty 웹 서버에 있는 서블릿으로 보낸다.showInsertResults콜백 함수는XMLHttpRequest객체인onreadystatechange속성과 함께 등록되었다.

서버 측에서 서블릿은 매개변수를 파싱했고 요청을 Derby 데이터베이스로 보내서 연락처가 테이블로 삽입되도록 했다. 행이xmldatatype을 활용하는 테이블로 삽입되었고 스토어드 프로시저는 JDBCCallable인터페이스를 사용하여 호출되었다. 연락처를 테이블에 삽입한 후에 데이터베이스는 막 삽입된 행의id칼럼의 값을 리턴했다.

이제 클라이언트에서 콜백 함수를 볼 수 있다. (showInsertResults)


Listing 12. 클라이언트에서 삽입 결과 처리하기 (address_book.js)
function showInsertResults() {  if (request.readyState == 4) {    if (request.status == 200 ) {     var response = request.responseText;       if (response != "0") {         confirmAddPhoto(response);             }       else {         alert("There was a problem inserting the contact.");       }     }     else if (request.status == 404) {      alert("Request URL does not exist");     }     else {      alert("Error inserting data, please try again.");     }    }}

이 함수는 매우 표준적인 방식으로 비동기식 요청을 처리하고 있다.readyStatestatus를 체크하여 응답이 완료되고 "OK" 또는 200 상태가 될 때에만 웹 서버에서 리턴된 값을 내보낸다.

지금까지 Address Book 애플리케이션에 추가했고 JavaScript와XMLHttpRequest객체를 사용하여 막후에서 어떤 일이 발생하는지를 보았다. 이제 내 친구 마틴의 사진을 업로드 해보자.

다음 페이지인 AddPhoto.html은 표준 파일 인풋 필드로서 파일 시스템 상의 파일을 검색할 수 있다. 이미지 파일을 검색한 후에Upload to Cloudscape버튼을 클릭한다. Derby 데이터베이스에 이미지가 업로드 된 것이다.

CONTACT_PHOTO테이블용 SQL DDL 이 아래 보인다.


Listing 13. DerbyDatabase.java의 CONTACT_PHOTO 테이블
CREATE TABLE APP.CONTACT_PHOTO(id int constraint CONTACT_FK references APP.CONTACTS(id), fname varchar(30),  lname varchar(30),  filename varchar(30),  picture BLOB(150000),  filesize int));

DerbyDatabase애플리케이션 클래스의insertPhoto메소드는PreparedStatment를 사용하고 사진 파일을 BLOB으로서 데이터베이스에 삽입한다. 아래는 코드표는 읽기 쉽도록 라인을 자른 것이다. 실제 소스에는'\'문자가 없다.


Listing 14. setBinaryStream을 사용하여 데이터베이스에 BLOB 삽입하기
public int insertPhoto(int id, String fname, String lname, String filename,  File photoFile) {  int numRows = 0;  String sql = "insert into APP.CONTACT_PHOTO (id, fname, lname,  filename, picture, filesize) \    values (?,?,?,?,?,?)";  Connection conn;  try {    conn = ds.getConnection();    PreparedStatement prepStmt = conn.prepareStatement(sql);    prepStmt.setInt(1, id);    prepStmt.setString(2, fname);    prepStmt.setString(3, lname);    prepStmt.setString(4, filename);    InputStream fileIn = new FileInputStream(photoFile);    prepStmt.setBinaryStream(5, fileIn, (int) photoFile.length());    prepStmt.setInt(6,(int) photoFile.length());    numRows = prepStmt.executeUpdate();    fileIn.close();    prepStmt.close();    conn.close();  } catch (SQLException sqlExcept) {    SQLExceptionPrint(sqlExcept);  } catch (Exception e) {    e.printStackTrace();  }  return numRows;}

CONTACT_PHOTO테이블의id는 원래 서버에서 JavaScript 함수인confirmAddPhoto함수로 응답을 통해 클라이언트 측에 전달되었다. 이 함수는 이것을 다음 페이지인 AddPhoto.html로 전달한다. 이는 결국 위에 보인insertPhoto메소드를 호출했던 서블릿에 의해 처리되었다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


웹 서버와 데이터베이스를 실행하는 JVM을 호스팅하는 브라우저? Part II

일찍이 웹 애플리케이션에 임베디드 아키텍처가 특정 유스 케이스의 목적에 맞는지를 설명했다. 다음은 부가적인 이유들이다.

  • 네트워크 연결없이웹 애플리케이션을 통해 Derby 데이터베이스에 정보를 저장하고 액세스 할 수 있다.
  • 웹 브라우저는 신참 사용자들에게도 익숙한 사용자 인터페이스이다.
  • 모바일 사용자들은 인터넷 연결 없이 연락처 정보를 추가 및 업데이트 할 수 있다.
  • 이러한 유형의 애플리케이션의 추가 확장을 통해 연결이 가능하다면 원격 서버와 동기화 할 것이다.

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


애플리케이션 검사- Part II

애플리케이션 검사섹션에서는 클라이언트와 서버 그리고 전체 아키텍처간 통신에 초점을 맞췄다. Part II에서는Show All버튼이 눌려지고 애플리케이션의 메인 페이지가 액세스 될 때 마다 데이터베이스에서 가져오는 결과를 클라이언트가 어떻게 처리하는지를 볼 것이다. 또한 이름과 성으로 리스트를 정렬하는데 사용되는 JavaScript도 볼 것이다.

연락처가 데이터베이스에서 리턴될 때 어떤 일이 일어나는지 보려면 이들이 데이터베이스에 어떻게 삽입되었는지를 떠올리면 알 수 있다.CONTACTS테이블에 연락처 내용-성과 이름 같은 이름 값 쌍-을 나타내는 값을 삽입할 때 마다 삽입되기 전에 Derby XML 데이터 유형으로 변환되어야 한다. 헬퍼 클래스ContactXMLBeantoXMLString이라는 유틸리티 메소드를 갖고 있는데 이것은 XML 엘리먼트에 빈을 래핑한다.Listing 11을 보면toXMLString메소드가 새로운 연락처를 삽입했을 때CallableStatement의 매개변수 설정 시 호출되는 모습을 볼 수 있다.


Listing 15. 엘리먼트 태그에 JavaBean 래핑하기
public String toXMLString() {  return "<firstname>" + firstname + "</firstname><lastname>"   + lastname + "</lastname><email>" + email + "</email><phone1>"   + phone1 + "</phone1><address>" + address + "</address><city>"   + city + "</city><state>" + state + "</state><zip_code>"   + zipcode + "</zip_code><phone2>" + phone2 + "</phone2><bday>" + bday + "</bday>";}

테이블에서 모든 연락처를 선택하는 데이터베이스에 대한 JDBC 호출은 Listing 16을 참조하라.


Listing 16. 데이터베이스에서 모든 연락처 가져오기
public String getContacts() {  StringBuffer sbuf = new StringBuffer(2000);  try {    Connection conn = ds.getConnection("APP", "APP");    Statement stmt = conn.createStatement();    sbuf.append("<Results>");    ResultSet rs = stmt.executeQuery("SELECT XMLSERIALIZE(XML_COL AS \     VARCHAR(500)) from APP.CONTACTS");    while (rs.next()) {      sbuf.append(rs.getString(1));    }    sbuf.append("</Results>");    rs.close();    stmt.close();    conn.close();  } catch (SQLException sqlExcept) {    sqlExcept.printStackTrace();  }  return sbuf.toString();}

SQL 함수는 Cloudscape 10.2 릴리스에서 사용할 수 있는 새로운 함수이다. 현재는 사용할 수 없다. XML이VARCHAR(500)로 던져진다.VARCHAR는 Cloudscape 10.1에서 지원되는 데이터 유형이기 때문에 이 애플리케이션을 변경하여 현재 Cloudscape와 작동하도록 하려면APP.CONTACTS테이블을XML_COL과 함께VARCHAR(500)유형으로서 정의하고ContactXMLBean클래스의toXMLString메소드를 사용하고 선택이XMLSerialize가 되도록 한다:

SELECT XML_COL from APP.CONTACTS;

APP.CONTACTS한 개의 행만이 있을 때에만 선택 아웃풋은 그림 17처럼 된다.


Listing 17. XMLSerialize를 통해 데이터베이스에서 리턴된 연락처
<firstname>Susan</firstname><lastname>Cline</lastname><email>susanc@mycorp.com</email><phone1>510 589-8888</phone1><address>300 East 2nd Street</address><city>Oakland</city><state>California</state><zip_code>98654</zip_code><phone2>415 703-6345</phone2><bday>July 15</bday>

getContacts메소드는 스트링 형태로 결과 세트만 리턴한다. 전체 결과 세트에<Results>루트 태그를 어떻게 붙일까? 이 루트 엘리먼트는 구성이 잘 된 XML 문서에 필요하다. 리턴된 결과인 연락처를 파싱하여 디스플레이 하는 방법을 설명하겠다.

연락처 파싱과 디스플레이

Show All버튼이 클릭될 때 마다 또는 AddressBook.html 페이지가 로딩될 때 마다 연락처는 데이터베이스에서 비동기식으로 검색되고 파싱되며 페이지에 디스플레이 된다.

페이지가 로딩될 때 JavaScript 함수를 실행하려면onload이벤트용 이벤트 핸들러가 지정되어야 한다. HTMLBODY태그가 AddressBook.html에서 취해져서OnPageLoad함수와onload이벤트를 제휴시키고 페이지가 로딩될 때 마다 호출된다.

<BODY onLoad="OnPageLoad(queryStringText)" id="thebody">

OnPageLoad함수는show의 매개변수를 가진makeDerbyRequest함수를 호출한다.Listing 18에서 콜백 함수인updateContactList가 바로 그 부분이다.


Listing 18. address_book.js의 makeDerbyRequest와 updateContactList 함수
function makeDerbyRequest(databaseAction, parameters, selectedOption) {  var url = "/Ajax/ControlServlet/?querytype="  if (databaseAction == "show") {    url = url + "show";    request.open("GET", url, true);    request.onreadystatechange = updateContactList;    request.send(null);  }  ... function updateContactList() {   if (request.readyState == 4) {     if (request.status == 200 ) {     // the servlet is returning a string, so I need to      // use responseText vs responseXML     var response = request.responseText;     var parser = new DOMParser();     // this turns the doc into an xml doc so it can be parsed.     var doc = parser.parseFromString(response, "text/xml");     // The xml returned from the server is of the format:     //<Results><contact><firstname>Susan</firstname><lastname>Cline</lastname>     //<email>susan@yahoo.net</email>     //<phone1>510 547 -8888</phone1><address>1569 Balboa</address><city>Oaktown</city>     //<state>California</state><zip_code>94618</zip_code><phone2>510 774-6345</phone2>     //<bday>July 15</bday></contact></Results>             var Results = doc.getElementsByTagName("Results")[0];     if(!Results.getElementsByTagName("contact")[0]) {       alert("No Contacts found - please add one.");       return;     }           // remove existing rows from the select list and populate only with the      // new ones retrieved     var theList = document.getElementById("select_contacts");     theList.length = 0;           // a collection of all contact elements wrapped by the <Results> tag     var allContacts = Results.getElementsByTagName("contact");     var contactOutput = "";     var columns;     var option;     var txt;     var optionValueAttribute;     var optionValue;     var optionId = "";     // iterate through the contacts found     for (var j = 0; j < allContacts.length; j++) {      // columns represents all of the children of the contact      columns = allContacts[j].childNodes;      for (var i = 0; i < columns.length; i++) {        if (columns[i].firstChild) {          // extract the unique integer value but don't include it           // as part of the output string          if (i == 0) {          // this is the value generated from Derby as the id value            optionValue = columns[0].firstChild.nodeValue;          }          else {            // concatenate all the rest of the attributes     contactOutput = contactOutput + " " + columns[i].firstChild.nodeValue;   } }      }      // add a new option element to the select list      option = theList.appendChild(document.createElement('OPTION'));      // assign the unique integer for each contact as the value of the select option      option.value = optionValue;      // create a text node that contains the attributes of the contact      txt = document.createTextNode(contactOutput);      // append the text to the <option>      option.appendChild(txt);      contactOutput = "";    }  }  else if (request.status == 404) {    alert("Request URL does not exist");  }  else {    alert("Error fetching data, please try again.");  }  } }

udpateContactList함수에 대해 가장 먼저 알아야 할 것은 Mozilla 스팩의 객체인DOMParser를 사용한다는 점이다. 이는 클라이언트 상에서 XML이나 텍스트를 파싱하는데 사용된다. 이 경우 서버에서 리턴된 스트링을 파싱한다.parseFromString함수는 스트링을 DOM 트리로 파싱할 수 있도록 해준다.

사용자 삽입 이미지
Mozilla Firefox 전용
소프트웨어 조건섹션에서 언급한 것처럼, 이 애플리케이션은 Firefox에서만 실행된다. JavaScript 코드는Internet Explorer 환경을 제공하지 않는다.

DOM 트리에서 리턴된 문서는getElementsByTagName를 사용하여 파싱되어Results태그를 찾고 첫 번째contact태그를 찾는다. 최소한 한 개의contact엘리먼트도 찾지 못하면 경고가 생기고 함수는 더 이상의 프로세싱을 하지 않고 리턴된다.

연락처를 찾으면AddressBook.html페이지에 연락처가 디스플레이 되는 곳인 HTML select list는 select list 길이를 0으로 설정하여 비워진다.

for루프는 변수j가 0으로 초기화 될 때 첫 번째 연락처의 모든childNodes를 가져온다. 그런 다음, 이 모든childNodes는 첫 번째 노드인id를 통해 반복되고 변수optionValue로 설정된다.optionValue변수는 각 HTML select list 옵션에 대한 고유 식별자로서 사용되어 하나의 연락처를 나타내는 개별 옵션들이 나중에 클라이언트 상에서 조작되도록 한다. 내부for루프에서 처리되는 노드가firstChild가 아니라면 노드의 각 값은contactOutput변수에서 이전 값으로 연결된다.

내부for루프에서 외부for루프로 건너뛰면 자식 노드인 OPTION 노드를 select list에 추가하고optionValue변수에 저장된 값으로 옵션 값을 할당한다.contactOutput변수는 OPTION 노드에 붙은 텍스트 노드로 할당되고 다음 반복 시 빈 스트링으로 다시 초기화 된다.

데이터베이스의 select에서 리턴된 모든 행들은 이러한 방식으로 처리되고 이는Show All버튼이 클릭되거나 페이지가 로딩될 때 마다 HTML select list들을 동적으로 생성한다.

클라이언트에서 연락처 정렬하기

연락처를 검색하고 select list에서 이들을 디스플레이 하는 JavaScript를 보았다. 이제 이것이 클라이언트에 있을 때 정렬 및 조작하는 방법을 알아보자. 더 많은 연락처가 추가되어 더 재미있어졌다. 그림 6은 My Address Book에 있는 모든 연락처를 보여준다. 사진은 두세 장 정도이다.


그림 6. My Address Book 연락처 리스트
사용자 삽입 이미지

이름 별로 정렬된 연락처 리스트를 보려면Sort by Firstname버튼을 클릭한다.


그림 7. 이름 별로 정렬된 연락처 리스트
사용자 삽입 이미지

성 별로 정렬하는 것도 마찬가지이다.


그림 8. 성 별로 정렬하기
사용자 삽입 이미지

이름이나 성 별로 정렬하는데 사용되는 JavaScript 함수는sortSelect함수이다. 이 함수는 두 개의 매개변수를 취한다. -- 첫 번째는 정렬하는데 사용되는 HTML select list이고 두 번째는 비교 함수이다.


Listing 19. address_book.js의 sortSelect 함수
function sortSelect (select, compareFunction) {  var options = new Array (select.options.length);  for (var i = 0; i < options.length; i++) {    options[i] =     new Option (select.options[i].text, select.options[i].value,                select.options[i].defaultSelected, select.options[i].selected);  }  options.sort(compareFunction);  select.options.length = 0;  for (var i = 0; i < options.length; i++) {    select.options[i] = options[i];  }}

이 함수는 첫 번째 매개변수로서 전달된 select list의 크기인 JavaScript Array 객체를 만든다. Select list의 모든 옵션들은 새로운 Array에 복사된다. 각 오래된 옵션에서 새로운 Option 객체를 만들면 된다. Array의 정렬 방식은 Array를 정렬하는 알고리즘으로서 사용된 선택적 함수 매개변수와 함께 사용된다. 그런 다음 Select list는 비워지고 select list 옵션들이 정렬된 어레이에 저장된 값으로 설정된다. 어레이를 정렬하는데 사용된 함수는compareFunction이다.

Sort by Lastname버튼이 클릭되는 경우 sortSelect를 호출하면 다음과 같이 된다:

<INPUT type="button" id="lastname_sort_button" name="lastname_sort_button" value="Sort by Lastname" onclick="sortSelect(document.select_form.contacts,sortLastName)" class="buttons">

Array 객체에 사용되는 정렬 함수는sortLastName이다. (Listing 20)


Listing 20. address_book.js의 sortLastName 함수
function sortLastName(option1, option2) {     var lastName1 = extractLastName(option1).toUpperCase();  var lastName2 = extractLastName(option2).toUpperCase();     return lastName1 < lastName2 ? -1 :  lastName1 > lastName2 ? 1 : 0;}

이 함수는 옵션에서 두 번째 단어를 파싱하고(이름) 이를 대문자로 바꾸고 두 개의 스트링을 비교한 다음 -1, 1, 또는 0을 리턴한다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


요약

Ajax 기반 웹 애플리케이션에서 클라이언트와 서버가 같은 호스트에 있는 상황에서 임베디드 데이터베이스 엔진과 웹 서버를 사용하는 방법을 설명했다.

JavaScript와 XMLHttpRequest 객체를 사용하여 My Address Book 애플리케이션의 연락처들은 Jetty 웹 서버에서 실행되는 서블릿을 통해 Derby 데이터베이스에서 가져오게 된다.

데이터베이스와 웹 서버를 웹 애플리케이션에 삽입하면 연결이 되어 있는 상황에서 원격 서버와 동기화 되어 애플리케이션을 확장할 수도 있고 영속 데이터로 액세스 할 수 있는 친숙한 사용자 인터페이스라는 장점이 있다.

더 복잡한 애플리케이션에는 이 애플리케이션의 아키텍처가 수정되어야 하지만 웹 애플리케이션에 Derby를 삽입할 때의 이점을 설명하기엔 충분했으리라 믿는다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


감사의 말

이 애플리케이션은 2005년 12월 ApacheCon에서 Francois Orsini의 데모를 기반으로 하고 있다. Francois는 Apache Derby 신봉자이다. 그의 애플리케이션 역시 Firefox 브라우저에 Derby를 삽입했지만 임베디드 웹 서버는 사용하지 않으므로 웹 서버로 비동기식 호출은 하지 못한다. Francois 데모를 다운로드 하려면참고자료섹션을 참조하라.

기사의 원문보기



사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


다운로드 하십시오

설명이름크기다운로드 방식
Source and binary files for the ApplicationCloudscape_Ajax_Demo.zip4.87MBHTTP
사용자 삽입 이미지
사용자 삽입 이미지다운로드 방식에 대한 정보사용자 삽입 이미지사용자 삽입 이미지Get Adobe® Reader®


참고자료

교육

제품 및 기술 얻기

토론


필자소개

사용자 삽입 이미지

사용자 삽입 이미지

Susan Cline은 Eclipse와 Derby 툴링 통합을 중심으로 Cloudscape 개발자와 사용자를 위한 기술 콘텐츠를 개발하고 있다.

신고
Posted by The.민군

Data Access Objects를 사용하여 DB2 또는 MySQL 데이터베이스에 있는 데이터에 액세스 하기 (한글)

웹 서비스를 사용하여 서비스 지향 아키텍처에서 자바 애플리케이션 클라이언트에 사용할 수 있는 데이터 만들기

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
문서 옵션
사용자 삽입 이미지이 페이지를 이메일로 보내기');// :badtag -->
사용자 삽입 이미지사용자 삽입 이미지

이 페이지를 이메일로 보내기

사용자 삽입 이미지
사용자 삽입 이미지

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

사용자 삽입 이미지사용자 삽입 이미지

샘플 코드


제안 및 의견
사용자 삽입 이미지피드백
사용자 삽입 이미지

난이도 : 중급

Peter Wansch, Software Engineer, IBM

2006 년 9 월 11 일
2006 년 10 월 23 일 수정

IBM Rational Application Developer version 6.0을 사용하여 IBM DB2나 MySQL 데이터베이스에 데이터를 저장하는 트레이닝 관리 웹 서비스를 만드는 방법을 설명합니다. 이 웹 서비스를 기존 IBM WebSphere application server (version 6.0)에 저장하고, DB2와 MySQL 데이터베이스에 대한 JDBC 데이터 액세스를 설정하고, Apache Axis를 SOAP 프로세서처럼 사용하는 독립형 자바 웹 서비스 클라이언트를 만드는 방법도 설명합니다.

머리말

Data access objects (DAO)는 코어 Java™ 2, Enterprise Edition (J2EE™) 플랫폼 패턴이다. 엔터프라이즈 정보 티어에 저장된 데이터에 액세스 하는 엔터프라이즈 애플리케이션을 위한 추상 레이어를 구현한다. 그와 같은 정보 티어에 액세스 하는 방법은 스토리지 유형(관계형 데이터베이스, 객체 지향 데이터베이스, 플랫 파일 등)과 벤더 구현에 따라 매우 다양하다. SQL과 JDBC™ (Java Database Connectivity)를 사용하여 IBM® DB2®, IDS, Derby, SQL Server, Oracle, MySQL 같은 관계형 데이터베이스에 액세스만 할 것이라도 데이터 소스에 대한 모든 액세스를 캡슐화 하는 것은 매우 유용한 패턴이다. 이는 미묘한 벤더들간 차이를 수용할 뿐만 아니라 코드의 관리성과 품질을 향상시킨다. 패턴에 대한 자세한 내용은참고자료섹션을 참조하라.

이 글에서는 IBM® Rational® Application Developer version 6.0 (이후 IRAD)를 사용하여 DAO를 사용하는 트레이닝 관리 웹 서비스를 생성 및 전개하는 방법을 설명하겠다. 이 웹 서비스를 사용하여 사용자들은 코스, 학생, 등록을 만들고 추가할 수 있다. 또한 Apache 액세스를 SOAP 프로세서로서 사용하는 자바 애플리케이션 클라이언트를 구현하는 방법도 설명한다. 이 애플리케이션에는 모든 학생들과 코스가 들어있고 등록을 처리하게 된다.

이 글의 첫 번째 부분은 DB2나 MySQL 데이터베이스에 저장된 데이터에 액세스 하기 위해 JDBC를 사용하는 SQL 문을 캡슐화 하여 DAO를 구현하는 방법을 설명한다.

두 번째 부분에서는 클라이언트가 코스, 학생, 등록을 볼 수 있도록 하는 웹 서비스를 구현하는 방법을 설명한다. 이 웹 서비스는 Java Transaction API (JTA)의 범위 안에서 DAO를 사용하여 데이터베이스에 저장된 데이터에 액세스 한다.

세 번째 부분에서는 Apache Axis를 사용하여 Java™ 2, Server Edition (J2SE™) 클라이언트를 구현하는 방법을 설명한다. J2SE 클라이언트는 단순한 웹 클라이언트와 비교할 때 보다 복잡한 엔드 유저 태스크를 단순화 하는 풍부한 GUI를 제공한다.

사전 조건

다음의 소프트웨어가 설치되어야 한다:
  • IRAD. IBM® Rational® Product Updater를 사용하여 version 6.0.1.1로 업데이트 하고 모든 임시 픽스에 적용한다.
  • IBM® WebSphere® Application Server Version 6.0. version 6.0.2.5이나 그 이후 버전으로 업데이트 한다.
  • IBM® Rational® Agent Controller. *
  • IBM DB2 Version 8.1(Fixpak 7 이상), 또는 MySQL 4.1.13(또는 그 이상), MySQL Connector/J 3.1.11(또는 그 이상)
  • Apache Axis 1.3.
  • SunSMJavaBeans™ Activation Framework 1.0.2.
  • SunSMJavaMail™ 1.3.3_01. **
다운로드 링크는참고자료섹션을 참조하라.

주:
* 에이전트 컨트롤러는 네트워크 전개에만 필요하다.
** JavaMail이 필요하다. SOAP with Attachments를 사용할 경우에만 필요하다. 하지만 앞으로 웹 서비스에 SOAP with Attachments를 사용하려면 설치하는 것이 좋다.

WebSphere 애플리케이션 서버 인스턴스 server1과 DB2 또는 MySQL이 시작했는지를 확인하라. 이 글은 여러분이 IRAD를 사용하는 컴퓨터에 WebSphere 애플리케이션 서버를 실행하는 것으로 가정한다.

Data Access Objects 구현하기

전송 객체 구현하기

다음은 시스템 준비 과정이다:

  1. IRAD에서Window > Open Perspective > Web을 선택하여 웹 퍼스펙티브로 전환한다.
  2. File > New > Dynamic Web Project를 선택하고 프로젝트 이름을TAWebService로 입력한다.
  3. Show Advanced를 클릭하고 서블릿 버전에2.4를, Target server에WebSphere Application Server v6.0을 선택한다.
    여러분이 유일하게 선택할 수 있는 것이 이라면 Target server 드롭다운 리스트 옆에 있는New를 클릭하여 새로운 서버 런타임을 정의한다.

서버 런타임을 정의한 후에 IRAD에서 서버를 정의해야 한다.

  1. File > New > Other > Server > Server를 선택한다.
  2. 호스트 네임으로localhost를 입력하고 서버 유형으로WebSphere v6.0 Server를 선택한다.Next를 클릭한다.
  3. 모든 설정이 server1 인스턴스의 세팅과 매치하는지를 확인하고(그림 1)Finish를 클릭한다.

그림 1. 새로운 서버 런타임 정의하기
사용자 삽입 이미지
  1. TAWebServiceEAR이라는 EAR 프로젝트가 추가되었는지, 콘텍스트 루트가 TAWebService로 설정되었는지 확인한다. (그림 2)Next를 클릭한다.

그림 2. 새로운 동적 웹 프로젝트 생성하기
사용자 삽입 이미지
  1. 모든Web Project기능들을 지우고Finish를 클릭한다. 웹 퍼스펙티브가 열릴 것이다.

우선, 코스, 학생, 등록에 대한 전송 객체를 만든다. 전송 객체들은 웹 서비스 메소드 호출에 대한 매개변수로서 사용되는 단순한 빈으로서 데이터를 클라이언트에 리턴하거나 클라이언트에서 데이터를 받는다. 다음은 빈의 최소 조건들이다.

  • 공용 무인자 구조체(public no-argument constructor)를 제공한다.
  • java.io.Serializable을 구현한다.
  • 속성을 위해 set/get 메소드를 요청한다.
  • 쓰레드 보안이 된다.

다음은 전송 객체를 만드는 과정이다:

  1. Project Explorer에서 Web Projects, TAWebService, Java Resources를 확장하여 JavaSource 폴더를 선택한다.
  2. 오른쪽을 클릭하여New > Class를 선택한다.
  3. 패키지 이름으로com.ibm.ta.webservice를 클래스 이름으로Course를 입력한다.
  4. java.io.Serializable을 인터페이스로 추가하고 모든 메소드 스텁들을 지운다. (그림 3)
  5. Finish를 클릭한다.

그림 3. 새로운 클래스, Course 생성하기
사용자 삽입 이미지

  1. 인스턴스 변수idname을 추가하고 이들을 구조체에서 초기화 한다. (그림 5)
  2. Source Code Editor 윈도우를 오른쪽 클릭하고Source > Generate Setters and Getters를 선택한다.
  3. 그림 4처럼 모든 것을 선택한다.
  4. ClickOK를 클릭한다.

그림 4. Course용 게터와 세터 생성하기
사용자 삽입 이미지

결과 소스 코드는Listing 1과 같다.


Listing 1: Course.java
package com.ibm.ta.webservice;import java.io.Serializable;public class Course implements Serializable { private long id; private String name; public Course() {  this.id = 0;  this.name = null; } public Course(long id, String name) {  this.id = id;  this.name = name; } public long getId() {  return id; } public void setId(long id) {  this.id = id; } public String getName() {  return name; } public void setName(String name) {  this.name = name; }}

위 과정을 Student (Listing 2)와 Enrollment (Listing 3)에도 적용한다.


Listing 2: Student.java
package com.ibm.ta.webservice;import java.io.Serializable;public class Student implements Serializable { private long id; private String name; public Student() {  this.id = 0;  this.name = null; } public Student(long id, String name) {  this.id = id;  this.name = name; } public long getId() {  return id; } public void setId(long id) {  this.id = id; } public String getName() {  return name; } public void setName(String name) {  this.name = name; }}


Listing 3: Enrollment.java
package com.ibm.ta.webservice;import java.io.Serializable;public class Enrollment implements Serializable { private long studentID; private long courseID; public Enrollment() {  this.studentID = 0;  this.courseID = 0; } public Enrollment(long studentID, long courseID) {  this.studentID = studentID;  this.courseID = courseID; } public long getCourseID() {  return courseID; } public void setCourseID(long courseID) {  this.courseID = courseID; } public long getStudentID() {  return studentID; } public void setStudentID(long studentID) {  this.studentID = studentID; }}

DAO 인터페이스 만들기

DAO 인터페이스는 영속 객체인 Courses, Student, Enrollments에 대해 DAO 메소드를 정의한다. 이들은 각 지원 데이터베이스에 MySQLCourseDAO나 DB2CourseDAO 처럼 구체적 DAO 구현으로서 만들어진다.

구체적 DAO 구현은 각각의 데이터베이스에 액세스 할 때(예를 들어, 데이터베이스에 연결할 수 없을 때) 시스템 예외가 생긴다. 이들은 매개변수가 특정 기준에 부합되지 못하면 애플리케이션 스팩의 예외를 던진다. 예를 들어, 매개변수는 한계를 벗어나거나, 기존 데이터를 중복시키거나, 불완전 할 수 있다.

DAO 인터페이스는 데이터 소스 스팩의 예외를 콜러(caller)에게 전달해서는 안된다. 대신 이들을 의미가 있고, 데이터 소스와 독립적인, 애플리케이션 스팩의 예외로서 다시 발생되어야 한다. 이 예제에서 DAO 구현이 회복할 수 없는 시스템 에러를 만나면 이들은 DAOException을 던진다.

  1. 새로운 패키지 com.ibm.ta.dao에 있는 예외를 확장한 DAOException 클래스를 만든다. (Listing 4)

Listing 4: DAOException.java
package com.ibm.ta.dao;public class DAOException extends Exception { public DAOException(String message, Throwable cause) {  super(message, cause); } public DAOException(String message) {  super(message); }}

  1. 2. 무효 매개변수가 DAO 메소드에 전달되면 인터페이스는 DAOParameterException 이라고 하는 특별한 예외를 던진다. 이것은 DAOException을 하위 클래스로 만든다. (Listing 5)

Listing 5: DAOParameterException.java
package com.ibm.ta.dao;public class DAOParameterException extends DAOException { public DAOParameterException(String message, Throwable cause) {  super(message, cause); } public DAOParameterException(String message) {  super(message); }}

이제 DAO 인터페이스를 만들어야 한다.

  1. com.ibm.ta.dao 패키지를 오른쪽 클릭하고New > Interface를 선택한다.
  2. 인터페이스 이름으로CourseDAO를 입력하고Finish를 클릭한다.
  3. 필요한 메소드를 추가한다. (Listing 6)

Listing 6: CourseDAO.java
package com.ibm.ta.dao;import com.ibm.ta.webservice.Course;public interface CourseDAO { public Course[] selectCourses() throws DAOException; public void insertCourse(String name) throws DAOException;}

이 과정을 StudentDAO (Listing 7)와 EnrollmentDAO (Listing 8)에도 반복한다.


Listing 7: StudentDAO.java
package com.ibm.ta.dao;import com.ibm.ta.webservice.Student;public interface StudentDAO { public Student[] selectStudents() throws DAOException; public void insertStudent(String name) throws DAOException;}


Listing 8: EnrollmentDAO.java
package com.ibm.ta.dao;import com.ibm.ta.webservice.Student;import com.ibm.ta.webservice.Course;public interface EnrollmentDAO { public void insertEnrollment(long studentID, long courseID)   throws DAOException; public Student[] getEnrolledStudents(long courseID) throws DAOException; public Course[] getCourseEnrollments(long studentID) throws DAOException;}

MySQL과 DB2를 위한 구체적 DAO 구현을 만들기 전에 각각의 데이터베이스를 만들어야 한다.

트레이닝 관리 데이터베이스 만들기

이 섹션에서는 명령행 인터페이스를 사용하여 DB2나 MySQL 서버 상에 트레이닝 관리 데이터베이스를 만드는 방법을 설명한다. 다운로드 Zip 파일에는 TAWebService\sql 디렉토리에 있는 스크립트가 포함되어 있다.

DB2 데이터베이스 서버를 사용하여 트레이닝 관리 데이터베이스 만들기

  1. 명령행에db2cmd를 입력하여 DB2 명령어 윈도우를 연다.
  2. TAWebService 디렉토리로 변경하고 다음을 입력한다:mkdir sql
  3. sql 디렉토리를 변경하고 노트패드를 사용하여 createdb.db2라고 하는 파일을 만든다. (Listing 9)
  4. db2 -tf createdb.db2를 입력하여 DB2 데이터베이스를 만든다.

데이터베이스가 성공적으로 구현되었다면 다음과 같은 아웃풋을 볼 수 있다.


C:\source\TAWebService\sql>db2 -tf createdb.db2
SQL1013N The database alias name or database name "TA " could not be
found. SQLSTATE=42705

DB20000I The CREATE DATABASE command completed successfully.


Database Connection Information

Database server = DB2/NT 8.2.0
SQL authorization ID = PETER
Local database alias = TA


DB20000I The SQL command completed successfully.

DB20000I The SQL command completed successfully.

DB20000I The SQL command completed successfully.

DB20000I The SQL command completed successfully.

DB20000I The SQL command completed successfully.

DB20000I The SQL command completed successfully.

DB20000I The SQL command completed successfully.

Listing 9: createdb.db2
DROP DATABASE ta;CREATE DATABASE ta;CONNECT TO ta;SET SCHEMA ta;CREATE TABLE course(  course_id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NO CACHE),  course_name VARCHAR(255) NOT NULL DEFAULT '',  PRIMARY KEY (course_id));CREATE TABLE student(  student_id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NO CACHE),  student_name VARCHAR(255) NOT NULL DEFAULT '',  PRIMARY KEY (student_id));CREATE TABLE enrollment(  course_id BIGINT NOT NULL,  student_id BIGINT NOT NULL,  PRIMARY KEY (course_id, student_id),  FOREIGN KEY (course_id) REFERENCES course(course_id)                      ON DELETE RESTRICT,    FOREIGN KEY (student_id) REFERENCES student(student_id)                      ON DELETE RESTRICT);INSERT INTO course (course_name) VALUES ('Java Programming in the Sun');INSERT INTO student (student_name) VALUES ('Peter Wansch');CONNECT RESET;

MySQL 데이터베이스 서버를 사용하여 트레이닝 관리 데이터베이스 만들기

  1. 명령행에서 sql 디렉토리로 변경하고 노트패드를 사용하여 createdb.mysql 파일을 만든다. (Listing 10) 이 스크립트도 "password"와 함께 "ta"라는 새로운 MySQL 사용자를 만든다.
  2. mysql -h localhost -u root -p < createdb.mysql을 입력하여 MySQL 데이터베이스를 만든다.

MySQL 데이터베이스가 성공적으로 구현되었다면 다음과 같은 아웃풋을 볼 수 있다:


C:\source\TAWebService\sql>mysql -h localhost -u root -p < createdb.mysql
Enter password: ********

Listing 10: createdb.mysql
DROP DATABASE IF EXISTS ta;CREATE DATABASE ta;GRANT ALL PRIVILEGES ON ta.* TO ta@localhost IDENTIFIED BY 'password';USE ta;CREATE TABLE course(  course_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,  course_name VARCHAR(255) NOT NULL DEFAULT '',  PRIMARY KEY (course_id));CREATE TABLE student(  student_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,  student_name VARCHAR(255) NOT NULL DEFAULT '',  PRIMARY KEY (student_id));CREATE TABLE enrollment(  course_id BIGINT UNSIGNED NOT NULL,  student_id BIGINT UNSIGNED NOT NULL,  PRIMARY KEY (course_id, student_id),  FOREIGN KEY (course_id) REFERENCES course(course_id)                      ON DELETE RESTRICT,    FOREIGN KEY (student_id) REFERENCES student(student_id)                      ON DELETE RESTRICT  );INSERT INTO course (course_name) VALUES ('Java Programming in the Sun');INSERT INTO student (student_name) VALUES ('Peter Wansch');

DB2와 MySQL용 데이터 소스 정의하기

더 많은 코드를 작성하기 전에 웹 서비스를 전개할 애플리케이션 서버에서 데이터베이스에 액세스 할 수 있는지를 확인해야 한다.

브라우저에http://localhost:9060/ibm/console/을 입력하여 WebSphere Administrative로 로그인 한다.

MySQL 데이터베이스로의 액세스 설정

Guided Activities를 확장하고Connecting to a database를 선택한다. 마법사를 통해 다음 단계를 수행한다:
  1. 안전한 데이터베이스 액세스를 위해 크리덴셜을 설정한다. 다음과 같은 속성을 사용하여 새로운 J2C 인증 앨리어스를 만든다.
    • Alias:mysqlta
    • User ID:ta
    • Password:password
    • Description:TA MySQL database user
  2. JDBC 드라이버를 설정한다. 다음 속성을 사용하여 새로운 JDBC 프로바이더를 만든다:
    • Name:MySQL JDBC Provider
    • Description:MySQL JDBC Provider
    • Class path:${MYSQL_JDBC_DRIVER_PATH}/mysql-connector-java-3.1.11-bin.jar
    • Implementation class name:com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
  3. WebSphere 변수를 설정한다. 다음 속성으로 새로운 WebSphere 변수를 만든다:
    • Name:MYSQL_JDBC_DRIVER_PATH
    • Value:C:\Program Files\MySQL\mysql-connector-java-3.1.11
  4. MySQL JDBC Provider용 데이터 소스를 설정한다. MySQL JDBC Provider를 클릭한다.Additional Properties밑에, link Data sources를 클릭한다.New를 선택하여 다음과 같은 매개변수를 가진 새로운 데이터 소스를 만든다:
    • Name:MySQLTA
    • JNDI name:jdbc/mysqlta
    • Use this data source in container managed persistence (CMP)선택
    • Description:MySQL TA JDBC Datasource
    • 데이터 소스 헬퍼(com.ibm.websphere.rsadapter.GenericDataStoreHelper) 가 선택되었는지를 확인한다.
    • Component-managed authentication alias:mysqlta
    • Authentication alias for XA recovery밑에Select Use component-managed authentication alias가 선택되었는지를 확인한다.
  5. OK를 클릭하여 데이터 소스를 저장하여 추가 속성들을 편집할 수 있도록 한다.Additional Properties밑에, 다음과 같은 Connection 풀 매개변수들이 설정되었는지를 확인한다.
    • Connection timeout:180
    • Reap time:180
    • Unused timeout:1800
    • Aged timeout:3600
    • Purge policy:FailingConnectionOnly
  6. Custom Properties를 추가한다:
    • databaseName=ta
    • port=3306
    • serverName=localhost

주:Aged timeout을 3600 초 (1 시간)으로 설정하는 것이 중요하다. MySQL 서버는 지정된 비활성 기간 후에 연결을 닫기 때문이다. (MySQL 서버 타임아웃 설정 참조)

설정을 저장하고Test Connection을 클릭한다. 데이터 소스를 정확히 설정했다면 연결이 성공적으로 이루어질 것이다. 서버를 재시작 할 필요는 없다.

DB2 데이터베이스로 액세스 설정하기

다음 과정을 반복하여 DB2 데이터 소스를 설정한다.
  1. 안전한 데이터베이스 액세스를 위해 크리덴셜을 설정한다. 다음과 같은 속성을 사용하여 새로운 J2C 인증 앨리어스를 만든다:
    • Alias:db2ta
    • User ID:db2admin
    • Password:<password>
    • Description:TA DB2 database user
  2. JDBC 드라이버를 설정한다. 다음 속성을 사용하여 새로운 JDBC 프로바이더를 만든다:
    • Database type:DB2
    • Provider type:DB2 Universal JDBC Provider
    • Implementation type:XA data source
  3. WebSphere 변수를 설정한다. 다음 속성으로 새로운 WebSphere 변수를 만든다:
    • DB2UNIVERSAL_JDBC_DRIVER_PATH = C:\Program Files\IBM\SQLLIB\java
    • DB2UNIVERSAL_JDBC_DRIVER_NATIVEPATH = C:\Program Files\IBM\SQLLIB\BIN
  4. DB2 JDBC Provider용 데이터 소스를 설정한다. DB2 JDBC Provider를 클릭한다.Additional Properties밑에, link Data sources를 클릭한다.New를 선택하여 다음과 같은 매개변수를 가진 새로운 데이터 소스를 만든다:
    • name=DB2TA
    • jndiName=jdbc/db2ta
    • description=DB2 TA JDBC Datasource
    • 데이터 소스 헬퍼 클래스로서DB2 Universal data store helper를 선택한다.
    • 컴포넌트 관리형 인증 앨리어스로서 db2ta를 설정한다.
    • Database name=ta
    • Driver type=2
  5. OK를 클릭하여 데이터 소스를 저장하여 추가 속성들을 편집할 수 있도록 한다.
  6. 다음과 같은Custom Properties를 추가한다:
    • currentSchema=ta
    • Server 이름과 Port 넘버에 있는 엔트리를 삭제하여 빈 공간으로 만든다.

설정을 저장하고Test Connection을 클릭한다. 데이터 소스를 정확히 설정했다면 연결이 성공적으로 이루어질 것이다. 서버를 재시작 할 필요는 없다.

Data Access Objects Strategy용 팩토리

데이터베이스 설정을 캡슐화 하려면 com.ibm.ta.webservice 패키지에 유틸리티 클래스 DBConfig를 만든다. (Listing 11) 이 데이터베이스 설정에는 실제 데이터소스 JNDI 이름이 있는 웹 컴포넌트 전개 디스크립터와 연결된 리소스 레퍼런스가 포함되어 있다. 따라서 웹 애플리케이션이 JNDI 이름을 하드 코딩 할 필요가 없고, 전개 시 웹 서비스가 각각 DB2나 MySQL에 대해 설정될 수 있다.


Listing 11: DBConfig.java
package com.ibm.ta.webservice;public class DBConfig { // List of supported databases public static final int DB2 = 1; public static final int MYSQL = 2; private String resRef; private int dbType; public DBConfig(String resRef, int dbType) {  this.resRef = resRef;  this.dbType = dbType; } public int getDbType() {  return dbType; } public String getResRef() {  return resRef; }}

시스템 예외나 애플리케이션 스팩의 예외가 발생하면 웹 서비스는 TAServiceException이라고 하는 서비스 스팩의 예외를 리턴한다. TAServiceException 클래스를 java.io.Serializable을 구현하고 예외를 확장하는 com.ibm.ta.webservice 패키지에 추가한다. (Listing 12)


Listing 12: TAServiceException.java
package com.ibm.ta.webservice;import java.io.Serializable;public class TAServiceException extends Exception implements Serializable { public TAServiceException(String message, Throwable cause) {  super(message, cause); } public TAServiceException(String message) {  super(message); }}

ServiceLocator 라고 하는 헬퍼 클래스를 만들어서 리소스 레퍼런스에서 데이터 소스를 가져와야 한다. 네임드 리소스를 찾으려면 J2EE 컴포넌트는 먼저 객체를 만들고 InitialContext에 있는 환경 네이밍 콘텍스트를 찾는다. (리소스 레퍼런스의 이름으로 잘린 java:comp/env 밑에 있다.) 퍼포먼스 때문에 초기 콘텍스트와 데이터 소스를 캐싱해야 한다. 또한 ServiceLocator를 com.ibm.ta.webservice 패키지에 추가한다. (Listing 13)


Listing 13: ServiceLocator.java
package com.ibm.ta.webservice;import java.util.Collections;import java.util.HashMap;import java.util.Map;import javax.naming.InitialContext;import javax.naming.NamingException;import javax.sql.DataSource;import javax.transaction.UserTransaction;public class ServiceLocator { private InitialContext ic; private Map cache; private static ServiceLocator sl = null; private ServiceLocator() throws TAServiceException {  cache = Collections.synchronizedMap(new HashMap());  try {   ic = new InitialContext();  } catch (NamingException ne) {   throw new TAServiceException(ne.getMessage(), ne);  } } static public void initializeInstance() throws TAServiceException {  if (sl == null) {   sl = new ServiceLocator();  } } static public ServiceLocator getInstance() {  return sl; } public DataSource getDataSource(String dataSourceName)   throws TAServiceException {  DataSource dataSource = null;  try {   if (cache.containsKey(dataSourceName)) {    dataSource = (DataSource) cache.get(dataSourceName);   } else {    dataSource = (DataSource) ic.lookup(dataSourceName);    cache.put(dataSourceName, dataSource);   }  } catch (NamingException ne) {   throw new TAServiceException(ne.getMessage(), ne);  }  return dataSource; }}

Abstract Factory와 Factory Method 패턴을 채택하여 DAO 패턴을 유연하게 만든다. DB2와 MySQL을 지원하기 위해 구체적 DAO 팩토리에서 상속 받고 구현된 추상 클래스인 DAOFactory 라고 하는 기본적인 DAO 팩토리를 만든다. 데이터베이스 설정에 기반하여 클라이언트는 DB2DAOFactory 같은 구체적 DAO 팩토리 구현을 가져와서, 이를 사용하여 특정 구현과 작동하는 구체적 DAO를 가져올 수 있다.

그림 5는 팩토리 메소드를 사용하는 DB2 DAO의 클래스 다이어그램이다.


그림 5. DB2 DAO의 클래스 다이어그램
사용자 삽입 이미지

com.ibm.ta.dao 패키지에 DAOFactory 추상 클래스를 만든다. (Listing 14)


Listing 14: DAOFactory.java
package com.ibm.ta.dao;import java.sql.Connection;import java.sql.SQLException;import javax.sql.DataSource;import com.ibm.ta.webservice.DBConfig;import com.ibm.ta.webservice.ServiceLocator;import com.ibm.ta.webservice.TAServiceException;import com.ibm.ta.dao.db2.DB2DAOFactory;import com.ibm.ta.dao.mysql.MySQLDAOFactory;public abstract class DAOFactory { private DBConfig dbConfig; public abstract CourseDAO getCourseDAO(); public abstract StudentDAO getStudentDAO(); public abstract EnrollmentDAO getEnrollmentDAO(); public static DAOFactory getDAOFactory(DBConfig dbConfig) {  switch (dbConfig.getDbType()) {  case DBConfig.DB2:   return new DB2DAOFactory(dbConfig);  case DBConfig.MYSQL:   return new MySQLDAOFactory(dbConfig);  default:   return null;  } } public DAOFactory(DBConfig dbConfig) {  this.dbConfig = dbConfig; } // Create database connections from the values in dbConfig public Connection createConnection() throws DAOException {  try {   String str = "java:comp/env/" + dbConfig.getResRef();   DataSource dataSource = (DataSource) ServiceLocator.getInstance()     .getDataSource(str);   return dataSource.getConnection();  } catch (SQLException se) {   throw new DAOException(se.getMessage(), se);  } catch (TAServiceException tase) {   throw new DAOException(tase.getMessage(), tase);  } }}

이 클래스를 저장하면 다음과 같은 에러들이 Problems 뷰에 나타난다.

  • The import com.ibm.ta.dao.db2 cannot be resolved
  • The import com.ibm.ta.dao.mysql cannot be resolved
  • DB2DAOFactory cannot be resolved or is not a type
  • MySQLDAOFactory cannot be resolved or is not a type

DB2 (DB2DAOFactory,Listing 15)와 MySQL (MySQLDAOFactory,Listing 16)를 지원하는 구체적 DAO 팩토리를 만들면 이것은 사라진다. 클래스를 만들어 보자.


Listing 15: DB2DAOFactory.java
package com.ibm.ta.dao.db2;import com.ibm.ta.dao.CourseDAO;import com.ibm.ta.dao.DAOFactory;import com.ibm.ta.dao.EnrollmentDAO;import com.ibm.ta.dao.StudentDAO;import com.ibm.ta.webservice.DBConfig;public class DB2DAOFactory extends DAOFactory { private DBConfig dbConfig; public DB2DAOFactory(DBConfig dbConfig) {  super(dbConfig); } public CourseDAO getCourseDAO() {  return new DB2CourseDAO(this); } public StudentDAO getStudentDAO() {  return new DB2StudentDAO(this); } public EnrollmentDAO getEnrollmentDAO() {  return new DB2EnrollmentDAO(this); }}


Listing 16: MySQLDAOFactory.java
package com.ibm.ta.dao.mysql;import com.ibm.ta.dao.CourseDAO;import com.ibm.ta.dao.DAOFactory;import com.ibm.ta.dao.EnrollmentDAO;import com.ibm.ta.dao.StudentDAO;import com.ibm.ta.webservice.DBConfig;public class MySQLDAOFactory extends DAOFactory { private DBConfig dbConfig; public MySQLDAOFactory(DBConfig dbConfig) {  super(dbConfig); } public CourseDAO getCourseDAO() {  return new MySQLCourseDAO(this); } public StudentDAO getStudentDAO() {  return new MySQLStudentDAO(this); } public EnrollmentDAO getEnrollmentDAO() {  return new MySQLEnrollmentDAO(this); }}

그런 다음, 실제 JDBC 코드와 SQL 문을 포함하고 있는 다음과 같은 구체적 DAO 클래스를 구현하여 데이터베이스에 액세스 한다.

먼저 com.ibm.ta.dao에 DAOUtil (Listing 17) 클래스를 만든다. 이 클래스에는 리소스를 릴리스 할 때 유용한 헬퍼 함수가 포함되어 있다.


Listing 17: DAOUtil.java
package com.ibm.ta.dao;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;public class DAOUtil { public static void closeResources(ResultSet rs, Statement stmt1,   Statement stmt2, Connection con) {  try {   if (rs != null) {    rs.close();   }  } catch (SQLException e) {   // Ignore  }  try {   if (stmt1 != null) {    stmt1.close();   }  } catch (SQLException e) {   // Ignore  }  try {   if (stmt2 != null) {    stmt2.close();   }  } catch (SQLException e) {   // Ignore  }  try {   if (con != null) {    con.close();   }  } catch (SQLException e) {   // Ignore  } }}


Listing18: DB2CourseDAO.java
package com.ibm.ta.dao.db2;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.ArrayList;import java.util.List;import com.ibm.ta.dao.CourseDAO;import com.ibm.ta.dao.DAOException;import com.ibm.ta.dao.DAOParameterException;import com.ibm.ta.dao.DAOUtil;import com.ibm.ta.webservice.Course;public class DB2CourseDAO implements CourseDAO { private DB2DAOFactory factory; public DB2CourseDAO(DB2DAOFactory factory) {  this.factory = factory; } public Course[] selectCourses() throws DAOException {  Connection con = null;  PreparedStatement ps = null;  ResultSet rs = null;  List results = new ArrayList();  try {   con = factory.createConnection();   ps = con.prepareStatement("SELECT course_id, course_name from ta.course");   rs = ps.executeQuery();   while (rs.next()) {    Course course = new Course(rs.getLong(1), rs.getString(2));    results.add(course);   }  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(rs, ps, null, con);  }  return (Course[]) results.toArray(new Course[results.size()]); } public void insertCourse(String name) throws DAOException,   DAOParameterException {  Connection con = null;  PreparedStatement ps = null;  // Check the parameter  if (name == null || name.trim().length() == 0) {   throw new DAOParameterException(     "Parameter course name is invalid or null");  }  try {   con = factory.createConnection();   ps = con.prepareStatement("INSERT INTO course (course_name) VALUES (?)");   ps.setString(1, name);   ps.executeUpdate();  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(null, ps, null, con);  } }}


Listing 19: DB2StudentDAO.java
package com.ibm.ta.dao.db2;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.ArrayList;import java.util.List;import com.ibm.ta.dao.StudentDAO;import com.ibm.ta.dao.DAOException;import com.ibm.ta.dao.DAOParameterException;import com.ibm.ta.dao.DAOUtil;import com.ibm.ta.webservice.Student;public class DB2StudentDAO implements StudentDAO { private DB2DAOFactory factory; public DB2StudentDAO(DB2DAOFactory factory) {  this.factory = factory; } public Student[] selectStudents() throws DAOException {  Connection con = null;  PreparedStatement ps = null;  ResultSet rs = null;  List results = new ArrayList();  try {   con = factory.createConnection();   ps = con.prepareStatement("SELECT student_id, student_name from ta.STUDENT");   rs = ps.executeQuery();   while (rs.next()) {    Student student = new Student(rs.getLong(1), rs.getString(2));    results.add(student);   }  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(rs, ps, null, con);  }  return (Student[]) results.toArray(new Student[results.size()]); } public void insertStudent(String name) throws DAOException,   DAOParameterException {  Connection con = null;  PreparedStatement ps = null;  // Check the parameter  if (name == null || name.trim().length() == 0) {   throw new DAOParameterException(     "Parameter student name is invalid or null");  }  try {   con = factory.createConnection();   ps = con.prepareStatement("INSERT INTO student (student_name) VALUES (?)");   ps.setString(1, name);   ps.executeUpdate();  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(null, ps, null, con);  } }}


Listing 20: DB2EnrollmentDAO.java
package com.ibm.ta.dao.db2;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.ArrayList;import java.util.List;import com.ibm.ta.dao.EnrollmentDAO;import com.ibm.ta.dao.DAOException;import com.ibm.ta.dao.DAOUtil;import com.ibm.ta.webservice.Course;import com.ibm.ta.webservice.Student;public class DB2EnrollmentDAO implements EnrollmentDAO { private DB2DAOFactory factory; public DB2EnrollmentDAO(DB2DAOFactory factory) {  this.factory = factory; } public void insertEnrollment(long studentID, long courseID)   throws DAOException {  Connection con = null;  PreparedStatement ps = null;  try {   con = factory.createConnection();   ps = con.prepareStatement   ("INSERT INTO ta.enrollment (course_id, student_id) VALUES (?, ?)");   ps.setLong(1, studentID);   ps.setLong(2, courseID);   ps.executeUpdate();  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(null, ps, null, con);  } } public Student[] getEnrolledStudents(long courseID) throws DAOException {  Connection con = null;  PreparedStatement ps = null;  ResultSet rs = null;  List results = new ArrayList();  try {   con = factory.createConnection();   ps = con.prepareStatement   ("SELECT s.student_id, s.student_name from student AS s,    enrollment AS e where s.student_id = e.student_id and course_id = ?");   ps.setLong(1, courseID);   rs = ps.executeQuery();   while (rs.next()) {    Student student = new Student(rs.getLong(1), rs.getString(2));    results.add(student);   }  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(rs, ps, null, con);  }  return (Student[]) results.toArray(new Student[results.size()]); } public Course[] getCourseEnrollments(long studentID) throws DAOException {  Connection con = null;  PreparedStatement ps = null;  ResultSet rs = null;  List results = new ArrayList();  try {   con = factory.createConnection();   ps = con.prepareStatement   ("SELECT c.course_id, c.course_name from course AS c,    enrollment AS e where c.course_id = e.course_id and e.student_id = ?");   ps.setLong(1, studentID);   rs = ps.executeQuery();   while (rs.next()) {    Course course = new Course(rs.getLong(1), rs.getString(2));    results.add(course);   }  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(rs, ps, null, con);  }  return (Course[]) results.toArray(new Course[results.size()]); }}


Listing 21: MySQLCourseDAO.java
package com.ibm.ta.dao.mysql;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.ArrayList;import java.util.List;import com.ibm.ta.dao.CourseDAO;import com.ibm.ta.dao.DAOException;import com.ibm.ta.dao.DAOParameterException;import com.ibm.ta.dao.DAOUtil;import com.ibm.ta.webservice.Course;public class MySQLCourseDAO implements CourseDAO { private MySQLDAOFactory factory; public MySQLCourseDAO(MySQLDAOFactory factory) {  this.factory = factory; } public Course[] selectCourses() throws DAOException {  Connection con = null;  PreparedStatement ps = null;  ResultSet rs = null;  List results = new ArrayList();  try {   con = factory.createConnection();   ps = con.prepareStatement("SELECT course_id, course_name from course");   rs = ps.executeQuery();   while (rs.next()) {    Course course = new Course(rs.getLong(1), rs.getString(2));    results.add(course);   }  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(rs, ps, null, con);  }  return (Course[]) results.toArray(new Course[results.size()]); } public void insertCourse(String name) throws DAOException,   DAOParameterException {  Connection con = null;  PreparedStatement ps = null;  // Check the parameter  if (name == null || name.trim().length() == 0) {   throw new DAOParameterException(     "Parameter course name is invalid or null");  }  try {   con = factory.createConnection();   ps = con.prepareStatement("INSERT INTO course (course_name) VALUES (?)");   ps.setString(1, name);   ps.executeUpdate();  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(null, ps, null, con);  } }}


Listing 22: MySQLStudentDAO.java
package com.ibm.ta.dao.mysql;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.ArrayList;import java.util.List;import com.ibm.ta.dao.StudentDAO;import com.ibm.ta.dao.DAOException;import com.ibm.ta.dao.DAOParameterException;import com.ibm.ta.dao.DAOUtil;import com.ibm.ta.webservice.Student;public class MySQLStudentDAO implements StudentDAO { private MySQLDAOFactory factory; public MySQLStudentDAO(MySQLDAOFactory factory) {  this.factory = factory; } public Student[] selectStudents() throws DAOException {  Connection con = null;  PreparedStatement ps = null;  ResultSet rs = null;  List results = new ArrayList();  try {   con = factory.createConnection();   ps = con.prepareStatement("SELECT student_id, student_name from student");   rs = ps.executeQuery();   while (rs.next()) {    Student student = new Student(rs.getLong(1), rs.getString(2));    results.add(student);   }  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(rs, ps, null, con);  }  return (Student[]) results.toArray(new Student[results.size()]); } public void insertStudent(String name) throws DAOException,   DAOParameterException {  Connection con = null;  PreparedStatement ps = null;  // Check the parameter  if (name == null || name.trim().length() == 0) {   throw new DAOParameterException(     "Parameter student name is invalid or null");  }  try {   con = factory.createConnection();   ps = con.prepareStatement("INSERT INTO student (student_name) VALUES (?)");   ps.setString(1, name);   ps.executeUpdate();  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(null, ps, null, con);  } }}


Listing 23: MySQLEnrollmentDAO.java
package com.ibm.ta.dao.mysql;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.ArrayList;import java.util.List;import com.ibm.ta.dao.EnrollmentDAO;import com.ibm.ta.dao.DAOException;import com.ibm.ta.dao.DAOUtil;import com.ibm.ta.webservice.Course;import com.ibm.ta.webservice.Student;public class MySQLEnrollmentDAO implements EnrollmentDAO { private MySQLDAOFactory factory; public MySQLEnrollmentDAO(MySQLDAOFactory factory) {  this.factory = factory; } public void insertEnrollment(long studentID, long courseID)   throws DAOException {  Connection con = null;  PreparedStatement ps = null;  try {   con = factory.createConnection();   ps = con.prepareStatement   ("INSERT INTO enrollment (course_id, student_id) VALUES (?, ?)");   ps.setLong(1, studentID);   ps.setLong(2, courseID);   ps.executeUpdate();  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(null, ps, null, con);  } } public Student[] getEnrolledStudents(long courseID) throws DAOException {  Connection con = null;  PreparedStatement ps = null;  ResultSet rs = null;  List results = new ArrayList();  try {   con = factory.createConnection();   ps = con.prepareStatement   ("SELECT s.student_id, s.student_name from student AS s,    enrollment AS e where s.student_id = e.student_id and course_id = ?");   ps.setLong(1, courseID);   rs = ps.executeQuery();   while (rs.next()) {    Student student = new Student(rs.getLong(1), rs.getString(2));    results.add(student);   }  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(rs, ps, null, con);  }  return (Student[]) results.toArray(new Student[results.size()]); } public Course[] getCourseEnrollments(long studentID) throws DAOException {  Connection con = null;  PreparedStatement ps = null;  ResultSet rs = null;  List results = new ArrayList();  try {   con = factory.createConnection();   ps = con.prepareStatement   ("SELECT c.course_id, c.course_name from course AS c,    enrollment AS e where c.course_id = e.course_id and e.student_id = ?");   ps.setLong(1, studentID);   rs = ps.executeQuery();   while (rs.next()) {    Course course = new Course(rs.getLong(1), rs.getString(2));    results.add(course);   }  } catch (SQLException sqle) {   throw new DAOException(sqle.getMessage(), sqle);  } finally {   DAOUtil.closeResources(rs, ps, null, con);  }  return (Course[]) results.toArray(new Course[results.size()]); }}

이 예제의 SQL 문은 너무나 간단하여 구체적 DB2와 MySQL DAO 구현에 있는 대부분의 JDBC 코드와 SQL 문은 동일하다. DB2와 MySQL 모두를 지원하는 애플리케이션에서 코드 중복을 피해서 클래스 관리를 더욱 쉽게 할 수 있다. 공통 코드를 슈퍼클래스에 유일 코드를 데이터베이스 스팩의 하위 클래스에 두는 것을 권장한다. 대부분 SQL과 JDBC 같은 표준은 관계형 데이터베이스들 간 차이점을 다룬다. 실제로 데이터베이스 스팩의 클래스를 필요로 하는 데이터베이스들간 차이는 미묘하기 때문에 클래스 디자인에서 이것을 예상하는 것이 좋다.

이 부분에서 Problems 뷰에서 모든 문제들은 사라진다.

웹 서비스 구현하기

그런 다음, 웹 서비스 클라이언트가 호출할 수 있는 메소드들로 구성된 웹 서비스 인터페이스 정의를 만든다. 두 가지 접근 방식이 있다. Java-to-WSDL 과 WSDL-to-Java 이다. 일반적으로 여러분은 후자를 사용한다. 즉 웹 서비스 인터페이스의 상세를 설명하는 WSDL 문서로 시작하여 상응하는 자바 인터페이스를 만드는 것이다. 이는 웹 서비스 진화가 더 간단하고 기존 클라이언트들과의 상호 운용성을 관리한다. 바로 이것 때문에 이것을 선택하는 것일 수도 있다.

Java to WSDL은 더 쉬운 방식이다. 서버 엔드포인트 인터페이스와 구현 클래스로 시작한다. 하지만 서비스 인터페이스를 변경하면 상응하는 WSDL 문서도 변경해야 한다. 때문에 서비스의 클라이언트도 다시 작성해야 한다. 한편, 새로운 메소드를 계속 추가하는 한 Java to WSDL 방식이 안전하다. 이 예제에서는 간단히 하기 위해 Java to WSDL 방식을 사용한다.

사용자 삽입 이미지
실제로, 웹 서비스 개발자들은 개발 과정 중에 Java-to-WSDL과 WSDL-to-Java 모두를 사용해야 한다. 기능을 추가하거나 변경하기 위해서거나 WSDL의 특성을 변화시켜 벤더가 제공한 툴링 간 차이점을 활용하려는 것이다. (예를 들어, Microsoft .NET SOAP 툴 대 IBM SOAP 툴의 차이점을 모두 수용한다.)

인터페이스 엔드포인트 유형처럼 JAX-RPC 서비스 엔드포인트를 선택한다. 비즈니스 로직은 웹 티어 안에 완전히 포함될 수 있기 때문이다. 비즈니스 로직이 Enterprise Java Beans™ (EJB™) 컴포넌트를 사용했다면 EJB 엔드포인트를 선택했을 것이다.

웹 서비스는 웹 컨테이너에서 실행되기 때문에 트랜잭션 콘텍스트는 지정되지 않고 JTA를 사용하여 트랜잭션을 분리해야 한다. 포팅을 위해 애플리케이션 클라이언트는 웹 티어를 통해 트랜잭션 작업을 위임해야 한다.

서비스 인터페이스 디자인에는 강력한 결합이 중요하다. 책임 소재는 긴밀이 연관되어야 하고 관련 메소드들이 완벽히 포함되어야 한다. 다시 말해서, 서비스 인터페이스에 너무나 많은 기능을 추가하지 말아야 하며 모든 메소드들은 완벽하고 깨끗한 서비스 태스크를 수행해야 한다. 이상적으로는, 서비스 인터페이스에서 제공하는 기능들에는 완전한 것만 포함되어야 한다. 지원되는 연산들은 밀접히 연관되어야 하고 관련 보완 연산들을 포함하고 있어야 한다.

예를 들어, enroll 메소드를 추가한다면 여기에는 등록을 할 수 있는 모든 매개변수들이 포함되어야 하고 메소드를 호출하기 전에 각 정보 조각을 개별적으로 보내는 다른 메소드들에 의존하지 말아야 한다. 웹 서비스들은 STATELESS이다. 상태 관리는 애플리케이션 레이어에서 구현되어야 한다.

이제 com.ibm.ta.webservice 패키지에 서비스 엔드포인트 인터페이스 TAService_SEI를 만들 준비가 되었다. (Listing 24)


Listing 24: TAService_SEI.java
package com.ibm.ta.webservice;import java.rmi.Remote;import java.rmi.RemoteException;public interface TAService_SEI extends Remote { public Course[] listCourses() throws RemoteException, TAServiceException; public Student[] listStudents() throws RemoteException, TAServiceException; public void enroll(long studentID, long courseID) throws RemoteException,   TAServiceException;}

웹 서비스 인터페이스는 메소드 호출을 노출하고 호출과 연관된 매개변수들은 SOAP 메시지의 일부로서 보내진다. SOAP 메시지의 일부가 되려면 XML로 매핑되어야 한다. 다음 사항만을 사용하는 것이 좋다.

  • 자바 프리머티브 유형(int와 Integer 같은 상응하는 래퍼 클래스와 함께)
  • 표준 자바 클래스(String 또는 Date)
  • 이들 유형의 어레이
  • 빈을 가진 사용자 클래스 (속성)

예제에서 전송 객체로서 이전에 만들었던 Course와 Student 빈을 사용한다.

이 서비스 메소드는 복구 가능한, 서비스 스팩의 예외를 던진다. 예를 들어, 학생이 이미 등록되었다는 것을 나타낸다. 클라이언트에서 JAX-RPC 런타임이 복구 불가능한 시스템 예외를 만나면 RemoteException을 던져야 한다.

웹 서비스를 전개할 때 DB2나 MySQL 백엔드 데이터베이스가 사용되는지 여부와 리소스 레퍼런스의 이름도 지정해야 한다. 서버에 있는 코드를 변경하거나 속성 파일들을 편집할 필요는 없다. 따라서 다음 두 개의 매개변수들을 웹 서비스 전개 디스크립터에 저장한다.

  • dbtype=1
  • resref=ref/ta

이러한 속성들이 사용자 선호도에 따라서 WebSphere 관리 콘솔을 사용하여 업데이트 될 수 있다. (dbtype=1(DB2), dbtype=2(MySQL))

이러한 웹 서비스에 사용될 실제 데이터 소스를 참조할 리소스 레퍼런스를 전개자가 지정할 수 있도록 하면 WebSphere 환경에 이쓴 데이터 소스로의 액세스를 정의할 때 상당히 유연해진다. 예를 들어, 리소스 레퍼런스를 사용하면 전개자는 같은 JNDI 데이터 소스를 가리키면서 다른 트랜잭션 격리를 지정하고, 보다 세분화된 제어를 수행한다.

웹 애플리케이션의 모든 인스턴스는 ServletContext 인스턴스를 갖고 있다. (웹 애플리케이션이 여러 자바™ 가상 머신(JVM™)을 통해 분산되지 않을 경우). 콘텍스트는 애플리케이션이 로딩될 때 초기화 된다. 이 콘텍스트에 대한 초기화 매개변수는 전개 디스크립터에서 서정될 수 있고 웹 서비스는 이러한 초기화 매개변수들을 가져올 수 있다.

원격 인터페이스를 구현하는 TAService 클래스를 코딩 할 것이다. 지금까지 DBConfig 객체를 초기화 하는 것을 설명했다. 서비스 메소드는 아직 구현하지 않을 것이다. TAService 클래스를 com.ibm.ta.webservice에 만든다. (Listing 25)


Listing 25: TAService.java
package com.ibm.ta.webservice;import java.rmi.RemoteException;import javax.servlet.ServletContext;import javax.transaction.SystemException;import javax.transaction.UserTransaction;import javax.xml.rpc.ServiceException;import javax.xml.rpc.server.ServiceLifecycle;import javax.xml.rpc.server.ServletEndpointContext;import com.ibm.ta.dao.CourseDAO;import com.ibm.ta.dao.DAOFactory;import com.ibm.ta.dao.EnrollmentDAO;import com.ibm.ta.dao.StudentDAO;public class TAService implements TAService_SEI, ServiceLifecycle { private DBConfig dbConfig; private ServletContext servletContext; public void init(Object arg) throws ServiceException {  String resRef = null;  int dbType = 0;  servletContext = ((ServletEndpointContext) arg).getServletContext();  // Get and check context parameters  resRef = servletContext.getInitParameter("resref");  if (resRef == null || resRef.trim().length() == 0) {   throw new ServiceException("Parameter resref is null or invalid");  }  try {   dbType = Integer     .parseInt(servletContext.getInitParameter("dbtype"));  } catch (NumberFormatException nfe) {   throw new ServiceException("Parameter dbtype is not numeric");  }  if (dbType != DBConfig.DB2 && dbType != DBConfig.MYSQL) {   throw new ServiceException(     "Parameter dbtype has to be 1 (DB2) or 2 (MySQL)");  }  // Save dbConfig as instance variable  dbConfig = new DBConfig(resRef, dbType);  try {   ServiceLocator.initializeInstance();  } catch (TAServiceException se) {   servletContext.log(se.getMessage(), se);   throw new ServiceException(se.getMessage());  } } public void destroy() {  // Nothing to do } public Course[] listCourses() throws RemoteException, TAServiceException {  return null; } public Student[] listStudents() throws RemoteException, TAServiceException {  return null; } public void enroll(long studentID, long courseID) throws RemoteException,   TAServiceException { }}

init 메소드를 살펴보자. JAX-RPC 서비스 엔드포인트의 라이프사이클을 정의하는 서비스라이프사이클 인터페이스를 구현한다. 서비스 엔드포인트 인스턴스가 만들어진 후에 JAX-RPC 런타임 시스템은 init 메소드를 호출한다. init 메소드를 사용하여 데이터베이스 설정에 대한 속성을 얻고 서비스 로케이터를 초기화 한다. init 메소드로 전달된 인자는 ServletEndpointContext 유형의 것이다. 이 예제에서, 여러분은 서블릿 콘텍스트와 dbConfig를 인스턴스 변수에 저장할 것이다. 각각 나중에 메시지를 기록하고 데이터베이스 설정 정보를 얻을 때 사용할 것이다. 인스턴스 변수는 웹 서비스 시작 시 초기화 될 수 있는 객체를 캐싱할 때 사용되고 웹 서비스의 수명 동안 사용될 수 있다. 인스턴스 변수로의 액세스는 쓰레드 보안이 되어야 한다. 다중 쓰레드가 여기에 동시에 액세스 할 것이기 때문이다.

그런 다음, listCourses 메소드를 만든다. (Listing 27) listCourse에서 코스 DAO를 사용하여 모든 코스들을 선택한다. 이 호출은 JTA 트랜잭션 범위 내에서 수행되어야 한다. 이는 데이터베이스 트랜잭션에서 DAO에 JDBC 호출을 자동으로 나열한다. JTA 사용자 트랜잭션을 얻으려면Listing 26의 메소드를 ServiceLocator 클래스에 추가한다.


Listing 26: ServiceLocator.getUserTransaction()
public UserTransaction getUserTransaction() throws TAServiceException {  try {   return (UserTransaction) ic.lookup("java:comp/UserTransaction");  } catch (NamingException ne) {   throw new TAServiceException(ne.getMessage(), ne);  } }

UserTransaction은 시스템에서 제공하는 네임드 객체로서 트랜잭션을 직접 구분해야 하는 웹 티어에서 실행되는 컴포넌트에서 사용된다. 사용자 트랜잭션을 시작할 방법이 있으니 TAService에 있는 listCourse는 다음과 같다. (Listing 27)


Listing 27: TAService.listCourses().java
public Course[] listCourses() throws RemoteException, TAServiceException {  Course[] courses = null;  UserTransaction ut = null;  // Access the database within a transaction  try {   ut = ServiceLocator.getInstance().getUserTransaction();   ut.begin();   DAOFactory daoFactory = DAOFactory.getDAOFactory(dbConfig);   CourseDAO courseDAO = daoFactory.getCourseDAO();   courses = courseDAO.selectCourses();   // End the transaction   ut.commit();  } catch (Exception e) {   try {    if (ut != null) {     ut.rollback();    }   } catch (SystemException se) {    // Only throw the first failure exception   }   servletContext.log(e.getMessage(), e);   throw new TAServiceException(e.getMessage(), e);  }  return courses; }

이 디자인의 장점은 웹 서비스에 어떤 데이터베이스 스팩의 코드가 없고, 데이터 액세스 객체에 어떤 트랜잭션 관리도 없다는 점이다. 이로서 디자인이 유연하고 관리하기도 편리해진다.Listing 28은 listStudents와 enroll에 대한 코드이다.


Listing 28: TAService.listStudents(), TAService.enroll()
public Student[] listStudents() throws RemoteException, TAServiceException {  Student[] students = null;  UserTransaction ut = null;  // Access the database within a transaction  try {   ut = ServiceLocator.getInstance().getUserTransaction();   ut.begin();   DAOFactory daoFactory = DAOFactory.getDAOFactory(dbConfig);   StudentDAO studentDAO = daoFactory.getStudentDAO();   students = studentDAO.selectStudents();   // End the transaction   ut.commit();  } catch (Exception e) {   try {    if (ut != null) {     ut.rollback();    }   } catch (SystemException se) {    // Only throw the first failure exception   }   servletContext.log(e.getMessage(), e);   throw new TAServiceException(e.getMessage(), e);  }  return students; } public void enroll(long studentID, long courseID) throws RemoteException,   TAServiceException {  UserTransaction ut = null;  // Access the database within a transaction  try {   ut = ServiceLocator.getInstance().getUserTransaction();   ut.begin();   DAOFactory daoFactory = DAOFactory.getDAOFactory(dbConfig);   EnrollmentDAO enrollmentDAO = daoFactory.getEnrollmentDAO();   enrollmentDAO.insertEnrollment(studentID, courseID);   // End the transaction   ut.commit();  } catch (Exception e) {   try {    if (ut != null) {     ut.rollback();    }   } catch (SystemException se) {    // Only throw the first failure exception   }   servletContext.log(e.getMessage(), e);   throw new TAServiceException(e.getMessage(), e);  } }

웹 서비스에 필요한 모든 코딩을 수행했으니 이를 전개할 차례이다. Project Explorer에서 TAService 클래스를 선택하고 다음 단계에 따라 웹 서비스를 만든다.

  1. 오른쪽 클릭하여New > Other > Web Services > Web Service를 선택하고Next를 클릭한다.
  2. Web service type으로서Java bean Web Service를 선택한다.
  3. Start Web service in Web projectLaunch the Web Services Explorer to publish this Web service to a UDDI registry가 선택되지 않았는지를 확인한다. 프록시를 생성하지 않으며 웹 서비스를 테스트 하지 않을 것이다. (그림 6).
  4. Next를 클릭한다.
  5. Bean이 com.ibm.ta.webservice.TAService인지를 확인한다.Next를 클릭한다.
  6. Service Deployment Configuration을 확인한다.
    • Web service runtime:IBM WebSphere
    • Server:WebSphere v6.0 Server @ localhost (server1)
    • J2EE version:1.4
    • Service project:TAWebService
    • EAR project:TAWebServiceEAR
  7. Next를 클릭한다.
  8. Use an existing service endpoint interface를 선택하고, com.ibm.ta.webservice.TAService_SEI를 선택한 다음Next를 클릭한다.
    주:IRAD를 사용하여 기존 빈에서 서비스 인터페이스를 만들 수 있었다. 이것은 IRAD에서 권고되는 방식이다. 하지만 우리는 디자인 프로세스를 설명해야 하므로 여기에서는 서비스 인터페이스로 시작한다.
  9. Use WSDL-1 MIME attachments exclusively를 지우고Document/LiteralStyle and Use로서 선택되었는지를 확인한다. (그림 7)Finish를 클릭한다.

그림 6. TAService 웹 서비스 구현하기
사용자 삽입 이미지
사용자 삽입 이미지
Document/Literal스타일은 WS-I 프로파일에 순응한다. 이는 선호되고 있는 인코딩 스타일이다. 매개변수 구조는 인코딩을 완전히 기술하는 XML 스키마 문서를 따르는 반면, RPC/encoded 스타일은 상호운용성 문제가 생긴다. RPC 규칙이 알려져야 하기 때문이다. 이 두 가지 스타일 비교는참고자료섹션을 참조하라.

그림 7. TAService 웹 서비스의 매개변수 스타일과 사용
사용자 삽입 이미지

주:여러분이 웹 서비스 코드를 변경하고, 변경이 server1에 퍼블리시 되었는지를 확인할 때 다음과 같이 한다. 콘텍스트 메뉴에서Server탭을 보고,server1을 선택하고,Publish를 선택한다.

마지막 전개 단계는 데이터 소스를 가리키는 리소스 레퍼런스를 추가하고 콘텍스트 매개변수를 설정하는 단계이다.

IRAD에서 TAWebservice 전개 디스크립터에 리소스 레퍼런스를 추가하는 단계는 다음과 같다:
  1. TAWebService 프로젝트에서 Deployment Descriptor 폴더를 더블 클릭한다.
  2. References를 선택하고Add를 클릭한다.
  3. Resource Reference를 선택하고Next를 클릭한다.
  4. 다음 값들을 입력한다:
    • Name:ref/ta
    • Type:javax.sql.DataSource
    • Authentication:Container
    • Sharing scope:Shareable
  5. WebSphere binding섹션에서JNDI name을 jdbc/db2ta로 입력한다.
  6. JAAS Login Configuration처럼Use Default Method를 선택하고Authentication Alias로서<node01>/db2ta를 입력한다. (<node01>는 로컬 WebSphere 노드의 이름이다.) 노드의 이름을 모른다면 관리 콘솔로 로그인 하여JDBC providers > DB2 Universal JDBC Driver Provider (XA) > Data sources > DB2TA > J2EE Connector Architecture (J2C) authentication data entries를 찾는다.
  7. WebSphere extensions섹션에서Isolation Level을 TRANSACTION_READ_COMMITTED로 설정한다.

References탭은그림 8에 디스플레이 된 것처럼 보인다.


그림 8. 리소스 레퍼런스 추가하기
사용자 삽입 이미지

다음은 IRAD의 콘텍스트 매개변수들을 전개 디스크립터에 추가하는 단계이다:

  1. Overview탭을 선택하고Context Parameters가 보일 때까지 스크롤을 내린다.Details를 클릭한다.
  2. Context Parameters리스트를 고정시키고New를 클릭한다.
  3. 다음 값을 입력한다:
    • Parameter name:dbtype
    • Parameter value:1

      다음에도 반복한다:
    • Parameter name:resref
    • Parameter value:ref/ta
  4. Web Deployment Descriptor를 저장한다.Server탭에서 여러분의 서버를 선택하고 오른쪽 클릭하여Publish를 선택한다. 퍼블리싱을 성공했다는 메시지를 받을 것이다.

완료하고 나서 다음과 같이 작업을 확인 및 테스트 할 수 있다.

  1. WebSphere Administrative 콘솔로 로그인 하여Applications > Enterprise Applications에서 TAWebServiceEAR 엔터프라이즈 애플리케이션을 선택하여Map resource references to resources를 클릭하여 리소스 레퍼런스를 확인한다.
    웹 서비스가 전개 및 시작되어 클라이언트 요청을 받을 준비가 되었다.
  2. 다음 URL을 입력하여 웹 서비스가 반응하는지 확인한다:http://localhost:9080/TAWebService/services/TAService
  3. 브라우저에서 다음과 같은 응답을 받아야 한다:

    {http://webservice.ta.ibm.com}TAService
    Hi there, this is a Web service!
주:server1이 디폴트 포트를 사용하지 않는다면Application servers > Ports밑에 있는 WebSphere 관리 콘솔에서 WC_defaulthost의 값을 찾아라.

서버 측에서 해야 할 모든 일을 완성했다. 다음은 Project Explorer에서 새로운 웹 서비스를 선택하는 단계이다.

  1. Web Services와 Services 폴더를 확장한다.
  2. 오른쪽 클릭하여Test with Web Services Explorer를 선택한다.
  3. 웹 서비스를 선택하기 전에을 선택하고 Endpoints 리스트를 고정하고 Add를 클릭하여 다음과 같은 서비스 엔드포인트(그림 9)를추가해야 한다:
    http://localhost:9080/TAWebService/services/TAService

그림 9. Web Services Explorer를 사용한 테스팅
사용자 삽입 이미지

Web Services Explorer Navigator에서 listCourses 또는 listStudents를 선택한 후에 원하는 결과를 얻을 수 있다.

다음에는 IRAD에 웹 서비스용 웹 클라이언트를 만들 수 있다. TAWebService WebContent/wsdl 폴더에 있는 WSDL을 오른쪽 클릭하여Web Services > Generate Client를 선택한다. 하지만 이 예제에서는 클래스와 jar 파일의 컬렉션으로서 전개될 수 있고 자바 런타임이 설치된 어떤 컴퓨터에서도 실행될 수 있는 자바 애플리케이션 클라이언트를 만들 것이다.

자바 애플리케이션 클라이언트 만들기

쉬운 전개와 사용을 위해 Apache Axis를 사용하여 애플리케이션 클라이언트를 작성할 것이다. axis-bin-1_3.zip, Java activation framework jaf-1_0_2-upd2.zip, Java mail javamail-1_3_3_01.zip을 다운로드 하여 디렉토리(C:\)에 압축을 풀어라.

다음은 IRAD에서 자바 애플리케이션을 만드는 단계이다:

  1. Window > Open Perspective > Java를 선택하여 Java Perspective로 전환한다.
  2. File > New > Other > Java > Java Project를 선택하고 프로젝트 이름에TAClient를 입력한다.
  3. Next를 클릭한다.
  4. Finish를 클릭한다.
  5. TAClient 프로젝트를 선택하고, 오른쪽 클릭하여New > Class를 선택하여 메인 클래스를 만든다.
  6. 패키지 이름을com.ibm.ta.client로 입력하고 메인 메소드를 위한 메소드 스텁을 만든다. (그림 10)

그림 10. TAClient 클래스 만들기
사용자 삽입 이미지

클래스 측 바인딩을 만들려면 명령어 프롬프트에서 Axis WSDL-to-Java 툴을 실행한다.

  1. 명령어 윈도우를 열고 TAclient directory by entering (for instance) 다음을 입력하여 워크스페이스 TAclinent 디렉토리를 변경한다:
    cd c:\source\TAClient
  2. 다음 명령어들을 입력하여 클라이언트 측 바인딩을 만든다:
    • set CLASSPATH=C:\jaf-1.0.2\activation.jar;C:\javamail-1.3.3_01\mail.jar; C:\axis-1_3\lib\axis.jar;C:\axis-1_3\lib\jaxrpc.jar;C:\axis-1_3\lib\saaj.jar; C:\axis-1_3\lib\commons-logging-1.0.4.jar;C:\axis-1_3\lib\commons-discovery-0.2.jar; C:\axis-1_3\lib\wsdl4j-1.5.1.jar;%CLASSPATH%
    • java org.apache.axis.wsdl.WSDL2Java C:\source\TAWebService\WebContent\WEB-INF\wsdl\TAService.wsdl

F2를 눌러서 IRAD에서 TA 클라이언트 프로젝트를 리프레시 한다. com.ibm.ta.webservice 패키지의 TAClient 프로젝트에 다음과 같은 클래스들이 만들어진다.

  • Course.java
  • Student.java
  • TAService.java
  • TAServiceException.java
  • TAServiceService.java
  • TAServiceServiceLocator.java
  • TAServiceSoapBindingStub.java

이 클래스들은 많은 문제들을 시사하고 있다. Apache Axis는 구현 경로에 아직 추가되지 않았기 때문이다. 이 문제를 해결하려면 다음의 jar를 TAClient 프로젝트의 Java Build Path에 추가한다.

  1. TAClient 프로젝트를 선택하고Project > Properties를 선택한다.
  2. Java Build Path를 선택하고 다음 외부 JAR를Libraries탭 밑에 추가한다:
    • C:\jaf-1.0.2\activation.jar
    • C:\javamail-1.3.3_01\mail.jar
    • C:\axis-1_3\lib\axis.jar
    • C:\axis-1_3\lib\jaxrpc.jar
    • C:\axis-1_3\lib\saaj.jar
    • C:\axis-1_3\lib\commons-logging-1.0.4.jar
    • C:\axis-1_3\lib\commons-discovery-0.2.jar
    • C:\axis-1_3\lib\wsdl4j-1.5.1.jar

자동 재 구현 후에 모든 에러들이 지금처럼 해결되어야 한다. 여러분은 이제 클라이언트 애플리케이션 메인 메소드도 완성했다. (Listing 29)


Listing 29: TAClient.java
package com.ibm.ta.client;import java.rmi.RemoteException;import javax.xml.rpc.ServiceException;import com.ibm.ta.webservice.Course;import com.ibm.ta.webservice.Student;import com.ibm.ta.webservice.TAService;import com.ibm.ta.webservice.TAServiceException;import com.ibm.ta.webservice.TAServiceServiceLocator;public class TAClient { public static void main(String[] args) {  // Get the service locator  TAService service = null;  TAServiceServiceLocator serviceLocator = new TAServiceServiceLocator();  try {   serviceLocator     .setTAServiceEndpointAddress     ("http://localhost:9080/TAWebService/services/TAService");   service = serviceLocator.getTAService();  } catch (ServiceException se) {   se.printStackTrace();   System.exit(1);  }  try {   Student[] students = service.listStudents();   Course[] courses = service.listCourses();   System.out.println("Enrolling " + students[0].getName() + " in "     + courses[0].getName());   service.enroll(students[0].getId(), courses[0].getId());  } catch (TAServiceException tase) {   System.out.println(tase);   System.out.println(tase.getMessage());   System.exit(1);  } catch (RemoteException re) {   re.printStackTrace();   System.exit(1);  }  ; }}

서비스 로케이터로 서비스 메소드를 호출하기 전에 서비스 엔드포인트 URL을http://localhost:9080/TAWebService/services/TAService로 설정해야 한다.

사용자 삽입 이미지
제품 환경에서 WebSphere를 직접 호출할 수 없지만 WebSphere HTTP 서버 플러그인을 사용하여 웹 컨테이너로 호출을 라우팅 하는 IBM HTTP Server 같은 웹 서버를 호출한다.

모든 것이 올바르게 설정되었다면 IRAD에서 TAClient를 실행할 때 콘솔에 다음과 같은 아웃풋이 나타날 것이다:

Enrolling Peter Wansch in Java Programming in the Sun

MySQL 서버를 갖고 있다면 콘텍스트 매개변수와 리소스 레퍼런스를 변경하고, 서버를 퍼블리시 하고, TAClient 애플리케이션을 다시 실행하라. 정확히 같은 결과를 볼 수 있다.

IRAD를 사용하여 J2SE 애플리케이션을 만들 수도 있다. 단점은 그와 같은 클라이언트는 애플리케이션을 실행하기 전에 WebSphere 애플리케이션 클라이언트 패키지를 설치해야 한다는 점이다. InstallShield나 비슷한 기술을 사용하여 자바 애플리케이션을 전개하고 싶다면 Apache Axis가 더 쉽다. 앞서 언급한 jar 파일들을 설치 프로젝트에 추가만 하면 된다.

맺음말

서비스 지향 아키텍처(SOA)에서, IT 환경 밖에서 클라이언트에 액세스 할 수 잇는 정보 서비스를 만들고 싶다면 웹 서비스가 간단하고 강력한 솔루션을 제공한다. 클라이언트 기술(J2SE, J2EE, J2ME, .Net)이나 클라이언트가 실행되는 컨테이너와 전혀 상관이 없다. 데이터베이스 제품 스팩의 SQL 문을 캡슐화 하는 Data Access Objects (DAO)를 사용함으로서 많은 관계형 데이터베이스 관리 시스템을 지원할 수 있다. 개발과 프로토타이핑에 사용되는 MySQL 같은 작은 풋프린트의 데이터베이스 서버부터 미션 크리티컬 엔터프라이즈 데이터를 보유하고 있는 DB2 for z/OS 같은 큰 엔터프라이즈 데이터베이스까지 지원할 수 있다. 그리고 애플리케이션 코드와 데이터 액세스 코드 간 직접적인 의존성도 피할 수 있다. Rational Application Developer와 WebSphere 애플리케이션 서버는 통합 환경을 제공하여 정보 웹 서비스들을 빠르고 쉽게 개발, 전개, 테스트 할 수 있도록 한다.

감사의 말

이 글을 검토해 준 Timothy Joel Bethea, Michael Schenker, Wolfgang Schuh에게 감사의 말을 전한다.

기사의 원문보기



사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


다운로드 하십시오

설명이름크기다운로드 방식
Web service using DAOs sample source codewsdao10.zip35KB FTP사용자 삽입 이미지|사용자 삽입 이미지HTTP
사용자 삽입 이미지
사용자 삽입 이미지다운로드 방식에 대한 정보사용자 삽입 이미지사용자 삽입 이미지Get Adobe® Reader®


참고자료

교육

제품 및 기술 얻기

토론


필자소개

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

Peter Wansch는 IBM Silicon Valley Lab의 Data Server Common Administration Web Tooling의 소프트웨어 엔지니어이다. DB2와 웹 애플리케이션 개발 분야에서 컨설턴트, 개발자, 아키텍트, 프로젝트 매니저로서 활동했다. 오스트리아 University of Technology in Vienna에서 컴퓨터 엔지니어링 석사 학위를 받았다. Sun 인증 자바 개발자이며, DB2 관리 인증, AIX 관리자 인증도 받았다. 자바 네트워크 프로그래밍, 미디어 스트리밍, 데이터베이스 애플리케이션 개발과 관련하여 책도 쓰고 있다.

신고
Posted by The.민군

Design Pattern Toolkit을 사용하여 모델 중심 개발 시작하기 (한글)

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
문서 옵션
사용자 삽입 이미지이 페이지를 이메일로 보내기');// :badtag -->
사용자 삽입 이미지사용자 삽입 이미지

이 페이지를 이메일로 보내기

사용자 삽입 이미지
사용자 삽입 이미지

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

사용자 삽입 이미지사용자 삽입 이미지

샘플 코드


제안 및 의견
사용자 삽입 이미지피드백
사용자 삽입 이미지

난이도 : 초급

Roland Barcia, Certified IT Specialist, IBM
Chris Gerken, Senior Software Engineer, IBM

2006 년 9 월 18 일
2006 년 10 월 23 일 수정

모델 중심 개발의 실용성과 혜택, Design Pattern Toolkit으로 쉽게 패턴 템플릿을 만드는 방법, 애플리케이션 개발의 베스트 프랙티스를 파악하는 변형의 사용을 통해 속도를 높이는 툴을 알아봅시다. 툴킷을 사용하는 기본을 배우고 이를 사용하여 복잡한 시스템을 생성하고 자산 기반 비즈니스를 지원하는 방법까지 배워봅시다.

IBM WebSphere Developer Technical Journal발췌

머리말

모델 중심 개발을 생각하면 대부분 UML 모델의 유형으로 코딩하는 것과, 그 모델에서 생성물을 만들어내는 것을 생각한다. 하지만 꼭 그럴 필요는 없다. 모델은 모든 형태와 크기로 존재한다. 모델은 생성 또는 작동을 움직이는 모든 것이라 할 수 있다.

모델 중심 개발은 다음과 같은 목표가 있다:

  • 개발 시간을 줄인다.
  • 최소한의 정보량으로 유지한다.
  • 중립적인 방식으로 모델을 관리하면서 같은 모델에서 여러 유형의 구현들을 만들어 낼 수 있다. 예를 들어, 다른 템플릿을 사용하여 같은 모델에서 Web UI와 Native GUI를 만들 수 있다.

모델 중심 아키텍처 변형에 기반하여 애플리케이션을 만드는 Eclipse 구동 템플릿 엔진인Design Pattern Toolkit을 이용한 모델 중심 개발에 대해 설명하겠다. (참고자료) Design Pattern Toolkit (DPTK)의 코드 생성 장치의 기초를 설명하고 모델 중심 개발(MDD)로 이끄는 코드 생성과 관련한 좋은 디자인을 결합시켜 볼 것이다. 이러한 디자인 원리는 DPTK를 코드 제너레이터 그 이상으로 만든다. 이것은 완전한 기능을 갖춘 모델 중심 개발 툴로서 복잡한 시스템을 구현한다. 이 글에서는 패턴 템플릿을 구현하는 기초를 중심으로 설명하겠다.

Design Pattern Toolkit을 다운로드 하려면IBM alphaWorks를 참조하라.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


패턴 템플릿

DPTK의 패턴 작성자는 태그 없이 패턴 템플릿을 구현한다. 이러한 태그들은 모델에 액세스 하고, 로직을 실행하며, 다양한 태스크를 수행하는 특별한 작동을 갖고 있다. 많은 태그 세트들은 많은 기능들을 선사할 뿐만 아니라 복잡한 MDD 변형을 구현하는데 필요한 기본 구현 블록을 나타낸다. 패턴 템플릿의 주요 목적은 가공물을 만들어 내는 것이다. 다음 섹션에서는 패턴 템플릿이 그 이상의 것도 할 수 있다는 것을 증명하겠다.

잘 이해할 수 있도록 간단한 패턴 템플릿을 보도록 하자:


그림 1. 간단한 패턴 템플릿
사용자 삽입 이미지

이것은 매우 간단한 DPTK 모습이다:

  1. 이 패턴은 간단한 XML 모델과 반대로 적용된다.
  2. DPTK 엔진은 패턴 템플릿을 호출한다. 패턴 템플릿은 DPTK 태그 언어를 사용하여 동적인 부분들을 대체한다.
  3. 마지막 생성물이 만들어진다.

XML 선언 없이 자바™ 클래스를 만드는 것은 재미있는 일은 아니지만 여러분이 흐름을 파악해야 한다. 계속 진행해 가면서 이를 확장할 것이다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


코드 생성을 위한 Model View Controller

Model View Controller(MVC)는 다양한 애플리케이션 유형들을 구현하는 유명한 디자인 패턴이다. 예를 들어, Java EE에서 JavaServer™ Faces와 Struts는 MVC 디자인만 전담하여 제공하고 있다. MVC는 애플리케이션에서 영역 분리를 실행하여 Model은 데이터와 비즈니스 로직을, View는 데이터를 보는 방법을, Controller는 사용자 인풋, 모델 호출, 뷰 선택 등을 핸들한다.

MVC는 Design Pattern Toolkit을 사용하여 MDD 솔루션을 구현하기에 좋은 디자인 패턴이다. 그림 2를 보자.


그림 2. 디자인 패턴으로서의 MVC
사용자 삽입 이미지

DPTK 태그 라이브러리에는 많은 태스크들을 수행하는데 사용되는 다양한 태그들이 포함되어 있다. 따라서 우리는 특정 역할을 가진 잘 설계된 템플릿을 구현할 수 있다. 예를 들어, 컨트롤러 패턴 템플릿이 많은 인공물들의 전체적인 컨테이너를 만들고 다양한 다른 템플릿들을 호출하고, 그로 인해 구체적인 생성물들이 만들어 진다.

이제 MVC 디자인 패턴을 사용하여 생성물들을 만드는 방법을 설명하겠다.DPTK에서샘플 자료를 다운로드 하라.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


패턴 템플릿 구현하기

모델 중심 개발에서, 목표를 늘 염두 해 두는 것이 중요하다. 첫 번째 케이스를 직접 구현해 보는 것이다. 이러한 방식으로 여러분이 무엇을 구현하고 싶은지를 알 수 있다. 먼저 여러분에게 샘플 모형부터 보여주겠다. (향후 기술자료에서는 패턴을 만드는데 필요한 모형을 분석하는데 사용되는 툴을 설명하겠다.) 이 예제에서는 IBM Rational® Software Architect 내에서 Design Pattern Toolkit을 사용하는 방법을 설명하겠다. DPTK 플러그인이 설치된 상태에서 Eclipse를 사용해도 된다. (참고자료).

  1. Rational Software Architect를 시작하고 빈 워크스페이스를 연다:

    1. Window => Open Perspective => Other를 선택하여 Design Pattern Toolkit 퍼스펙티브로 전환한다. (그림 3).



      그림 3. 퍼스펙티브 열기
      사용자 삽입 이미지

    2. 다이얼로그에서Design Pattern Toolkit을 선택한다. (그림 4).



      그림 4. 퍼스펙티브 선택하기
      사용자 삽입 이미지

  2. 샘플 자바 모형을 반입한다; 이것은 우리가 만들고자 하는 JavaBean의 예제이다:

    1. Navigator view를 오른쪽 클릭하여Import를 선택한다. (그림 5).



      그림 5. 샘플 반입하기
      사용자 삽입 이미지

    2. Import wizard에서Project Interchange를 선택한다. (Eclipse를 사용한다면 프로젝트의 압축을 풀어 이것을 기존 프로젝트처럼 워크스페이스에 반입한다.)



      그림 6. 샘플 반입하기
      사용자 삽입 이미지

    3. 다운로드 자료를 검색하여MyExemplar.zip을 선택한다. (그림 7).



      그림 7. 프로젝트 선택하기
      사용자 삽입 이미지

    4. MyExemplar프로젝트를 선택하고Finish를 누른다.

  3. 인공물을 검사한다:

    1. 프로젝트를 확장하고(그림 8)sample.appdef파일을 연다.



      그림 8. 샘플 열기
      사용자 삽입 이미지

      이 파일에는 만은 XML 태그들이 포함되어 있다. IBatis(참고자료)에 익숙한 사람들의 경우 XML 모델(Listing 1)이 iBatis 매핑 파일의 parameterMap 구조를 반영하고 있다는 것을 알 수 있을 것이다. 이것은 자바 클래스와 함께 속성과 각 속성에 대한 데이터 유형을 정의하고 생성용 모델로서 제공한다.

      (이 글의 목표는 패턴 템플릿 작성에 익숙해 지는 것이다. 모델을 관리하는 방법은 여기에서는 설명하지 않겠다. 실제로 인풋과 내부 모델들은 맞지 않는다. 내부 모델은 나중에 변경할 것이다.)



      Listing 1
      <app> <parameterMap class="com.ibm.dptk.ibatis.dataobject.Customer"  id="newCustomerMap" >  <parameter property="customerId" javaType="java.lang.Integer"/>  <parameter property="name" javaType="java.lang.String"/>  <parameter property="address1" javaType="java.lang.String" />  <parameter property="address2" javaType="java.lang.String" />  <parameter property="city" javaType="java.lang.String" />  <parameter property="state" javaType="java.lang.String" />  <parameter property="zip" javaType="java.lang.String" /> </parameterMap></app>

    2. 자바 클래스를 열면 속성과 세터와 게터를 가진 JavaBean을 볼 수 있다. JavaBean 속성을 상응하는 게터와 세터로 그룹핑했다. 아래는 JavaBean이다.



      Listing 2
      public class Customer implements Serializable { private java.lang.String address1; /**  * Gets the address1  *   * @return Returns a java.lang.String  */ public java.lang.String getAddress1() {  return address1; } /**  * Sets the address1  *   * @param java.lang.String  *            The address1 to set  */ public void setAddress1(java.lang.String address1) {  this.address1 = address1; }...

이는 목표 예제를 검사하는 매우 간단한 방법이다. 많은 경우, 모델과 생성물들은 그렇게 분명하지 않다. 다음 글에서는 모형을 준비하고 모델을 발견하는 고급 분석 기술을 설명하겠다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


패턴 생성 및 실행

패턴 생성과 테스팅을 이해할 수 있도록 패턴 프로젝트를 만들고 그 패턴을 모델에 적용시켜 보겠다.

  1. 패턴 프로젝트 만들기.

    1. Navigator 뷰를 오른쪽 클릭하고New => New pattern project를 선택한다. (그림 9)



      그림 9. 새로운 패턴 만들기
      사용자 삽입 이미지

    2. 패턴 이름을IBatisJavaBeanGenerator로 하고Next를 누른다. (그림 10).



      그림 10. 새로운 패턴 만들기
      사용자 삽입 이미지

    3. 다음 필드에 맞는 값을 확인 또는 입력한다:

      • Pattern Name:패턴 이름을 입력한다.
      • Pattern ID:패턴 아이디 값.
      • Appdef Type:모델이 저장된 파일 확장 유형. 이 확장으로 모델에 대해 패턴을 실행할 때 이 패턴은 이 모델에 적용할 수 있는 패턴 리스트 상에 나타난다.
      • Applies To:하나의 파일에 모델이 나타나는지를 가리키면서 하나의 파일로 코드 생성을 실현하는지 여부를 묻는다.


      그림 11. 새로운 패턴 만들기
      사용자 삽입 이미지

  2. 2. DPTK는 Model View Controller를 사용하도록 설계된다. 따라서, Design Pattern Toolkit Project 마법사는 Model View Controller를 염두 해 두고 디폴트 프로젝트 구조를 만든다. (그림 12)



    그림 12. MVC 프로젝트 구조
    사용자 삽입 이미지

    1. 컨트롤러 폴더 밑에 생성을 책임지는 템플릿이 있다. 패턴을 모델에 적용할 때, control.pat 파일이 먼저 호출된다.

    2. 뷰 폴더 밑에, 패턴 작성자들이 데이터 뷰를 나타내는 템플릿을 저장한다.

    3. 프로젝트는 더미 sample.appdef 파일을 만들었다(그림 13). Navigator 뷰에서 파일을 선택하여 이것을 연다.



      그림 13. Dummy sample.appdef 파일
      사용자 삽입 이미지

    4. 그림 14는 sample.appdef 파일이 <app> 태그를 가진 유일한 파일이라는 것을 보여준다.



      그림 14. Dummy sample.appdef 파일
      사용자 삽입 이미지

    5. DPTK는 뷰 템플릿의 예제인 dump.pat 파일을 제공한다. (그림 15).



      그림 15. 뷰 템플릿 예제
      사용자 삽입 이미지

    6. dump.pat 파일을 열면 또 다른 파일이 보인다(Listing 3). 이것은 인메모리 모델을 사용하여 이것을 파일로 덤핑한다.



      Listing 3
      <exists node="/*/.." attr=".encoding"><?xml version="1.0" encoding="<attr node="/*/.." name=".encoding"/>"?></exists><exists node="/*/.." attr=".name"><!DOCTYPE <attr node="/*/.." name=".name"/> PUBLIC "<attr node="/*/.." name=".publicId"/>" "<attr node="/*/.." name=".systemId"/>"></exists><dump node="/*" format="true" entities="true"/>

    7. control.pat 파일을 연다. (그림 16)



      그림 16. control.pat 파일 열기
      사용자 삽입 이미지

    8. control.pat 파일의 내용은 Listing 4에 나와있다. 코드에서 볼드체 부분을 보면 <start> 템플릿 태그를 사용하여 dump.pat을 호출한다는 것을 알 수 있다. 이 시작 태그는 패턴 템플릿을 호출하고 결과를 지정된 리소스, 이 경우 dump.xml에 덤핑한다.



      Listing 4
      *** High-Level Controller   *** Apply naming conventions   <include template="ibatis.java.bean.generator/controller/naming.pat"/>   *** Derive names and other data    <include template="ibatis.java.bean.generator/controller/derived.pat"/>   *** Dump the modified appdef file for testing and debug purposes   <start template="ibatis.java.bean.generator/view/dump.pat" resource="dump.xml"   replace="true"/>

  3. 이제 패턴을 호출할 방법을 보자.

    1. sample.appdef파일을 오른쪽 클릭하고Apply Pattern을 선택한다. (그림 17)



      그림 17. Apply pattern
      사용자 삽입 이미지

    2. Ibatis Java Bean Generator를 선택하고OK를 누른다. (그림 18)



      그림 18. 패턴 생성하기
      사용자 삽입 이미지

    3. 패턴이 만들어지면 그림 19 같은 텍스트 박스가 보인다.



      그림 19. 성공적으로 적용된 패턴
      사용자 삽입 이미지

    4. dump.xml 이라고 하는 파일도 생겼다. (그림 20).



      그림 20. Dump.xml 파일
      사용자 삽입 이미지

    5. e. dump.xml의 내용을 확인해 보면(그림 21), 이것이 모델을 복사하는 XML이라는 것을 알 수 있다. (디버깅에 유용하다.)

      그림 21. Dump.xml 파일
      사용자 삽입 이미지

    6. 모형 프로젝트에서 sample.appdef를 패턴 프로젝트에 복사하고 디폴트 프로젝트를 오버라이드 한다.

    7. 패턴을 sample.appdef에 다시 적용한다. (그림 22).



      그림 22. 패턴 다시 적용하기
      사용자 삽입 이미지

    8. dump.xml 파일에는 이제 모형 모델이 생겼다. (그림 23)



      그림 23. Dump.xml 파일
      사용자 삽입 이미지


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


DPTK 태그로 패턴 템플릿 만들기

이제 첫 번째 템플릿을 만들 준비가 되었다. 우리의 목적은 sample.appdef 파일에서 JavaBean을 만드는 것이다.

  1. 자바 소스 파일을 만들려면 자바 프로젝트 내에서 생성이 이루어져야 한다. Design Pattern Toolkit은 다양한 Eclipse 프로젝트를 만들 수 있지만 여기에서는 자바 프로젝트를 직접 만들어 보겠다:

    1. Design Pattern Toolkit에서File => New=> Project를 선택한다. (그림 24).



      그림 24. 자바 프로젝트 생성하기
      사용자 삽입 이미지

    2. Java Project를 선택한다. (그림 25).



      그림 25. 자바 프로젝트 생성하기
      사용자 삽입 이미지

    3. 프로젝트 이름을MyJavaBeanProject로 하고Finish를 누른다. (그림 26).



      그림 26. 자바 프로젝트 생성하기
      사용자 삽입 이미지

    4. 자바 퍼스펙티브 변환에 대한 물음이 나오면No라고 한다. (그림 27).



      그림 27. 퍼스펙티브 변환 거절하기
      사용자 삽입 이미지

    5. 모형에서 sample.appdef 파일을 자바 프로젝트로 복사한다. (그림 28).



      그림 28. sample.appdef 파일 복사하기
      사용자 삽입 이미지

  2. 이제 패턴 템플릿을 만들 것이다.view폴더를 오른쪽 클릭하고New => File(그림 29)를 선택하고 새로운 파일 이름을JavaBean.pat으로 한다.



    그림 29. 패턴 템플릿 만들기
    사용자 삽입 이미지



    그림 30. 패턴 템플릿 만들기
    사용자 삽입 이미지

  3. Customer JavaBean을 사용하여 JavaBean 코드를 템플릿에 복사한다. 모형 프로젝트에JavaBean을 사용하여 JavaBean 코드를 템플릿에 복사한다. 모형 프로젝트에서 JavaBean을 사용하여 JavaBean 코드를 템플릿에 복사한다. 모형 프로젝트에서 Customer.java 코드를 복사하여 템플릿에 붙인다. pat 파일은 그림 31의 파일과 같다.



    그림 31. 패턴 템플릿 만들기
    사용자 삽입 이미지

  4. JavaBean 템플릿이 패턴의 일부가 되려면 이것을 control.pat에 추가해야 한다.

    1. control.pat파일을 연다. (그림 32).



      그림 32. control.pat 파일 열기
      사용자 삽입 이미지

    2. Listing 5의 볼드체 텍스트로 되어 있는 또 다른 시작 태그를 추가한다. (또는 다운로드 파일의 C:\DPTKArticles\Part1\CodeSnippet1.txt에서 복사한다.) 템플릿 애트리뷰트는 JavaBean.pat 파일을 가리킨다. 리소스 애트리뷰트는 파일의 이름을 정의한다. (리소스 폴더는 Eclipse 프로젝트에 지정된 src 디렉토리와 관련이 있다. filetype이 자바이므로 프로젝트의 루트와는 반대된다.)



      Listing 5
      <start template="ibatis.java.bean.generator/view/dump.pat" resource="dump.xml"project="MyJavaBeanProject" replace="true"/>   <start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="com/ibm/dptk/ibatis/dataobject/Customer.java" replace="true"/>

    3. MyJavaBeanProject 내의sample.appdef를 클릭하여 패턴을 적용한다. (그림 33).



      그림 33. 패턴 적용하기
      사용자 삽입 이미지

    4. 패턴 리스트에서Ibatis Java Bean Generator를 선택하면 (그림 34), Customer 클래스가 생성된다 (그림 35).



      그림 34. 패턴 적용하기
      사용자 삽입 이미지



      그림 35. 생성된 Customer 클래스
      사용자 삽입 이미지


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


Design Pattern Toolkit으로 구현하기

이제 Design Pattern Toolkit 태그를 사용하여 JavaBean 패턴 템플릿을 구현한다.

  1. Design Pattern Toolkit은 모델로 액세스 할 수 있고 패턴에서 데이터를 사용할 수 있도록 하는 여러 태그들을 갖고 있다. 첫 번째 태그는 모델에서 애트리뷰트 데이터로 액세스 한다.

    1. 클래스 이름(그림 36)을 얻기 위해 사용했던 정보인 모델을 기억할 것이다. 이제JavaBean.pat파일을 연다. (그림 37).



      그림 36. JavaBean.pat 파일 열기
      사용자 삽입 이미지



      그림 37. JavaBean.pat 파일 열기
      사용자 삽입 이미지

    2. 첫 번째 태그는 <attr> 이다. 이것으로 특정 태그의 애트리뷰트에 액세스 할 수 있다. <attr> 태그는 텍스트를 특정 방식으로 포맷팅 할 수 있게 해주는 몇 가지 애트리뷰트도 갖고 있다. 그림 38의 패키지 이름과 클래스 이름을 Listing 6의 텍스트로 대체한다. C:\DPTKArticles\Part1\CodeSnippet2.txt에서 텍스트를 복사할 수도 있다. (아니면, 전체 템플릿이 다운로드 파일에 있다.)



      그림 38. 패키지 이름과 클래스 이름 바꾸기
      사용자 삽입 이미지



      Listing 6
      package<attr node="/app/parameterMap" name="class" format="QP" />;import java.io.Serializable;public class<attr node="/app/parameterMap " name="class" format="QC">implements Serializable {

  2. 태그를 사용하여 모델의 애트리뷰트 데이터를 덤핑할 수 있다. 하지만 가끔 인메모리 모델을 바꿔야 할 경우도 있다.control.pat파일(그림 39)을 열고 앞서 추가했던 시작 템플릿을 검사한다. (Listing 7).



    그림 39. control.pat 파일 열기
    사용자 삽입 이미지



    Listing 7
    <start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="com/ibm/dptk/ibatis/dataobject/Customer.java"  replace="true"/>

  3. 또 다른 DPTK 태그 내에서 attr 태그를 사용할 수 없다는 문제가 있다. DPTK는 데이터에 액세스 하는데 사용되는 동적 표현 언어를 제공한다. 한 가지 문제는 클래스의 포맷이 디렉토리 구조에 있어야 한다는 점이다. 동적 표현이 엉망이 될 수도 있다. 이러한 문제를 해결하는 또 따른 방법은 인메모리 모델을 변경하는 것이다. 같은 데이터를 다른 방식으로 포맷팅 하여 직접 액세스 하는 것이다. <setAttr> 태그로는 기존 태그에 애트리뷰트를 설정할 수 있다.

    1. Listing 8의 볼드체 텍스트로 되어 있는 태그를 추가한다. (또는 C:\DPTKArticles\Part1\CodeSnippet3.txt에서 복사한다). 여기에서 우리는 여러 방식으로 클래스 이름을 포맷팅 한다.



      Listing 8
      <setAttr node="/app/parameterMap" name="classname" ><attr node="/app/parameterMap" name="class"></setAttr><setAttr node="/app/parameterMap" name="name" ><attr node="/app/parameterMap" name="class" format="QC"></setAttr><setAttr node="/app/parameterMap" name="dir"><attr node="/app/parameterMap" name="class" format="PD"></setAttr><setAttr node="/app/parameterMap" name="package"><attr node="/app/parameterMap" name="class" format="QP" /></setAttr>   *** Dump the modified appdef file for testing and debug purposes   <start template="ibatis.java.bean.generator/view/dump.pat" resource="dump.xml" project="MyJavaBeanProject" replace="true"/>   <start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="com/ibm/dptk/ibatis/dataobject/Customer.java" replace="true"/>

    2. 패턴을 적용하고 dump.xml 파일을 본다. 인메모리 모델을 보여준다. (그림 40). 원래 인풋 모델을 결코 수정할 수 없다.



      그림 40. 인메모리 모델
      사용자 삽입 이미지

  4. 이제 템플릿을 변경하여 새로운 모델 엘리먼트에 액세스 할 수 있다:

    1. 아래 볼드체로 되어있는 리소스 애트리뷰트의 값을 변경한다.



      Listing 9
      <start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="%/app/parameterMap(dir)%.java" replace="true"/>

    2. JavaBean.pat으로 가서 attr 태그를 수정하여 모델의 새로운 데이터를 참조하도록 한다.



      Listing 10
      package<attr node="/app/parameterMap" name="package" />;import java.io.Serializable;public class<attr node="/app/parameterMap" name="name">implements Serializable {

  5. JavaBean 속성을 만들어야 한다. 자바 속성들의 수는 다양하기 때문에 모델을 트래버스 해야 한다. Design Pattern Toolkit은 반복 태그를 갖고 있다. (또는 C:\DPTKArticles\Part1\CodeSnippet4.txt에서 전체 클래스를 복사한다.)

    1. JavaBean.pat 파일을 연다.

    2. 모델로서 하나의 자바 속성만 필요하다. 첫 번째 것을 제외하고 모든 것을 삭제한다. 어드레스 속성과 게터와 세터를 유지한다.

    3. 자바 속성, 게터, 세터 주위에 Listing 11의 반복 태그를 추가한다. 그림 41은 예제 모습이다.



      Listing 11
      <iterate nodes="/app/parameterMap/parameter" name="currentParameter" >



      그림 41. Iterate 태그
      사용자 삽입 이미지

    4. 유형과 속성을 아래 태그로 바꾼다. 그림 42를 보면 그 모습을 볼 수 있다.



      Listing 12
      private <attr node="currentParameter" name="javaType"/> <attr node="currentParameter" name="name"/>;



      그림 42. 유형과 속성 바꾸기
      사용자 삽입 이미지

    5. 마지막으로 Listing 13처럼 세터와 게터를 업데이트 하고 패턴을 적용한다. (그림 43).



      Listing 13
      package <attr node="/app/parameterMap" name="package" />;import java.io.Serializable;public class <attr node="/app/parameterMap" name="name"> implements Serializable { <iterate nodes="/app/parameterMap/parameter" name="currentParameter" > private <attr node="currentParameter" name="javaType"/> <attr node="currentParameter" name="name"/>; /**  * Gets the<attr node="currentParameter" name="name"/>* @return Returns a<attr node="currentParameter" name="javaType"/>*/ public<attr node="currentParameter" name="javaType"/>get<attr node="currentParameter" name="name" format="U1"/&gt />() {      return<attr node="currentParameter" name="name"/>; } /**  * Sets the<attr node="currentParameter" name="name"/>* @param<attr node="currentParameter" name="javaType"/>The<attr node="currentParameter" name="name"/>to set  */public void set<attrnode="currentParameter" name="name" format="U1"/&gt />(<attr node="currentParameter" name="javaType"/> <attr node="currentParameter" name="name"/>) {      this.<attr node="currentParameter" name="name"/>=<attr node="currentParameter" name="name"/>; } </iterate>}



      그림 43. 패턴 적용하기
      사용자 삽입 이미지


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


두 번째 JavaBean 만들기

패턴의 본질을 확인하려면 다른 것을 만들어서 테스트해야 한다. 우리 패턴은 하나의 모델에서 하나의 파일을 만든다. 하지만 최소한의 변경도 가능하다. (C:\DPTKArticles\Part1/Part1Solution.zip에서 솔루션 프로젝트 인터체인지를 로딩하여 마지막 결과를 실행할 수 있다.)

  1. 또 다른 JavaBean을 모델에 추가한다. MyJavaBeanProject (그림 44)에서sample.appdef파일을 열고, parameterMap 태그를 추가한다. (Listing 14)



    그림 44. sample.appdef 파일 열기
    사용자 삽입 이미지



    Listing 14
    <app> <parameterMap class="com.ibm.dptk.ibatis.dataobject.Customer"  id="newCustomerMap" >  <parameter property="customerId" javaType="java.lang.Integer"/>  <parameter property="name" javaType="java.lang.String"/>  <parameter property="address1" javaType="java.lang.String" />  <parameter property="address2" javaType="java.lang.String" />  <parameter property="city" javaType="java.lang.String" />  <parameter property="state"  javaType="java.lang.String" />  <parameter property="zip" javaType="java.lang.String" /> </parameterMap> <parameterMap class="com.ibm.dptk.ibatis.dataobject.Order"  id="newCustomerMap" >  <parameter property="customerId" javaType="java.lang.Integer"/>  <parameter property="orderId" javaType="java.lang.String" /> </parameterMap></app>

  2. 한 개 이상의 파일을 만들도록 패턴을 업데이트 하려면 control.pat 파일을 업데이트 한다:

    1. control.pat 파일을 열고 변경한다. 반복 태그를 사용하여 파일 생성과 모델 업데이트를 하나로 모을 수 있다.



      Listing 15
      *** High-Level Controller   *** Apply naming conventions   <include template="ibatis.java.bean.generator/controller/naming.pat"/>   *** Derive names and other data    <include template="ibatis.java.bean.generator/controller/derived.pat"/><iterate nodes="/app/parameterMap" name="currentBean" ><setAttrnode="currentBean"name="classname" ><attrnode="currentBean"name="class"></setAttr> <setAttrnode="currentBean"name="name" ><attrnode="currentBean"name="class" format="QC"></setAttr> <setAttrnode="currentBean"name="dir"><attrnode="currentBean"name="class" format="PD"></setAttr> <setAttrnode="currentBean"name="package"><attrnode="currentBean"name="class" format="QP" /></setAttr>   </iterate>   *** Dump the modified appdef file for testing and debug purposes   <start template="ibatis.java.bean.generator/view/dump.pat" resource="dump.xml" project="MyJavaBeanProject" replace="true"/><iterate nodes="/app/parameterMap" name="currentBean" ><start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="%currentBean(dir)%.java" replace="true"/></iterate>package <attrnode="currentBean"name="package" />;import java.io.Serializable;public class <attr node="currentBean" name="name"> implements Serializable { <iterate nodes="currentBean/parameter" name="currentParameter" > private <attr node="currentParameter" name="javaType"/> <attr node="currentParameter" name="property"/>; /**  * Gets the <attr node="currentParameter" name="property"/>  * @return Returns a <attr node="currentParameter" name="javaType"/>  */ public <attr node="currentParameter" name="javaType"/> get<attr node="currentParameter" name="property" format="U1"/&gt />() {      return <attr node="currentParameter" name="property"/>; } /**  * Sets the <attr node="currentParameter" name="property"/>  * @param <attr node="currentParameter" name="javaType"/> The <attr node="currentParameter" name="property"/> to set  */public void set<attr node="currentParameter" name="property" format="U1"/&gt /> (<attr node="currentParameter" name="javaType"/> <attr node="currentParameter" name="property"/>)|-------- XML error:  The previous line is longer than the max of 90 characters ---------|{     this.<attr node="currentParameter" name="property"/> = <attr node="currentParameter" name="property"/>; } </iterate>}

    2. 패턴을 적용한다(그림 45). 그림 46 같은 Order 클래스가 만들어졌다.



      그림 45. 패턴 적용하기
      사용자 삽입 이미지



      그림 46. Order 클래스
      사용자 삽입 이미지


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


맺음말

DPTK를 사용하는 방법을 살펴보았다. DPTK를 사용하면 최소한의 노력으로 복잡한 문제를 해결할 수도 있다. 이 글에서는 패턴 템플릿을 구현하는데 초점을 맞췄다. DPTK는 기존 구현에서 모델을 발견하는 분석 툴도 제공한다. 이는 코드를 중립 모델로 역 엔지니어링 할 수 있도록 하고 자산 기반 비즈니스를 구현하는 토대가 된다. 다음 글에서는 Design Pattern Toolkit의 고급 기능들을 사용하여 보다 복잡한 문제들을 해결하는 방법을 설명하겠다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


감사의 말

이 글에 도움을 준 Geoffrey Hambrick에게 감사의 말을 전한다.

기사의 원문보기



사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


다운로드 하십시오

설명이름크기다운로드 방식
Code samplesDPTKArticleMaterials.zip11 KB FTP사용자 삽입 이미지|사용자 삽입 이미지HTTP
사용자 삽입 이미지
사용자 삽입 이미지다운로드 방식에 대한 정보사용자 삽입 이미지사용자 삽입 이미지Get Adobe® Reader®


참고자료

신고
Posted by The.민군

Eclipse 시작하기 (한글)

Eclipse 다운로드 및 시작하기

사용자 삽입 이미지
사용자 삽입 이미지하이라이트사용자 삽입 이미지Start here사용자 삽입 이미지다운로드사용자 삽입 이미지커뮤니티사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지

Eclipse는 소프트웨어 구현을 위한 확장된 개발 플랫폼과 애플리케이션 프레임웍을 제공하는데 초점을 맞춘 오픈 소스 커뮤니티의 프로젝트입니다. 이 글에서는 최신 버전의 Eclipse, IBM의 Eclipse 참여, 가장 눈에 띄는 Eclipse 프로젝트 가이드를 소개합니다. Eclipse의 장점, 중요성, 시작 방법 등을 설명합니다.


Eclipse란 무엇인가?

Eclipse는 소프트웨어를 쉽고, 저렴하게 구현 및 전개하는 프레임웍과 툴의 범용 플랫폼을 개발하는 오픈 소스 커뮤니티이다.

주요 소프트웨어 벤더, 솔루션 공급자, 기업, 교육 및 연구 기관, 개인들이 큰 컨소시엄을 이루어 Eclipse 플랫폼을 향상시킬 수 있는 생태계를 만들기 위해 노력하고 있다.

Eclipse를 통해 사용자들은 여러 가지 혜택을 누릴 수 있다.

Eclipse 기반 오퍼링 사용자:

  • 전체 Eclipse 에코시스템에서 얻는 지식
  • 오픈 소스 커뮤니티의 감시를 통해 나오는 고급 소프트웨어
  • Eclipse의 인터페이스로 인한 기술 재사용

Eclipse를 사용하는 자바™ 개발자:

  • 세계적인 자바 IDE
  • 플랫폼에 구애 받지 않은 자연스러운 룩앤필
  • 자바 툴링으로의 확장

Eclipse 툴 개발자:

  • 이식 가능하고 개인화가 가능한 플랫폼
  • 완벽한 툴 통합
  • 엔드투엔드 솔루션

Eclipse의 장점은?

어려운 질문이다. 대답은 사람에 따라 달라지기 때문이다. 연구원의 관점에서 보면 Eclipse는 일반 아키텍처에서 빠른 프로토타이핑, 협업, 아이디어 공유가 가능한 플랫폼이다. 툴 개발자의 관점에서 볼 때, 강력하고 확장성 있는 플랫폼으로 액세스 되어 고급 툴을 빠르고 효율적으로 개발할 수 있다. 전체 플랫폼이 너무 무거워서 사용할 수 없다면 Eclipse는 공통의 리소스 모델이나 별도의 플랫폼 기능을 필요로 하지 않는 애플리케이션을 위한 Rich Client Platform (RCP)을 제공한다.RCP FAQ를 참조하기 바란다. FAQ에 실린 중요한 프로젝트와 유명한 애플리케이션을 보면 Eclipse 기능을 이해할 수 있을 것이다.


Eclipse의 중요성은?

Eclipse 플랫폼은 플랫폼을 위한 소스 코드를 제공함으로서 신뢰를 구축한다. 소프트웨어 개발자들은 툴을 통합하고 해체하는 것에 지쳐있다. 오픈 소스 Eclipse 플랫폼에서는 툴 개발자들도 새로운 플러그인에 기여할 뿐만 아니라 기존 플랫폼을 향상하는 것을 도울 수 있다. 결국, Eclipse의 중요성은 툴 개발자와 사용자 모두 산업 레벨에서 툴링을 개발하는 방법을 알 수 있다는 데 있다.


IBM과 Eclipse?

IBM은 Eclipse 플랫폼의 창시자이다. Eclipse가 성공에 이르기까지, 세 가지 가장 중요한 단계를 거쳤는데, 이것을 통해 IBM이 Eclipse에 어떻게 개입했는지를 볼 수 있다:

시작

이 플랫폼은 1998년 Object Technology International (1996년 IBM이 인수함. 현재 IBM Ottawa Lab) 에 의해 개발을 시작하여 IBM 소프트웨어 툴링을 다루는 고객들이 제기한 문제들을 해결하고 있다. 고객들은 IBM의 툴링이 다른 기업에서 나온 것처럼 보이고 함께 작동하지 않는 것에 대해 불평했다. IBM은 이것을 가슴 깊이 새겼다.

오픈 소스에 기여

2001년, IBM은 Eclipse 컨소시엄을 만들고 Eclipse를 오픈 소스 커뮤니티에 제공했다. 오픈 소스 커뮤니티가 코드를 관리하고 컨소시엄이 상업적 문제를 다루도록 하는 것이 목표였다. 초기 컨소시엄에는 IBM 파트너들과 경쟁사들이 포함된 9명의 멤버들이 있었다. IBM은 지속적으로 Eclipse 혁신 같은 다양한 프로그램에 자금을 지원하고 Eclipse 코드 캠프를 후원하여 플랫폼을 발전시켰다.

이 플랫폼은 누구나 참여할 수 있는 오픈 소스 라이센스를 통해 오른 소스 모델을 사용하여 개발되었다.

독립

IBM은 벤더들의 헌신을 요구했지만 벤더들은 Eclipse 컨소시엄을 IBM이 관리하는 것으로 인식했고, IBM이 관여하는 한 참여하기를 꺼렸다. 이러한 문제를 해결하기 위해, IBM은 어떤 제어권도 포기했다. 많은 기업들의 지원으로, Eclipse Foundation이 전임 전문가를 갖춘 비영리 조직으로서 2004년에 만들어졌다.

오늘날, IBM은 그 어느 때보다 Eclipse에 헌신적이며전략적 멤버로서 Eclipse Foundation에 참여하고 있다. 더욱이 IBM은 그 어떤 벤더들 보다 많은 개발자들을 Eclipse에 기여하고 있다.


Eclipse 커뮤니티?

Eclipse 플랫폼의 성공 요소는 다음 세 가지이다:

커미터(Committers)

  • 커미터들의 개방적이고 활동적인 커뮤니티가 공식 Eclipse 툴링 개발을 책임지고 있다.
  • 커미터 그룹 중에는Eclipse Web Tools Platform프로젝트 팀이 있다.

플러그인 개발자

  • 커미터 커뮤니티 밖에 있는 커뮤니티
  • Eclipse Plugin Central에는 플러그인 개발자들의 대형 샘플링이 포함되어 있다.

사용자

  • 커미터와 플러그인 개발자들이 개발한 툴링을 사용하는 사람들로 구성된 커뮤니티

Eclipse에 기여해야 하는 이유?

Eclipse의 목표는 많지만 그 중에 하나는 Eclipse 생태계와 영리 추구이다. Eclipse 기여자들은 확장 프레임웍에 가치 있는 제품을 구현한다. 기여를 하는 주요 이유는:

제품 의존성

프로젝트 방향을 지정하여 상용 오퍼링을 돕는다.

브랜딩

회사와 제품을 Eclipse 브랜드와 제휴시켜서 대중적인 관점과 부합시킨다.

상품화

현재 트랜드를 인지하여 경쟁자들 보다 우위에 설 수 있다.

개발 영역으로 나아가야 하는 다른 이유는 커뮤니티 프로세스에 참여함으로서 개발자 사기 진작과 제품의 품질을 높일 수 있기 때문이다.


Eclipse 커미터가 되는 방법?

Eclipse Foundation은Eclipse 개발 프로세스를 만들어서 Eclipse 프로젝트가 제안되고 진행되는 방법을 관리하고 있다. Eclipse는 실력 위주이다. 다시 말해서 Eclipse에 기여를 많이 할수록 커미터 커뮤니티에서 많은 존경을 받을 수 있다. Eclipse 커미터가 되는 세 가지 방법이 있다:

  • 고용주가 여러분을 Eclipse 프로젝트에 전임으로 투입시킨다. (프로젝트에서 전임으로 일하면 피어(peer)들의 존경을 빠르게 받을 수 있고 커미터가 될 수 있다.)
  • 새로운 Eclipse를 시작하면 여러분이 그 프로젝트의 커미터가 된다. 하지만 프로젝트를 시작하는 과정은 매우 철저하며Eclipse 개발 프로세스에 기록된다. 프로젝트의 신뢰도, 전망, 결과가 좋아지면 커뮤니티에서의 명성도 높아진다.
  • 파트 타임으로 기여하던가 프로젝트의 특정 부분에 대해 작업을 한다. 프로젝트는 많은 전임 커미터들이 있기 때문에 이는 커미터가 되기에는 가장 어려운 방법이다. 프로젝트가 빠르게 진화하면서 파트 타임 개발자가 따라가기가 더 힘들어진다.

Eclipse를 사용하거나 기여할 때 알아야 할 것들?

우선 Eclipse Foundation에서 릴리스 한 모든 콘텐트는Eclipse Public License(EPL)가 관리한다. 2004년 5월, EPL은 Open Source Initiative (OSI)에서 승인을 받아서공식오픈 소스 라이센스가 되었다. Eclipse Foundation은 라이센싱 문제와 관련하여 많은 자료들을 제공한다:

Eclipse Foundation은 오픈 소스의 개발 프로세스를 따른다. 이 프로세스를Eclipse 개발 프로세스라고 하며 Eclipse라는 우산 밑에서 어떻게 개발 작업들이 이루어져야 하는지를 명시하고 있다. 이해를 돕기 위해Eclipse 개발 프로세스 가이드라인도 나와있다.


Eclipse 프로젝트?

다음은 플랫폼의 유연성과 우수함을 증명해 보이는 일부 프로젝트들이다:

Business Intelligent and Reporting Tools(BIRT)

BIRT은 웹 애플리케이션을 위한 오픈 소스 리포팅 시스템이다. BIRT은 두 개의 주 컴포넌트가 있다. Eclipse에 기반한 그래픽 리포트 디자이너와 애플리케이션 서버에 전개할 수 있는 런타임 컴포넌트이다. BIRT을 사용하여 다양한 리포트를 드래그&드롭 GUI를 사용하여 애플리케이션에 추가할 수 있다.

Eclipse Web Tools Platform(WTP)

WTP 프로젝트는 J2EE 웹 애플리케이션 개발에 필요한 툴을 기여한다. WTP 프로젝트에는 HTML, JavaScript, CSS, JSP 등을 편집하는 툴이 포함되어 있고 데이터베이스 액세스와 쿼리 툴을 제공한다. 프로젝트 범위는 크며,프로젝트의 기능을 설명하는 자료들도 있다.

Graphical Editing Framework(GEF)

GEF 프로젝트에서는 개발자들이 기존 애플리케이션 모델을 사용하여 리치 그래픽 에디터를 빠르게 구현할 수 있다. 프로젝트에는 그래픽 서킷 디자이너부터 WYSIWYG 텍스트 에디터 까지 포함되어 있다.

Visual Editor (VE) Project

VE Project는 Eclipse 내에서 GUI를 개발하는 프레임웍이다. 기본적으로 Swing, SWT, RCP 기반 GUI를 지원한다. VE 프로젝트의 기능을 나타내는플래쉬 데모도 있다.

C/C++ Development Tools(CDT)

CDT 프로젝트는 Eclipse 플랫폼에 C/C++ IDE를 제공한다.

Mylar

과거에 IDE를 사용했고 많은 프로젝트나 객체들이 스크린에 있을 때 정보 오버로드의 문제를 경험했다면 Mylar가 적합하다. Mylar 프로젝트는 Eclipse에서 시작할 때 정보 은닉을 피한다.

Eclipse Communications Framework (ECF) Project(ECF)

ECF는 신뢰성 있는 분산 애플리케이션을 단순화 하는 API를 제공한다. 프로젝트는 아직 초기 단계이지만 Eclipse 플랫폼이 통신에 사용되는 것 까지 보여주었다. ECF 프로젝트의 샘플 작업에는 Jabber를 사용하여 에디터를 시각적으로 공유하는 기능이 포함되어 있다.

더 많은 Eclipse프로젝트참조.



필자소개

Chris Aniszczyk은 IBM(Tivoli Security) 소프트웨어 엔지니어이며 IBM Extreme Blue 인턴쉽 프로그램을 수료했다. 오픈 소스 옹호자이며 Gentoo Linux로 작업하고 있다. 현재 Eclipse Modeling Framework Technology (EMFT) 프로젝트의 커미터이다.

신고
Posted by The.민군

Guide to Advanced Linux Command Mastery


by Arup Nanda사용자 삽입 이미지

Published August 2006

In Sheryl Calish's excellent article“Guide to Linux File Command Mastery,"you learned some routine Linux commands, which are especially valuable for Linux newbies. But now that you have mastered the basics, let’s move on to some more sophisticated commands that you will find extremely useful.

In this four-part series, you will learn some not-so-well-known tricks about various routine commands as well as variations in usage that make them more useful. As the series progresses, you will learn successively difficult commands to master.

Note that these commands may differ based on the specific version of Linux you use or which specific kernel is compiled, but if so, probably only slightly.

Painless Changes to Owner, Group, and Permissions

In Sheryl's article you learned how to use chown and chgrp commands to change ownership and group of the files. Say you have several files like this:

# ls -ltotal 8-rw-r--r--    1 ananda   users          70 Aug  4 04:02 file1-rwxr-xr-x    1 oracle   dba           132 Aug  4 04:02 file2-rwxr-xr-x    1 oracle   dba           132 Aug  4 04:02 file3-rwxr-xr-x    1 oracle   dba           132 Aug  4 04:02 file4-rwxr-xr-x    1 oracle   dba           132 Aug  4 04:02 file5-rwxr-xr-x    1 oracle   dba           132 Aug  4 04:02 file6

and you need to change the permissions of all the files to match those of file1. Sure, you could issuechmod 644 *to make that change—but what if you are writing a script to do that, and you don’t know the permissions beforehand? Or, perhaps you are making several permission changes and based on many different files and you find it infeasible to go though the permissions of each of those and modify accordingly.

A better approach is to make the permissions similar to those of another file. This command makes the permissions of file2 the same as file1:

chmod --reference file1 file2

Now if you check:

# ls -l file[12]total 8-rw-r--r--    1 ananda   users          70 Aug  4 04:02 file1-rw-r--r--    1 oracle   dba           132 Aug  4 04:02 file2

The file2 permissions were changed exactly as in file1. You didn’t need to get the permissions of file1 first.

You can also use the same trick in group membership in files. To make the group of file2 the same as file1, you would issue:

# chgrp --reference file1 file2# ls -l file[12]-rw-r--r--    1 ananda   users          70 Aug  4 04:02 file1-rw-r--r--    1 oracle   users         132 Aug  4 04:02 file2

Of course, what works for changing groups will work for owner as well. Here is how you can use the same trick for an ownership change. If permissions are like this:

# ls -l file[12] -rw-r--r--    1 ananda   users          70 Aug  4 04:02 file1-rw-r--r--    1 oracle   dba           132 Aug  4 04:02 file2

You can change the ownership like this:

# chown --reference file1 file2# ls -l file[12] -rw-r--r--    1 ananda   users          70 Aug  4 04:02 file1-rw-r--r--    1 ananda   users         132 Aug  4 04:02 file2

Note that the group as well as the owner have changed.

Tip for Oracle Users

This is a trick you can use to change ownership and permissions of Oracle executables in a directory based on some reference executable. This proves especially useful in migrations where you can (and probably should) install as a different user and later move them to your regular Oracle software owner.

More on Files

Thelscommand, with its many arguments, provides some very useful information on files. A different and less well known command –stat– offers even more useful information.

Here is how you can use it on the executable “oracle”, found under $ORACLE_HOME/bin.

# cd $ORACLE_HOME/bin# stat oracle  File: `oracle'  Size: 93300148        Blocks: 182424     IO Block: 4096   Regular FileDevice: 343h/835d       Inode: 12009652    Links: 1    Access: (6751/-rwsr-s--x)  Uid: (  500/  oracle)   Gid: (  500/     dba)Access: 2006-08-04 04:30:52.000000000 -0400Modify: 2005-11-02 11:49:47.000000000 -0500Change: 2005-11-02 11:55:24.000000000 -0500

Note the information you got from this command: In addition to the usual filesize (which you can get fromls -lanyway), you got the number of blocks this file occupies. The typical Linux block size is 512 bytes, so a file of 93,300,148 bytes would occupy (93300148/512=) 182226.85 blocks. Since blocks are used in full, this file uses some whole number of blocks. Instead of making a guess, you can just get the exact blocks.

You also get from the output above the GID and UID of the ownership of the file and the octal representation of the permissions (6751). If you want to reinstate it back to the same permissions it has now, you could usechmod 6751 oracleinstead of explicitly spelling out the permissions.

The most useful part of the above output is the file access timestamp information. It shows you that the file was accessed on 2006-08-04 04:30:52 (as shown next to “Access:”), or August 4, 2006 at 4:30:52 AM. This is when someone started to use the database. The file was modified on 2005-11-02 11:49:47 (as shown next to Modify:). Finally, the timestamp next to “Change:” shows when the status of the file was changed.

-f, a modifier to thestatcommand, shows the information on the filesystem instead of the file:

# stat -f oracle  File: "oracle"    ID: 0        Namelen: 255     Type: ext2/ext3Blocks: Total: 24033242   Free: 15419301   Available: 14198462   Size: 4096Inodes: Total: 12222464   Free: 12093976

Another option,-t, gives exactly the same information but on one line:

# stat -t oracle oracle 93300148 182424 8de9 500 500 343 12009652 1 0 0 1154682061 1130950187 1130950524 4096

This is very useful in shell scripts where a simple cut command can be used to extract the values for further processing.

Tip for Oracle Users

When you relink Oracle (often done during patch installations), it moves the existing executables to a different name before creating the new one. For instance, you could relink all the utilities by

relink utilities

It recompiles, among other things, the sqlplus executable. It moves the exiting executable sqlplus to sqlplusO. If the recompilation fails for some reason, the relink process renames sqlplusO to sqlplus and the changes are undone. Similarly, if you discover a functionality problem after applying a patch, you can quickly undo the patch by renaming the file yourself.

Here is how you can use stat on these files:

# stat sqlplus*  File: 'sqlplus'  Size: 9865            Blocks: 26         IO Block: 4096   Regular FileDevice: 343h/835d       Inode: 9126079     Links: 1    Access: (0751/-rwxr-x--x)  Uid: (  500/  oracle)   Gid: (  500/     dba)Access: 2006-08-04 05:15:18.000000000 -0400Modify: 2006-08-04 05:15:18.000000000 -0400Change: 2006-08-04 05:15:18.000000000 -0400   File: 'sqlplusO'  Size: 8851            Blocks: 24         IO Block: 4096   Regular FileDevice: 343h/835d       Inode: 9125991     Links: 1    Access: (0751/-rwxr-x--x)  Uid: (  500/  oracle)   Gid: (  500/     dba)Access: 2006-08-04 05:13:57.000000000 -0400Modify: 2005-11-02 11:50:46.000000000 -0500Change: 2005-11-02 11:55:24.000000000 -0500

It shows sqlplusO was modified on November 11, 2005, while sqlplus was modified on August 4, 2006, which also corresponds to the status change time of sqlplusO . It indicates that the original version of sqlplus was in effect from Nov 11, 2005 to Aug 4, 2006. If you want to diagnose some functionality issues, this is a great place to start. In addition to the file changes, as you know the permission's change time, you can correlate it with any perceived functionality issues.

Another important output is size of the file, which is different—9865 bytes for sqlplus as opposed to 8851 for sqlplusO—indicating that the versions are not mere recompiles; they actually changed with additional libraries (perhaps). This also indicates a potential cause of some problems.

File Types

When you see a file, how do you know what type of file it is? The commandfiletells you that. For instance:

# file alert_DBA102.logalert_DBA102.log: ASCII text

The file alert_DBA102.log is an ASCII text file. Let’s see some more examples:

# file initTESTAUX.ora.ZinitTESTAUX.ora.Z: compress'd data 16 bits

This tells you that the file is a compressed file, but how do you know the type of the file was compressed? One option is to uncompress it and run file against it; but that would make it virtually impossible. A cleaner option is to use the parameter-z:

# file -z initTESTAUX.ora.ZinitTESTAUX.ora.Z: ASCII text (compress'd data 16 bits)

Another quirk is the presence of symbolic links:

# file spfile+ASM.ora.ORIGINAL   spfile+ASM.ora.ORIGINAL: symbolic link to /u02/app/oracle/admin/DBA102/pfile/spfile+ASM.ora.ORIGINAL

This is useful; but what type of file is that is being pointed to? Instead of running file again, you can use the option-l:

# file -L spfile+ASM.ora.ORIGINALspfile+ASM.ora.ORIGINAL: data

This clearly shows that the file is a data file. Note that the spfile is a binary one, as opposed to init.ora; so the file shows up as data file.

Tip for Oracle Users

Suppose you are looking for a trace file in the user dump destination directory but are unsure if the file is located on another directory and merely exists here as a symbolic link, or if someone has compressed the file (or even renamed it). There is one thing you know: it’s definitely an ascii file. Here is what you can do:

file -Lz * | grep ASCII | cut -d":" -f1 | xargs ls -ltr

This command checks the ASCII files, even if they are compressed, and lists them in chronological order.

Comparing Files

How do you find out if two files—file1 and file2—are identical? There are several ways and each approach has its own appeal.

diff.The simplest command isdiff, which shows the difference between two files. Here are the contents of two files:

# cat file1In file1 onlyIn file1 and file2# cat file2In file1 and file2In file2 only

If you use thediffcommand, you will be able to see the difference between the files as shown below:

# diff file1 file21d0< In file1 only2a2> In file2 only#

In the output, a "<" in the first column indicates that the line exists on the file mentioned first,—that is, file1. A ">" in that place indicates that the line exists on the second file (file2). The characters 1d0 in the first line of the output shows what must be done insedto operate on the file file1 to make it same as file2.

Another option,-y, shows the same output, but side by side:

# diff -y file1 file2 -W 120In file1 only                             <In file1 and file2                             In file1 and file2                                          >    In file2 only

The-Woption is optional; it merely instructs the command to use a 120-character wide screen, useful for files with long lines.

If you just want to just know if the files differ, not necessarily how, you can use the-qoption.

# diff -q file3 file4# diff -q file3 file2Files file3 and file2 differ

Files file3 and file4 are the same so there is no output; in the other case, the fact that the files differ is reported.

If you are writing a shell script, it might be useful to produce the output in such a manner that it can be parsed. The-uoption does that:

# diff -u file1 file2        --- file1       2006-08-04 08:29:37.000000000 -0400+++ file2       2006-08-04 08:29:42.000000000 -0400@@ -1,2 +1,2 @@-In file1 only In file1 and file2+In file2 only

The output shows contents of both files but suppresses duplicates, the + and - signs in the first column indicates the lines in the files. No character in the first column indicates presence in both files.

The command considers whitespace into consideration. If you want to ignore whitespace, use the-boption. Use the-Boption to ignore blank lines. Finally, use-ito ignore case.

Thediffcommand can also be applied to directories. The command

diff dir1 dir2

shows the files present in either directories; whether files are present on one of the directories or both. If it finds a subdirectory in the same name, it does not go down to see if any individual files differ. Here is an example:

# diff DBA102 PROPRD     Common subdirectories: DBA102/adump and PROPRD/adumpOnly in DBA102: afiedt.bufOnly in PROPRD: archiveOnly in PROPRD: BACKUPOnly in PROPRD: BACKUP1Only in PROPRD: BACKUP2Only in PROPRD: BACKUP3Only in PROPRD: BACKUP4Only in PROPRD: BACKUP5Only in PROPRD: BACKUP6Only in PROPRD: BACKUP7Only in PROPRD: BACKUP8Only in PROPRD: BACKUP9Common subdirectories: DBA102/bdump and PROPRD/bdumpCommon subdirectories: DBA102/cdump and PROPRD/cdumpOnly in PROPRD: CreateDBCatalog.logOnly in PROPRD: CreateDBCatalog.sqlOnly in PROPRD: CreateDBFiles.logOnly in PROPRD: CreateDBFiles.sqlOnly in PROPRD: CreateDB.logOnly in PROPRD: CreateDB.sqlOnly in DBA102: dpdumpOnly in PROPRD: emRepository.sqlOnly in PROPRD: init.oraOnly in PROPRD: JServer.sqlOnly in PROPRD: logOnly in DBA102: oradataOnly in DBA102: pfileOnly in PROPRD: postDBCreation.sqlOnly in PROPRD: RMANTEST.shOnly in PROPRD: RMANTEST.sqlCommon subdirectories: DBA102/scripts and PROPRD/scriptsOnly in PROPRD: sqlPlusHelp.logCommon subdirectories: DBA102/udump and PROPRD/udump

Note that the common subdirectories are simply reported as such but no comparison is made. If you want to drill down even further and compare files under those subdirectories, you should use the following command:

diff -r dir1 dir2

This command recursively goes into each subdirectory to compare the files and reports the difference between the files of the same names.

Tip for Oracle Users

One common use ofdiffis to differentiate between different init.ora files. As a best practice, I always copy the file to a new name—e.g. initDBA102.ora to initDBA102.080306.ora (to indicate August 3,2006)—before making a change. A simplediffbetween all versions of the file tells quickly what changed and when.

This is a pretty powerful command to manage your Oracle home. As a best practice, I never update an Oracle Home when applying patches. For instance, suppose the current Oracle version is 10.2.0.1. The ORACLE_HOME could be /u01/app/oracle/product/10.2/db1. When the time comes to patch it to 10.2.0.2, I don’t patch this Oracle Home. Instead, I start a fresh installation on /u01/app/oracle/product/10.2/db2 and then patch that home. Once it’s ready, I use the following:

# sqlplus / as sysdbaSQL> shutdown immediateSQL> exit# export ORACLE_HOME=/u01/app/oracle/product/10.2/db2# export PATH=$ORACLE_HOME/bin:$PATH# sqlplus / as sysdbaSQL> @$ORACLE_HOME/rdbms/admin/catalog...

and so on.

The purpose of this approach is that the original Oracle Home is not disturbed and I can easily fall back in case of problems. This also means the database is down and up again, pretty much immediately. If I installed the patch directly on the Oracle Home, I would have had to shut the database for a long time—for the entire duration of the patch application. In addition, if the patch application had failed due to any reason, I would not have a clean Oracle Home.

Now that I have several Oracle Homes, how can I see what changed? It’s really simple; I can use:

diff -r /u01/app/oracle/product/10.2/db1 /u01/app/oracle/product/10.2/db2 | grep -v Common

This tells me the differences between the two Oracle Homes and the differences between the files of the same name. Some important files like tnsnames.ora, listener.ora, and sqlnet.ora should not show wide differences, but if they do, then I need to understand why.

cmp.The commandcmpis similar todiff:

# cmp file1 file2   file1 file2 differ: byte 10, line 1

The output comes back as the first sign of difference. You can use this to identify where the files might be different. Likediff,cmphas a lot of options, the most important being the-soption, that merely returns a code:

  • 0, if the files are identical
  • 1, if they differ
  • Some other non-zero number, if the comparison couldn’t be made

Here is an example:

# cmp -s file3 file4# echo $?0

The special variable$?indicates the return code from the last executed command. In this case it’s 0, meaning the files file1 and file2 are identical.

# cmp -s file1 file2# echo $?1

means file1 and file2 are not the same.

This property ofcmpcan prove very useful in shell scripting where you merely want to check if two files differ in any way, but not necessarily check what the difference is. Another important use of this command is to compare binary files, wherediffmay not be reliable.

Tip for Oracle Users

Recall from a previous tip that when you relink Oracle executables, the older version is kept prior to being overwritten. So, when you relink, the executable sqlplus is renamed to “sqlplusO” and the newly compiled sqlplus is placed in the $ORACLE_HOME/bin. So how do you ensure that the sqlplus that was just created is any different? Just use:

# cmp sqlplus sqlplusOsqlplus sqlplusO differ: byte 657, line 7

If you check the size:

# ls -l sqlplus*-rwxr-x--x    1 oracle   dba          8851 Aug  4 05:15 sqlplus-rwxr-x--x    1 oracle   dba          8851 Nov  2  2005 sqlplusO

Even though the size is the same in both cases,cmpproved that the two programs differ.

comm.The commandcommis similar to the others but the output comes in three columns, separated by tabs. Here is an example:

# comm file1 file2        In file1 and file2In file1 onlyIn file1 and file2        In file2 only

Summary of Commands in This Installment


CommandUse

chmod

To change permissions of a file, using the - -reference parameter

chown

To change owner of a file, using the - -reference parameter

chgrp

To change group of a file, using the - -reference parameter

stat

To find out about the extended attributes of a file, such as date last accessed

file

To find out about the type of file, such ASCII, data, and so on

diff

To see the difference between two files

cmp

To compare two files

comm

To see what’s common between two files, with the output in three columns

md5sum

To calculate the MD5 hash value of files, used to determine if a file has changed

This command is useful when you may want to see the contents of a file not in the other, not just a difference—sort of a MINUS utility in SQL language. The option-1suppresses the contents found in first file:

# comm -1 file1 file2In file1 and file2In file2 only

md5sum.This command generates a 32-bit MD5 hash value of the files:

# md5sum file1ef929460b3731851259137194fe5ac47  file1

Two files with the same checksum can be considered identical. However, the usefulness of this command goes beyond just comparing files. It can also provide a mechanism to guarantee the integrity of the files.

Suppose you have two important files—file1 and file2—that you need to protect. You can use the--checkoption check to confirm the files haven't changed. First, create a checksum file for both these important files and keep it safe:

# md5sum file1 file2 > f1f2

Later, when you want to verify that the files are still untouched:

# md5sum --check f1f2      file1: OKfile2: OK

This shows clearly that the files have not been modified. Now change one file and check the MD5:

# cp file2 file1# md5sum --check f1f2file1: FAILEDfile2: OKmd5sum: WARNING: 1 of 2 computed checksums did NOT match

The output clearly shows that file1 has been modified.

Tip for Oracle Users

md5sumis an extremely powerful command for security implementations. Some of the configuration files you manage, such as listener.ora, tnsnames.ora, and init.ora, are extremely critical in a successful Oracle infrastructure and any modification may result in downtime. These are typically a part of your change control process. Instead of just relying on someone’s word that these files have not changed, enforce it using MD5 checksum. Create a checksum file and whenever you make a planned change, recreate this file. As a part of your compliance, check this file using themd5sumcommand. If someone inadvertently updated one of these key files, you would immediately catch the change.

In the same line, you can also create MD5 checksums for all executables in $ORACLE_HOME/bin and compare them from time to time for unauthorized modifications.

Conclusion

Thus far you have learned only some of the Linux commands you will find useful for performing your job effectively. In the next installment, I will describe some more sophisticated but useful commands, such asstrace,whereis,renice,skill, and more.


신고
Posted by The.민군

Build a Google Earth Interface on Oracle Database XE


by Rich Gibson

Get an overview of spatial data, explore ways to add spatial attributes to existing data, and learn how to use Google Earth to "fly" over aerial imagery including that data.

Published August 2006

How we look at our data forms our view of the world. Until recently most of us have ignored location because we lacked the tools (or the ability to use those tools) needed to acquire, manage, and present the spatial component of our data. This means we have missed the geospatial component of human experience. Everything that we do, think, or experience, we do, think, or experience somewhere.

Until recently it has been complicated to connect a spatially enabled database with a visualization engine: to put our customers (and whole supply chain) onto a map. That is now a solved problem. In this article you will get an introduction to spatial data, explore ways to add spatial attributes to existing data, and learn how to use the free Google Earth program to let us ‘fly’ over aerial imagery that includes your data.

Some of these techniques are a bit awkward since we are in the early days of non-specialist access to geospatial visualization and analysis tools. Some of these tools are amazing, such as the ability to use Google Sketch Up to add 3D models of any feature to Google Earth, and to do that for free! But sometimes the tools are missing ‘obvious’ features, like a report writer that supports cross-tabbing, but can’t handle multiple levels of summation. But like the early days of any technology, the greatest rewards will go to those who take the time to learn the technology early, while the standards are incomplete and some of the tools still awkward.

To use spatial tools you need to add spatial attributes to your data. Spatial attributes are present in any information that has a location or spatial component. There are a huge number of details about spatial coordinate systems, from which geode you use (your model of the shape of the earth), to the Datum (where is the zero point on the X-Y-Z axis you choose), to the Projection of the curved surface of the Earth onto a flat map (which enables the curved surface of a sphere to be represented on a flat map). This is a rich, deep, and fascinating topic of study that we can safely ignore for now.

For our purposes we represent a location on the Earth with latitude and longitude. Lines of latitude circle the earth like layers of a wedding cake, with the base, or latitude 0, being the equator. There are 90 degrees of latitude from the equator to the North, or South, poles. Each degree of Latitude is roughly 69 miles. North of the equator we talk about degrees of latitude North, south of the equator we say degrees of latitude south.

Degrees of longitude divide the earth from North to South Poles like slices of an orange. While the equator is a natural feature, the lines of longitude start at the arbitrarily defined Prime Meridian that passes through Greenwich England. Lines of longitude converge as they approach the poles. A degree of longitude varies from roughly 69 miles at the equator, to zero miles at the north or south poles.

To simplify things for computer representation it is customary to represent degrees of latitude south of the equator and degrees of longitude West of the Prime Meridian as negative numbers. Latitude and longitude are commonly represented either as degrees, minutes, and seconds, or as decimal degrees. Decimal degrees are much simpler to work with, so this is what we’ll use in this article.

For example, Oracle Headquarters is at 500 Oracle Parkway, Redwood Shores, CA 94065, or ‘roughly’ at 37.529526,-122. 263969 (37.529526 degrees North Latitude by 122. 263969 degrees West Longitude).

This is an example of having more precision than is reasonable! You don’t need six decimals of precision in geographic coordinates to show a building. But how many digitsdoyou need? We know that one degree of latitude is 69 miles. So a tenth of a degree is 6.9 miles, a hundredth of a degree is .69 miles, and so on (See table below). Four digits of precision is more than enough if we just have a single point to mark the building.

사용자 삽입 이미지

Next, let's consider the three main forms of spatial data: points, lines, and polygons.

Points are easy: a single latitude and longitude marks a single spot. Lines and Polygons are simply a series of connected points. Lines are used for linear features like roads and rivers, while polygons are lines where the last point connects back to the first, thus defining an area. The route that a delivery truck takes would be a line, while a sales territory would likely be described as a polygon.

Now I'll explain how you can use the free Oracle Database XE database to experiment with storing, managing and analyzing spatial data.

On Simplicity

The easiest way to store a latitude and longitude is in a number field. Using number fields to store coordinates works well for doing simple things with points. Once your needs grow you can use Locator, an XE subset of the full Oracle Spatial tools found in commercial Oracle offerings. With Locator you move from thinking of a simple lat/long to working with "Geometries." The sdo_geometry data type can store points, lines, polygons, as well as more complicated geometries like multi points, multi lines and multi polygons. You also get operators that include allowing you to calculate distances between geometries, find nearest neighbors, and determine if two geometries intersect ("Does Interstate 70 go into Colorado?").

Locator is great to use once you move beyond putting points on a map, but for simple problems use simple tools.

Oracle Database XE, available as adownloadfrom Oracle Technology Network, is available in Windows and Linux versions. For a Linux installation, download the RPM, and then install it:

rpm –ivh oracle-xe-10.2.0.1-1.0.i386.rpm

I had two minor issues with my installation. First, I did not have enough swap space enabled. Rather than create a larger swap partition on my hard drive, I set up, and activated, more swap space by followingthese Red Hat instructions. To summarize, you need to create a file to be used for swap, designate it as a swap file, and then turn it on. This set of commands works:

dd if=/dev/zero of=/path-to-swap/swapfile bs=1024 count=1200000
mkswap /path-to-swap/swapfile
swapon /path-to-swap/swapfile

You configure and largely work with Oracle Database XE through the Web interface. If you’ve installed XE on your local machine, point your browser to http://127.0.0.1:8080/apex. You may run into a slight issue if you’ve installed it on a remote server, as by default the web interface only works for a local client.

You can enable access to remote clients by using the Administration section of the Web interface. (Alternatively, you can edit the XE config file located at /etc/init.d/oracle-xe.) There are two problems with this approach, however. For one, it exposes your database management in a manner that raises more security concerns. But the larger problem is that in order to enable remote access to the Web interface you need to have local Web access. This is a challenge for a remote headless server.

One way of addressing this problem is to use the Open SSH program that comes with Linux and Macintosh OSX. Open SSH includes a built-in Socks 4 proxy. You can connect with your remote server using this command:

ssh -D1080 username@yourserver.com

It will then look like you have set up a normal ssh connection to your server, but in the background SSH will be listening to port 1080, and carrying all those requests to your remote server. This also means that all your HTTP traffic is now encrypted until it gets to your server. This can be a very useful feature when you are on untrusted public networks!

The final step here is set up your browser proxy. In Firefox, selectPreferences->General->Connection. Set your socks host as localhost on port 1080, select Socks v4, and where it saysno proxy formake sure to remote 127.0.0.1 and localhost.

Once you have done this you can use the Web interface to do almost everything with your database. Since business data is (often) spatial data, let’s set up an example of adding latitude and longitude to addresses.

Create an address table with the SQL section of the Web Interface.

create table address (name varchar(128), address1 varchar(128),
address2 varchar(128), city varchar(128), state char(2),
zip char(9), latitude number(7,5), longitude number(8,5))

Load a few addresses:

insert into address (name, address1, city, state, zip)
values ('Oracle', '500 Oracle Parkway', 'Redwood Shores', 'CA', '94065')

insert into address (name, address1, city, state, zip)
values ('OReilly Media ', '1005 Gravenstein Highway North', 'Sebastopol', 'CA', '95472')

The process of adding latitude and longitude to other data is called geocoding. The full version of Oracle Spatial includes the SDO_GCDR package, which supports Geocoding. With Oracle Database XE you can use the Geocoder.us Web service to add geocodes for our addresses. For two addresses you would probably just look up the coordinates and manually update them, but you never have just two addresses!

Geocoder.us provides several Web services interfaces that take an address and return coordinates. The simplest is the Comma Separated Values (CSV) interface. You can enter a URL in your browser and get the coordinates. This address:

http://rpc.geocoder.us/service/csv?address=500 Oracle Parkway,Redwood Shores,CA,94065

returns:

37.529526,-122.263969,500 Oracle Pky,Redwood City,CA,94065

You can also call this from PHP. This code will take an address from the command line, call geocoder.us, and return the coordinates:

<?PHP
$address = $argv[1];
echo "query address: $address \n";
$url = "http://rpc.geocoder.us/service/csv?address=" . (urlencode($address));
$w = fopen($url,"r");
$result = fgetcsv($w,8000);
fclose($w);

$latitude = $result["0"];
$longitude = $result["1"];

echo "latitude $latitude longitude $longitude\n";
?>

Include the address and call this from the command line. (When you call PHP from the command line you can add the –q switch to suppress the normal http content-type headers):

php -q ./php_work.php '1600 pennsylvania ave, washington, dc'

which returns:

query address: 1600 pennsylvania ave, washington, dc
latitude 38.898748 longitude -77.037684

That gives you the simplest case for connecting with Geocoder.us. The next step is to get your addresses from the database, and then to update the database with the latitude and longitude returned from geocoder.us.

First you need to get PHP working with Oracle—seethese instructions. The one "gotcha" I experienced with these instructions was that my copy of apxs was not in the default location, so when I ran configure I replaced --with-apxs2=/usr/local/apache/bin/apxs with --with-apxs2=/usr/sbin/apxs.

The following code will read our address table, geocode each address, and then update the table with the latitude and longitude.

<?PHP

# create a connection to the database instance on localhost. If you
# have not done anything 'clever' the username 'system' will work
# with the password that you defined at installation
$conn=oci_connect('username','password', "//127.0.0.1/XE");

# Query our address table
$sql = "SELECT name, address1, city, state, zip from address";

# oci_parse is part of the Oracle PHP library to parse the SQL statement
$stmt = oci_parse($conn, $sql);

# oci_execute not surprisingly executes the statement we parsed in the previous line
oci_execute($stmt);

# This loads the associative array $row with the values of each row in our
# database in turn
while ( $row = oci_fetch_assoc($stmt) ) {
# print_r dumps a variable, including all of the keys for an associative array
print_r($row);
# assemble the query variable for our call to geocoder.us
$address = $row["ADDRESS1"] . "," . $row["CITY"] . "," . $row["STATE"] . " " . $row["ZIP"];
# pull the name out of the associative array
$name = $row["NAME"];
# the url to the free service of geocoder.us to return the data in CSV format
$url = "http://rpc.geocoder.us/service/csv?address=" . (urlencode($address));
# open the url
$w = fopen($url,"r");
#parse the CSV returned from the page into the array $result
$result = fgetcsv($w,8000);
fclose($w);

$latitude = $result["0"];
$longitude = $result["1"];

# query to update the address table with the lat/long we got from geocoder.us
# granted it is poor database design to have such an uncertain key as 'name'
# be our primary key…I'll leave it as an exercise to the reader to implement
# this code in a way that doesn't make DBA's cry.
$sqlu = "update address set =$latitude, =$longitude where NAME='$name'";
echo "sqlu $sqlu\n";
# as before, parse the SQL statement
$stmtu = oci_parse($conn, $sqlu);
# and execute the statement
oci_execute($stmtu);
}
?>

3D Maps Without Sherpas

All that effort has provided you with a database that includes spatial data. Now you know where your customers are! There are a lot of things you can do with that information, such as market analysis, planning advertising campaigns and sales trips, and routing delivery trucks, but the first thing we all want is to see our data on a map. Two years ago it was a pain to display data on a map; you either had to use expensive and specialized tools, or complicated tools with Himalayan learning curves. Luckily, it is now much easier.

Google Earth is a desktop application for Windows and the Mac that goes beyond the map and provides a 3D model of the world. In a growing number of urban areas, this also includes 3D building outlines. You can also use Google Sketch Up to add your own 3D models to the display.

Google Earth is available athttp://earth.google.com. There is a free version, as well as Google Earth Plus for $20 a year and Google Earth Pro for $400 a year. The pay versions add features such as global positioning system (GPS) integration. (See a comparison gridhere.) The Web site instructions are easy enough, so download the program and start it up! When you do this you are initially shown a view of the Earth, as though you were in space. You can then zoom in, search for places, explore the world, and generally satisfy your urge to make like Lewis and Clark—a modern-day member of the Corps of Discovery.

There is an active Google Earth community that publishes files of spatial information. For example, gohereto read about, and download, a Google Earth file of the Lewis and Clark Voyage of Discovery: 29917-lewis_and_clark_expedition.kmz. You'll notice that Google Earth files have the extension KML, for Keyhole Markup Language (Google Earth was originally called Keyhole), or KMZ for Keyhole Markup compressed with gzip. The KMZ file can be uncompressed with WinZip or Stuffit Expander.

I love my GUI, except when I try and script things, so from an OSX terminal command line, you can enter:

gunzip -S ".kmz" 29917-lewis_and_clark_expedition.kmz

When you open the file, you’ll see it is simply XML. Okay, perhaps not entirely simple, but well within the range of easy to read. Explore the Google Earth KMLdocumentation, or just dig in!

For example, here is theplacemarkdescribingCamp Disappointment.

<Placemark>
<description><![CDATA[
<a href="http://www.lewis-clark.org/content/content-article.asp?ArticleID=1069">
Click to read entry</a>]]></description>
<name>17: Camp Disappointment</name>
<LookAt>
<longitude>-112.820632</longitude>
<latitude>48.716670</latitude>
<range>1000.000</range>
<tilt>0</tilt>
<heading>0</heading>
</LookAt>
<styleUrl>root://styles#khStyle929</styleUrl>
<Point>
<coordinates>-112.820632,48.716670,0</coordinates>
</Point>
</Placemark>

When you bring up the Lewis and Clark file, you’ll see how Google Earth renders this description of Camp Disappointment.

The <name> element is shown on the map at the X, Y, Z coordinates specified within the<coordinates> element of the Point element (longitude = X, latitude = Y, altitude = Z, or 0 in this example). When you single click on the point you see a pop up, called a description balloon, with the contents of the name and description elements, and options to get Directions to or from this point.

Make special note that the description can contain a URL. So if this placemark described one of your customers you could include additional attributes about the customer within the callout, and then embed a link to the customer’s page within your CRM system.

The <styleURL> element contains a link to a URL of the "pushpin" that marks this location. In this case the style is contained on the local file system. The description of a style can also be contained within the KML document, or at an external URL.

Finally you get to the <LookAt> element. When you double-click on a placemark you are moved to the longitude and latitude specified within the <LookAt> element, and given the point of view described by the <heading>, <tilt>, and <range> elements. This allows you to create a placemark where you specify a point of view. For example, this is a complete KML file that says, "Go to Crissy Field in San Francisco and look at the Golden Gate Bridge":

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<Placemark>
<name>crissy field</name>
<LookAt>
<longitude>-122.4592370657115</longitude>
<latitude>37.8050682478946</latitude>
<altitude>0</altitude>
<range>1000.275193579794</range>
<tilt>90</tilt>
<heading>315</heading>
</LookAt>
<styleUrl>root://styles#default</styleUrl>
<Point>
<coordinates>-122.4592370657115,37.8050682478946,0</coordinates>
</Point>
</Placemark>
</kml>

In the interests of simplification, you can eliminate many of those attributes. Here is about the simplest useful KML description of two distinct places:

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<Folder>
<Placemark>
<name>First Place</name>
<Point>
<coordinates>-122.5,37.8,0</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Random Place</name>
<Point>
<coordinates>-122.6,37.9,0</coordinates>
</Point>
</Placemark>
</Folder>
</kml>

And here is a PHP program to grab our geocoded addresses and produce a KML file:

<?PHP

$conn=oci_connect('username','password', "//127.0.0.1/XE");
$sql = "SELECT name, address1, city, state, zip, latitude, longitude from address a";
$stmt = oci_parse($conn, $sql);
oci_execute($stmt);

print '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
print '<kml xmlns="http://earth.google.com/kml/2.0">' . "\n";
print '<Folder>' . "\n";
while ( $row = oci_fetch_assoc($stmt) ) {
$address = $row["ADDRESS1"] . "," . $row["CITY"] . "," . $row["STATE"] . " " . $row["ZIP"];
$name = $row["NAME"];
$latitude = $row["LATITUDE"];
$longitude = $row["LONGITUDE"];
print "<Placemark>\n";
print " <name>$name</name>\n";
print " <description>$address</description>\n";
print " <Point>\n";
print " <coordinates>$longitude,$latitude,0</coordinates>\n";
print " </Point>\n";
print "</Placemark>\n";
}
print '</Folder>' . "\n";
print '</kml>' . "\n";
?>

This is the caveman coder’s version that uses print statements designed to show all the details. In practice, you would want to use an XML library. Take a look at the Pear/XML/sql2xml class documented at:http://php.chregu.tv/sql2xml/. Furthermore, by using dynamic URL parameters and variables, we could easily add interesting context to this query (or other queries) whereby our spatial analysis would mean that much more.

Stay Focused

You now have the ability to create a database of addresses, add latitude and longitude to that data by geocoding, and then export the data so that you can see your data on Google Earth (assuming you are not still reading journal entries from Lewis and Clark and the Voyage of Discovery, and getting lost in pleasurable exploration and Location Creep!).


Rich Gibson(http://mappinghacks.com) is a mapping, geospatial and geocoding consultant and the co-author ofMapping Hacks: Tips & Tools for Electronic Cartography(O’Reilly, 2005) andGoogle Maps Hacks(O'Reilly, 2006).
신고
Posted by The.민군

Java XPath API (한글)

XML 2006.11.29 00:06

Java XPath API (한글)

자바 프로그램에서 XML 쿼리하기

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
문서 옵션
사용자 삽입 이미지이 페이지를 이메일로 보내기');// :badtag -->
사용자 삽입 이미지사용자 삽입 이미지

이 페이지를 이메일로 보내기

사용자 삽입 이미지
사용자 삽입 이미지

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.


제안 및 의견
사용자 삽입 이미지피드백
사용자 삽입 이미지

난이도 : 중급

Elliotte Rusty Harold, Adjunct Professor, Polytechnic University

2006 년 9 월 04 일
2006 년 10 월 17 일 수정

XPath 식은 상세한 Document Object Model (DOM) 네비게이션 코드 보다 작성하기가 훨씬 더 쉽습니다. XML 문서에서 정보를 추출하는 가장 빠르고 간단한 방법은 자바 프로그램 안에 XPath 식을 삽입하는 것입니다. Java 5에는 XPath로 문서를 쿼리하는 XML 객체-모델 독립형 라이브러리인 javax.xml.xpath 패키지가 포함되었습니다.

누군가에게 우유를 사오라고 시켜야 한다면, 그 사람에게 어떻게 말할 것인가? "우유를 좀 사다주겠니?" 라고 할 것인가? 아니면 "저기 현관문을 통해 이 집을 나가라. 저 길에서 좌회전을 하고 세 블록을 더 걸어라. 오른쪽으로 돌아서 반 블록만 걸어라. 오른쪽으로 돌아서 가게로 들어가라. 4번 통로로 가라. 그 통로에서 5미터를 걸어라. 좌회전 한 다음 우유 병을 집어서 계산대로 가져가라. 계산을 하고 다시 집으로 오라." 정말 웃기지 않은가? 웬만한 성인들이라면 "우유를 사다 주십시오."라는 간단한 부탁에 우유를 사다 줄 정도의 지능은 있다.

쿼리 언어와 컴퓨터 검색은 비슷하다. 데이터베이스 검색을 위해 상세한 로직을 작성하는 것 보다, "Find a copy of Cryptonomicon" 이라고 하는 것이 더 쉽다. 검색 연산은 매우 비슷한 로직을 갖고 있기 때문에 "Find all the books by Neal Stephenson" 같은 문장을 만들 수 있는 일반 언어를 고안하고 특정 데이터 스토어에 대해 쿼리를 처리하는 엔진을 작성할 수 있다.

XPath

많은 쿼리 언어들 중에, Structured Query Language (SQL)는 특정 유형의 관계형 데이터베이스에 최적화 된 언어이다. 기타 쿼리 언어로는 Object Query Language (OQL)와 XQuery가 있다. 하지만 이 글의 주제는 XML 문서를 쿼리하도록 설계된 XPath이다. 예를 들어, 한 문서에서 저자가 Neal Stephenson인 모든 책을 찾으라는 간단한 XPath 쿼리는 다음과 같다.

//book[author="Neal Stephenson"]/title

반대로 같은 정보에 대한 순수 DOM 검색은Listing 1과 같다:


Listing 1. 저자가 Neal Stephenson인 모든 책 제목을 찾는 DOM 코드
ArrayList result = new ArrayList();        NodeList books = doc.getElementsByTagName("book");        for (int i = 0; i < books.getLength(); i++) {            Element book = (Element) books.item(i);            NodeList authors = book.getElementsByTagName("author");            boolean stephenson = false;            for (int j = 0; j < authors.getLength(); j++) {                Element author = (Element) authors.item(j);                NodeList children = author.getChildNodes();                StringBuffer sb = new StringBuffer();                for (int k = 0; k < children.getLength(); k++) {                    Node child = children.item(k);                    // really should to do this recursively                    if (child.getNodeType() == Node.TEXT_NODE) {                        sb.append(child.getNodeValue());                    }                }                if (sb.toString().equals("Neal Stephenson")) {                    stephenson = true;                    break;                }            }            if (stephenson) {                NodeList titles = book.getElementsByTagName("title");                for (int j = 0; j < titles.getLength(); j++) {                    result.add(titles.item(j));                }            }        }

Listing 1의 DOM 코드는 간단한 XPath 식 만큼 포괄적이거나 강력하지 않다. 어떤 것을 작성, 디버깅, 관리하겠는가? 해답은 명확하다.

하지만 표현적인 특성 그 자체로 보자면 XPath는 자바 언어가 아니다. 사실 XPath는 완전한 프로그래밍 언어가 아니다. XPath로 많은 것을 표현할 수 없다. 쿼리도 예외는 아니다. 예를 들어, XPath는 International Standard Book Number (ISBN) 식별 번호가 맞지 않거나 로열티를 지불해야 하는 모든 저자들을 찾을 수 없다. 다행히도, XPath와 자바 프로그램을 통합할 수 있다. 자바의 장점과 XPath의 장점만을 취할 수 있는 것이다.

최근까지, 자바 프로그램이 XPath 쿼리를 만들 때 사용했던 정확한 애플리케이션 프로그램 인터페이스(API)는 XPath 엔진에 따라 변했다. Xalan은 하나의 API를, Saxon은 또 다른 API를, 또 다른 엔진은 다른 API를 갖고 있었다. 이상적으로는 다양한 퍼포먼스 특성을 갖춘 다양한 엔진들을 사용하고 싶을 것이다.

이러한 이유 때문에 Java 5에서는javax.xml.xpath패키지를 도입하여 엔진과 객체 모델에 독립적인 XPath 라이브러리를 선보였다. 이 패키지는 XML Processing (JAXP) 1.3용 자바 API를 설치한다면 Java 1.3과 이후 버전에서도 사용할 수 있다. 다른 제품들 중에서도, Xalan 2.7과 Saxon 8에는 이 라이브러리가 포함되어 있다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


간단한 예제

이것이 실제로 어떻게 작동하는지를 먼저 설명한 다음 세부적으로 들어가 보도록 하겠다. Neal Stephenson이 집필한 도서 리스트를 쿼리한다고 가정해 보자. 특별히, 그 리스트는Listing 2의 형식으로 되어 있다:


Listing 2. 도서 정보를 포함하고 있는 XML 문서
<inventory>    <book year="2000">        <title>Snow Crash</title>        <author>Neal Stephenson</author>        <publisher>Spectra</publisher>        <isbn>0553380958</isbn>        <price>14.95</price>    </book>     <book year="2005">        <title>Burning Tower</title>        <author>Larry Niven</author>        <author>Jerry Pournelle</author>        <publisher>Pocket</publisher>        <isbn>0743416910</isbn>        <price>5.99</price>    <book>     <book year="1995">        <title>Zodiac</title>        <author>Neal Stephenson<author>        <publisher>Spectra</publisher>        <isbn>0553573862</isbn>        <price>7.50</price>    <book>    <!-- more books... --> </inventory>

사용자 삽입 이미지
추상 팩토리

XPathFactory는 추상 팩토리이다. 추상 팩토리 디자인 패턴에서는 하나의 API가 DOM, JDOM, XOM 같은 다양한 객체 모델을 지원한다. 다양한 모델을 선택하려면 객체 모델을 구분하는 Uniform Resource Identifier (URI)를XPathFactory.newInstance()메소드로 전달한다. 예를 들어, http://xom.nu/는 XOM을 채택한다. 하지만 실제로, 현재까지 이 API가 지원하는 객체 모델은 DOM이 유일하다.

모든 책을 찾는 XPath 쿼리는 간단하다://book[author="Neal Stephenson"]. 책들의 제목을 찾으려면 한 단계만 더 추가하면 되고, 식은 다음과 같다.//book[author="Neal Stephenson"]/title. 마지막으로 여러분이 진정으로 원하는 것은title엘리먼트의 텍스트 노드 자식들이다. 여기에는 한 단계가 더 필요하다. 그래서 전체 식은 다음과 같이 된다://book[author="Neal Stephenson"]/title/text().

이제, 자바 언어에서 이 검색을 실행하고, 검색된 모든 책들의 제목을 프린트하는 간단한 프로그램을 만들 것이다. 우선, 문서를 DOMDocument객체로 문서를 로딩해야 한다. 이 문서가 현재 실행 디렉토리의 books.xml 파일에 있는 것으로 간주한다. 다음은 문서를 파싱하고 상응하는Document객체를 만드는 코드이다:


Listing 3. JAXP로 문서 파싱하기
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();        factory.setNamespaceAware(true); // never forget this!        DocumentBuilder builder = factory.newDocumentBuilder();        Document doc = builder.parse("books.xml");

지금까지는, 단순한 표준 JAXP와 DOM에 불과하다. 새로운 것은 아무것도 없다.

다음에는XPathFactory를 만든다:

XPathFactory factory = XPathFactory.newInstance();

이 팩토리를 사용하여XPath객체를 만든다:

XPath xpath = factory.newXPath();

XPath객체가 XPath 식을 컴파일 한다:

PathExpression expr = xpath.compile("//book[author='Neal Stephenson']/title/text()");

사용자 삽입 이미지
Immediate evaluation

XPath 식을 단 한번만 사용하려고 한다면 컴파일 단계를 건너 뛰고 대신XPath객체에evaluate()메소드를 호출한다. 하지만 같은 식을 여러 번 재사용 하면 컴파일이 더 빠르다.

마지막으로 XPath 식을 계산하여 결과를 얻는다. 이 식은 특정 콘텍스트 노드에 따라 계산된다. 이 경우에는 전체 문서이다. 리턴 유형을 지정하는 것 역시 필수이다. 여기에서 node-set을 요청한다:

Object result = expr.evaluate(doc, XPathConstants.NODESET);

결과를 DOMNodeList에 던지고 이것을 반복하여 모든 제목을 찾는다:

NodeList nodes = (NodeList) result;        for (int i = 0; i < nodes.getLength(); i++) {            System.out.println(nodes.item(i).getNodeValue());         }

Listing 4는 이 모든 것을 하나의 프로그램으로 모은 것이다. 이러한 방식으로throws문에서 선언해야 하는 여러 예외들을 던질 수 있다.


Listing 4. 고정된 XPath 식으로 XML 문서를 쿼리하는 전체 프로그램
import java.io.IOException;import org.w3c.dom.*;import org.xml.sax.SAXException;import javax.xml.parsers.*;import javax.xml.xpath.*;public class XPathExample {  public static void main(String[] args)    throws ParserConfigurationException, SAXException,           IOException, XPathExpressionException {    DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();    domFactory.setNamespaceAware(true); // never forget this!    DocumentBuilder builder = domFactory.newDocumentBuilder();    Document doc = builder.parse("books.xml");    XPathFactory factory = XPathFactory.newInstance();    XPath xpath = factory.newXPath();    XPathExpression expr      = xpath.compile("//book[author='Neal Stephenson']/title/text()");    Object result = expr.evaluate(doc, XPathConstants.NODESET);    NodeList nodes = (NodeList) result;    for (int i = 0; i < nodes.getLength(); i++) {        System.out.println(nodes.item(i).getNodeValue());     }  }}

XPath 데이터 모델

XPath와 자바 처럼, 다른 언어들을 혼합할 때 마다 두 개를 하나로 붙인 곳에 뚜렷한 흠집이 생기기 마련이다. 모든 것이 딱 맞지는 않는다. XPath와 자바 언어는 동일한 유형 시스템을 갖고 있지 않다. XPath 1.0은 단지 네 개의 기본 데이터 유형들만 갖고 있다:

  • node-set
  • number
  • boolean
  • string

물론, 자바는 사용자 정의 객체 유형을 비롯하여 훨씬 더 많이 갖고 있다.

대부분의 XPath 식, 특히 위치 경로는 node-set를 리턴한다. 하지만 다른 가능성도 있다. 예를 들어, XPath 식count(//book)이 문서에 있는 책의 수를 리턴한다. XPath 식count(//book[@author="Neal Stephenson"]) > 10은 부울을 리턴한다: 문서에 Neal Stephenson이 지은 책이 10권 이상이면 true 이고 10권 보다 적으면 false 이다.

evaluate()메소드는Object를 리턴하도록 선언된다. 이것이 실제로 수행하는 것은 XPath 식의 결과에 따라, 그리고 요청한 유형에 따라 리턴하는 것이다. 일반적으로 XPath는,

  • 숫자를java.lang.Double로 매핑한다.
  • 스트링을java.lang.String으로 매핑한다.
  • 부울을java.lang.Boolean으로 매핑한다.
  • node-set을org.w3c.dom.NodeList로 매핑한다.
사용자 삽입 이미지
XPath 2

지금까지, 여러분은 XPath 1.0을 사용했다. XPath 2는 유형 시스템을 많이 개정하고 확장했다. XPath 2를 지원하기 위해 자바 XPath API에 생긴 주요 변화라고 하면 새로운 XPath 2 유형들을 리턴하는 추가 상수들이다.

자바로 XPath 식을 계산할 때 두 번째 인자가 원하는 리턴 유형을 지정한다. 다섯 개의 가능성이 있는데, 모두javax.xml.xpath.XPathConstants클래스에 있는 네임드 상수이다:

  • XPathConstants.NODESET
  • XPathConstants.BOOLEAN
  • XPathConstants.NUMBER
  • XPathConstants.STRING
  • XPathConstants.NODE

마지막에XPathConstants.NODE는 XPath 유형과 맞지 않는다. XPath 식이 하나의 노드만을 리턴다는 것을 알고 있거나 단 한 개의 노드만 사용할 때 사용한다. XPath 식이 한 개 이상의 노드를 리턴하고XPathConstants.NODE를 지정했다면evaluate()이 문서 순서에서 첫 번째 노드를 리턴한다. XPath 식이 비어있는 세트를 선택하고XPathConstants.NODE를 지정했다면evaluate()은 null을 리턴한다.

변환이 되지 않으면evaluate()XPathException을 던진다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


네임스페이스 콘텍스트

XML 문서의 엘리먼트들이 네임스페이스에 있다면 그 문서를 쿼리하는 XPath 식은 같은 네임스페이스를 사용해야 한다. XPath 식은 같은 접두사를 사용할 필요가 없고 같은 네임스페이스 URI만 사용하면 된다. 정말로, XML 문서가 디폴트 네임스페이스를 사용할 때 XPath 식은 접두사를 사용해야 한다. 심지어, 목표 문서가 그렇지 않더라도 말이다.

하지만, 자바 프로그램들은 XML 문서가 아니기 때문에 정상적인 네임스페이스 방식이 적용되지 않는다. 대신 접두사를 네임스페이스 URI로 매핑하는 객체가 사용된다. 이 객체는javax.xml.namespace.NamespaceContext인터페이스의 인스턴스이다. 예를 들어 책 문서가 http://www.example.com/books 네임스페이스에 있으면Listing 5처럼 된다:


Listing 5. 기본 네임스페이스를 사용하는 XML 문서
<inventory xmlns="http://www.example.com/books">    <book year="2000">        <title>Snow Crash</title>        <author>Neal Stephenson</author>        <publisher>Spectra</publisher>        <isbn>0553380958</isbn>        <price>14.95<price>    </book>    <!-- more books... --><inventory>

Neal Stephenson의 모든 책 제목을 찾는 XPath 식은 이제//pre:book[pre:author="Neal Stephenson"]/pre:title/text()처럼 되었다. 하지만 접두사pre를 http://www.example.com/books URI에 매핑해야 한다.NamespaceContext인터페이스가 자바 소프트웨어 개발 키트(JDK)나 JAXP에 디폴트 구현을 갖고 있지 않다는 것이 약간 의아하지만 어쨌든 없다. 하지만 구현하기도 어렵지 않다.Listing 6은 하나의 네임스페이스 구현 방법을 설명한다.xml접두사를 매핑해야 한다.


Listing 6. 하나의 네임스페이스를 바인딩 하는 간단한 콘텍스트
import java.util.Iterator;import javax.xml.*;import javax.xml.namespace.NamespaceContext;public class PersonalNamespaceContext implements NamespaceContext {    public String getNamespaceURI(String prefix) {        if (prefix == null) throw new NullPointerException("Null prefix");        else if ("pre".equals(prefix)) return "http://www.example.org/books";        else if ("xml".equals(prefix)) return XMLConstants.XML_NS_URI;        return XMLConstants.NULL_NS_URI;    }    // This method isn't necessary for XPath processing.    public String getPrefix(String uri) {        throw new UnsupportedOperationException();    }    // This method isn't necessary for XPath processing either.    public Iterator getPrefixes(String uri) {        throw new UnsupportedOperationException();    }}

맵을 사용하여 바인딩을 저장하고 재사용 가능한 네임스페이스 콘텍스트에 사용되는 세터 메소드를 쉽게 추가할 수 있다.

NamespaceContext객체를 만든 다음, 식을 컴파일 하기 전에XPath객체에 이것을 설치한다. 이제부터는 전과 같이 접두사를 사용하여 쿼리할 수 있다.


Listing 7. 네임스페이스를 사용하는 XPath 쿼리
XPathFactory factory = XPathFactory.newInstance();  XPath xpath = factory.newXPath();  xpath.setNamespaceContext(new PersonalNamespaceContext());  XPathExpression expr     = xpath.compile("//pre:book[pre:author='Neal Stephenson']/pre:title/text()");  Object result = expr.evaluate(doc, XPathConstants.NODESET);  NodeList nodes = (NodeList) result;  for (int i = 0; i < nodes.getLength(); i++) {      System.out.println(nodes.item(i).getNodeValue());   }


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


함수 사용

어떤 경우에는, XPath 식에서 사용할 수 있도록 자바에서 확장 기능을 정의하면 유용하다. 이러한 기능은 순수 XPath 만으로 수행하기 불가능한 태스크를 수행한다. 하지만 이들은 모호한 메소드가 아닌 진정한 함수여야 한다. 다시 말해서, 이들은 부작용이 없어야 한다. (XPath 함수는 어떤 순서든 여러 번 계산될 수 있다.)

자바 XPath API를 통해 액세스 된 확장 함수들은javax.xml.xpath.XPathFunction인터페이스를 구현해야 한다. 이 인터페이스는 하나의 메소드, evaluate 을 선언한다:

public Object evaluate(List args) throws XPathFunctionException

이 메소드는 자바 언어가 XPath로 변환될 수 있는 다섯 가지 유형들 중 하나를 리턴한다.

  • String
  • Double
  • Boolean
  • Nodelist
  • Node

예를 들어,Listing 8은 ISBN에서 체크섬을 확인하고Boolean을 리턴하는 확장 함수이다. 이 체크섬의 기본 규칙은 첫 번째 9개의 숫자가 각자의 자리수로 곱해진다는 것이다. (다시 말해서 첫 번째 숫자에는 1을 곱하고 두 번째 숫자에는 2를 곱한다.) 이렇게 값들이 추가되고 11로 나눈 후에 나머지 값이 나온다. 나머지가 10이면 마지막 숫자는 X이다.


Listing 8. ISBN 체크를 위한 XPath 확장 함수
import java.util.List;import javax.xml.xpath.*;import org.w3c.dom.*;public class ISBNValidator implements XPathFunction {  // This class could easily be implemented as a Singleton.      public Object evaluate(List args) throws XPathFunctionException {    if (args.size() != 1) {      throw new XPathFunctionException("Wrong number of arguments to valid-isbn()");    }    String isbn;    Object o = args.get(0);    // perform conversions    if (o instanceof String) isbn = (String) args.get(0);    else if (o instanceof Boolean) isbn = o.toString();    else if (o instanceof Double) isbn = o.toString();    else if (o instanceof NodeList) {        NodeList list = (NodeList) o;        Node node = list.item(0);        // getTextContent is available in Java 5 and DOM 3.        // In Java 1.4 and DOM 2, you'd need to recursively         // accumulate the content.        isbn= node.getTextContent();    }    else {        throw new XPathFunctionException("Could not convert argument type");    }    char[] data = isbn.toCharArray();    if (data.length != 10) return Boolean.FALSE;    int checksum = 0;    for (int i = 0; i < 9; i++) {        checksum += (i+1) * (data[i]-'0');    }    int checkdigit = checksum % 11;    if (checkdigit + '0' == data[9] || (data[9] == 'X' && checkdigit == 10)) {        return Boolean.TRUE;    }    return Boolean.FALSE;  }}

다음 단계는 자바 프로그램에 사용할 수 있는 확장 함수를 만드는 것이다. 이를 위해 식을 컴파일 하기 전에 XPath 객체에javax.xml.xpath.XPathFunctionResolver를 설치한다. Resolver 함수는 XPath 이름과 이 함수에 대한 네임스페이스 URI를 이 함수를 구현하는 자바 클래스로 매핑한다.Listing 9는 http://www.example.org/books 라는 네임스페이스를 가진 확장 함수valid-isbnListing 8로 매핑하는 모습이다. 예를 들어, XPath 식//book[not(pre:valid-isbn(isbn))]이 ISBM 체크섬이 맞지 않는 모든 책을 찾는다.


Listing 9. 유효 isbn 확장 함수를 인식하는 함수 콘텍스트
iimport javax.xml.namespace.QName;import javax.xml.xpath.*;public class ISBNFunctionContext implements XPathFunctionResolver {  private static final QName name    = new QName("http://www.example.org/books", "valid-isbn");  public XPathFunction resolveFunction(QName name, int arity) {      if (name.equals(ISBNFunctionContext.name) && arity == 1) {          return new ISBNValidator();      }      return null;  }}

확장 함수들은 네임스페이스에 있어야 하므로 확장 함수를 포함하고 있는 식을 계산할 때NamespaceResolver를 사용해야 한다. 쿼리되는 문서가 네임스페이스를 전혀 사용하지 않더라도 말이다.XPathFunctionResolver,XPathFunction,NamespaceResolver는 인터페이스이기 때문에 이들을 같은 클래스에 둘 수 있다.


사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
위로


맺음말

SQL과 XPath 같은 선언적 언어에서 쿼리를 작성하는 것이 자바와 C 같이 명령형 언어를 사용하는 것 보다 더 쉽다. 자바와 C 같은 완벽한 언어로 복잡한 로직을 작성하는 것이 SQL과 XPath 같은 선언적 언어로 하는 것 보다 훨씬 쉽다. 다행히도 XPathFunctionResolver, XPathFunction, NamespaceResolver 같은 API를 사용하여 두 개를 혼합하는 것도 가능하다. 더 많은 데이터들이 XML로 옮겨지면서javax.xml.xpathjava.sql만큼 중요해 질 것이다.

기사의 원문보기



참고자료

교육

제품 및 기술 얻기

토론
신고
Posted by The.민군

최근, PC의 하드웨어적인 성능이 향상되어감에 따라 데스크탑의 성능을 높여주는 위젯툴이 인기를 끌고 있습니다. 이미 일전에 Konfabulator를 소개한 바 있듯이<맥OS 데스크탑 꾸미기의 전설이 윈도우즈로 Konfabulator v1.8> 대부분의 위젯은 화려한 그래픽 위주로 구현되어 있으며, 그만큼 다소간의 리소스를 점유하게 됩니다. 사용자의 구성에 따라 다르지만 대부분은 3~4개의 프로세스에, 20MB 정도는 훌쩍 넘으리라 여겨집니다. 그러나 오늘 소개하려는 Samurize는 이에 비하면 상당히 가볍습니다. 'Client'라는 1개의 프로세스에 5MB 정도의 메모리만 떠 있게 됩니다. 개다가 사용자 임의로 모니터링 대상을 수정/편집이 가능하다는 장점도 있습니다.

리뷰 기간 도중 Samurize의 공식 명칭이 'The 1337 Powerpuff Girls Meter Tool'로 변경되었음을 알립니다. 다만, 이전까지의 사용자들과 컨피그 파일의 다운로드 시 혼란을 우려하여 이전 명칭인 Samurize로 리뷰를 진행하겠으니 양해바랍니다.

사용자 삽입 이미지

▲ Samurize의 리소스 점유

 

프로그램 명

Samurize

최신버전

1.63

라이센스

프리웨어

제작사 및 다운로드

http://www.samurize.com

▲ 리뷰 프로그램 정보

 

 

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지
사용자 삽입 이미지

1. 설치와 기본 사용법

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지

◆ 설치

Samurize의 인스톨 역시 여타 프로그램과 마찬가지로 별 어려움은 없습니다. 기본 경로인 'C:\Program Files\Samurize'에 설치하게 됩니다. 물론 다른 폴더에 설치를 해도 작동하는 데에는 문제가 없지만, 차후 사용자들이 만든 콘피그 파일 셋을 이용할 경우 문제가 발생할 수도 있으니, 컴퓨터에 익숙하지 않은 사용자라면 기본 경로에 설치하는 것이 좋습니다. 설치가 끝나면 아래 그림과 같은 폴더와 파일들이 생성됩니다. 필자의 경우 여러 컨피그 파일과 플러그인, 스크립트 등을 추가하여 기본으로 생성되는 폴더 외에 몇 가지가 더 만들어졌음을 미리 밝힙니다.

<참고> Samurize 컨피그 파일 : 일전에 리뷰 한 Konfabulator의 Widget 파일과 비슷한 역할로서, 일종의 설정 파일을 의미합니다. 이 컨피그 파일을 이용하여 날씨, 뉴스, 하드 디스크 사용 현황, CPU/RAM 모니터링... 등등의 위젯을 구현하게 됩니다.

사용자 삽입 이미지

▲ Samurize 설치 후 생성되는 폴더와 파일

 

자주 사용되는 주요 폴더에 대한 간단한 설명과 역할에 대해 아래 표를 통하여 명시하였습니다. 일부 폴더의 경우 컨피그 파일을 만든 사용자에 따라 역할이 달라질 수 있습니다만 Samurize를 제대로 이용하기 위해서는 대략적으로라도 숙지하는 것이 좋습니다.

Configs

확장자가 'ini'로 끝나는 컨피그 파일이 저장되는 곳입니다. 이 파일의 명칭으로 각각의 컨피그 파일이 구분되어, 트레이에 있는 Samurize 아이콘을 오른쪽 클릭하여 컨피그 파일을 선택하게 됩니다.

Fonts

글꼴이 저장되는 폴더입니다. 대부분 경우 윈도우즈 시스템 폰트를 이용하지만 일부 컨피그는 특수한 폰트를 사용하게 되며, 이 경우 이 폴더에 저장되게 됩니다.

Icons

윈도우즈 작업표시줄의 트레이에 표시되는 Samurize의 아이콘이 저장됩니다.

lang

언어파일이 저장되는 곳입니다. 아쉽지만 아직 Samurize의 공식 한글 팩은 제공되지 않고 있습니다.

Plugins

사용자들 또는 주요 사이트에서 제작/제공한 각종 플러그인 파일들이 저장됩니다. 대표적인 것으로 실시간으로 날씨를 보고해 주는 날씨 플러그인(Weather.2004.dll)이 있습니다.

Scripts

사용자들 또는 주요 사이트에서 제작/제공한 각종 자바 스크립트(js), 비주얼베이직 스크립트(VBS) 파일들이 저장됩니다.

▲ 주요 폴더의 역할

 

 

◆ 트레이 아이콘 메뉴

Samurize를 실행시키면 우측 하단의 트레이에 아이콘이 나타나게 됩니다. 이를 마우스 오른쪽 버튼으로 클릭하면, Samurize의 거의 모든 기능을 제어할 수 있는 메뉴 팝업이 뜹니다. 각 항목에 대하여 알아보겠습니다.


Select Config File

컨피그 파일을 지정하는 곳입니다. 다운받아 둔 각종 ini 컨피그 파일들을 클릭하면 선택되어 화면에 나타납니다. 아래 그림의 (1) 부분은, 기본 경로인 'C:\Program Files\Samurize\Configs' 폴더에 저장되어 있는 ini 파일들이 나열되게 됩니다. 그림의 (2)번 부분은 'C:\Program Files\Samurize\Configs' 경로에 만들어진 폴더 명을 그대로 불러온 것입니다. 따라서 (3)번 영역에 있는 ini 파일들은 'C:\Program Files\Samurize\Configs\LHI' 경로로 저장된 컨피그들을 의미합니다.

사용자 삽입 이미지


Reload Config

아래의 'Edit Config'에서 수정한 컨피그 파일, 또는 잠시 'Pause'한 컨피그 파일을 다시 시작할 경우 사용됩니다.


Edit Config

컨피그 파일을 수정하는 Config Editor를 실행시킵니다. Config Editor는 컨피그 파일을 새로 만들거나, 다운로드 받은 것을 자신의 시스템에 적합하게 수정할 경우 사용하는 것으로 아래에서 다시 상세히 다루기로 합니다.

사용자 삽입 이미지


Position

컨피그의 위치와 화면표시 방법을 지정합니다. 언제나 보여지게 하거나, 화면 모서리에 붙여놓기 등의 다양한 설정이 가능합니다.

사용자 삽입 이미지


Pause

게임이나 그래픽 작업 등 시스템 부하가 많이 걸리는 작업 시, 좀 더 퍼포먼스를 늘리기 위하여 현재 로드 되어 실행되고 있는 컨피그를 잠시 중지합니다.


Instance Manager

최근 Samurize가 업데이트 되면서 추가된 인스턴스 기능을 제어하는 곳입니다. 인스턴스의 의미를 쉽게 이해하기 위해 굳이 예를 하나 들자면, 일종의 프로세스라고 보면 됩니다. 하나의 컨피그는 하나의 인스턴스를 이루게 됩니다. 따라서 예전에는 동시에 여러 개의 컨피그를 로딩하는 것이 불가했습니다. 그러나 최신버전의 Samurize에는 복수 인스턴스를 지원하게 되었으므로, 동시에 여러 개의 컨피그 파일을 불러와서 화면에 로딩시킬 수 있습니다. 이에 대한 활용법도 아래에서 자세히 다뤄보기로 합니다.

사용자 삽입 이미지


Run on Startup

윈도우즈 시작 시 자동으로 시작되게 합니다. 클릭하면 윈도우즈 레지스트리에 등록이 됩니다.


Config Info

현재 로딩되어 있는 인스턴스와 컨피그 파일을 나타내 줍니다.

사용자 삽입 이미지


Language

언어파일을 지정합니다. 현재 최신버전인 1.63의 경우에는 아직 한글 파일이 제공되지 않고 있습니다.


 

 

 

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지
사용자 삽입 이미지

2. 컨피그 파일을 불러와서 사용하기

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지

이제 본격적으로 Samurize를 사용해 보겠습니다. 컨피그를 적용하는 방법은 두 가지가 있습니다. 하나는 확장자가 '.sam'으로 끝나는 Samurize라이즈 패키지 파일(Package File)을 임포팅 해 오는 것이고, 다른 하나는 'C:\Program Files\Samurize'를 통째로 덮어 씌우는 것입니다. 통째로 덮어 씌우는 것은 비교적 간단한 작업이며, 마이테마 사이트의 Samurize 게시판(http://mytheme.net/zboard/zboard.php?id=the_samurize)에서 에서 손쉽게 다운로드 받을 수 있으므로 여기서는 생략하고, 패키지 파일을 임포팅 해 오는 방법을 위주로 설명합니다.

아래 스크린샷에 보이는 것은, 1280x1024 해상도에 최적으로 디자인 된 'Axiom Vertical'이라는 컨피그입니다. 이를 이용하여 패키지 파일을 임포팅 하여 꾸미는 법을 먼저 알아 보겠습니다.

사용자 삽입 이미지

▲ Axiom Vertical 컨피그 파일 (클릭하면 확대됩니다.)

 

 

◆ 컨피그 패키지 파일 다운로드 받기

컨피그 파일을 다운로드 받을 수 있는 곳은 많습니다. 국내에서는 마이테마가 잘 알려져 있습니다.. 하지만 이곳에 올라오는 것들은 대부분 덮어씌우는 형태의 컨피그 파일들로서  패키지 파일은 많지 않습니다. 손쉽게 패키지 파일을 다운로드 받을 수 있는 곳은 공식 사이트의 'Downloads' 섹션이 있습니다. (http://www.samurize.com/modules/mydownloads/) 여기서는 위에 예시한 Axiom Vertical을 다운로드 받아 보겠습니다.http://www.samurize.com/modules/mydownloads/singlefile.php?cid=18&lid=1449에서 확장자가 sam으로 끝나는 'Axiom.sam' 컨피그 파일을 받으면 Samurize의 Import/Export 툴을 이용하여 임포팅 해야 합니다. Import/Export 툴은 Samurize가 설치된 디렉토리 안에 'ImportExportTool.exe'이란 이름으로 있습니다. 기본값으로 설치했다면 'C:\Program Files\Samurize' 안에 있을 것입니다.

사용자 삽입 이미지

▲ Samurize의 Import/Export 툴

 

아래 그림을 참고하여 (1)의 아이콘을 클릭하여, 다운로드 받은 패키지 파일을 불러옵니다.(2) 마지막으로 3번의 'Import'를 누르면 불러오기 작업이 시작됩니다.

사용자 삽입 이미지

▲ Import/Export 툴을 이용하여 패키지 파일 불러오기

 

일부 간단한 설정이 필요한 것은 임포팅 도중 지정이 가능합니다. 그림에서는 네트워크 모니터링을 위해 필요한 랜카드를 지정해 주는 모습입니다. 자신의 시스템에 설치된 물리적인 랜카드를 선택해 주면 됩니다. 대부분 Realtek, Intel, 또는 nVidia나 3com사의 제품 중 하나가 설치되어 있을 겁니다.

사용자 삽입 이미지

▲ Importing 중 랜카드 지정

 

불러오기가 끝나면 이제 트레이에 있는 Samurize의 아이콘을 마우스 오른쪽으로 클릭하여 컨피그 파일을 선택해 주면 Axiom Vertical 컨피그가 구현됩니다.

사용자 삽입 이미지

▲ Axiom Vertical 컨피그 지정

 

그러나 일부 컨피그의 Meter는, 만든 사람이 자신의 시스템에만 해당되는 경로로 지정해 준 경우가 많습니다. 이 때에는 해당 Meter의 카운터 값을 수정해 줘야 하는데 이 경우 유용한 것이, 이미 앞에서 언급한 Config Editor입니다. Meter란 시스템을 모니터링 하기 위한 것으로, HDD의 남은 공간, 현재 CPU 사용률, 메모리 사용률, 실행중인 프로세스, 날짜, 컴퓨팅 시간... 등등을 표현해 줍니다. 방금 언급한 비교적 간단한 항목들은 Samurize라이와 윈도우즈가 제공해 주는 자체의 카운터를 이용하지만, 실시간 날씨 제공이나 뉴스 등은 스크립트 파일이나 플러그인을 이용하여 구현하게 됩니다.

기본적인 카운터를 수정하는 법에 대해 알아보겠습니다. 아래 그림에서 보면 그래프를 통하여 CPU 사용률을 표시해 주면서, 현재 사용률은 72%라고 숫자로 명시됩니다.그 옆에 카운터 정보가 있는데 '\Processor(0)\%Processor Time' 으로 경로가 나와있습니다.

사용자 삽입 이미지

▲ CPU 사용률 Meter 카운터

 

'Change Counter'를 누르면 아래와 같은 'Select Performance Counter' 창이 나타납니다. 가장 먼저 '다음 컴퓨터에서 카운터 선택'을 확인하여 자신의 PC가 지정이 되어 있는지 확인합니다. 자신의 PC 이름은 네트워크 설정에서 사용자가 정한 것으로 표기됩니다. 여기서는 '\\SBC-HQ'로 나타났습니다. '성능개체'를 보면 'Processor'가 선택되어 있습니다. 즉, CPU의 활동을 모니터링 하겠다는 의미입니다. 그 아래 '다음 목록에서 '카운터 선택'란이 있습니다. CPU의 활동 중 어떤 것을 모니터링 하겠는가를 지정하는 곳입니다. 가령, 맨 위의 'C3 Time'를 지정한다면 현재 내 CPU의 전력 최소화 상태를 모니터링 하게 됩니다. 여기서는 'Processor Time'을 지정하여 CPU의 사용률을 모니터링 합니다.

사용자 삽입 이미지

▲ Select Performance Counter

 

'성능개체'에서 제공하는 카운터는 Processor를 비롯하여 IP, Memory, Network Interface, Paging File, TCP 등 상당히 많습니다. 로컬 PC의 하드웨어적인 모니터링은 거의 모두 가능하게 해 줍니다.

사용자 삽입 이미지

▲ Performance Counter의 '성능개체'

 

만약에 실시간으로 인터넷 속도를 모니터링 하고 싶다면, 'Network Interface' → 'Bytes Received/sec' → 'Realtek RTL8139...'를 지정해 주면 됩니다. 'Bytes Received/sec'란 의미에서 눈치챌 수 있듯이 아래 그림은 다운로드 속도를 모니터링 하는 설정입니다. 만약 'Bytes sent/sec'를 지정하면 업로드 속도의 모니터링 설정이 될 것입니다.

사용자 삽입 이미지

▲ 실시간으로 인터넷 다운로드 속도를 모니터링 하기

 

아래 그림은 하드디스크 중 C 드라이브를 모니터링 하는 방법입니다. 'Source' 탭의 'Drive Space Properties'에 보면 현재 자신의 PC에 물리적으로 설치되었거나, 논리적으로 파티션을 나눈 드라이브가 모두 나열됩니다. 이 중 원하는 것을 지정한 후 그 아래의 펼침 목록을 확인해 보면 'Free Space', 'Used Space', 'Total Space' 세가지 메뉴가 나타납니다. 현재 남은 용량을 모니터링 하고 싶으면 'Free Space'를, 사용한 용량을 모니터링하고 싶으면 'Used Space'를 선택하면 됩니다.

사용자 삽입 이미지

▲ C 드라이브 모니터링

 

한편, Samurize나 윈도우즈에서 기본으로 제공하지 않는 Meter나 카운터는 플러그인이나 스크립트를 이용하여 구현하게 됩니다. 이 플러그인들은 Samurize의 공식 사이트에서 다운로드 받을 수 있으며, 일부는 프로그래밍에 어느 정도 실력 있는 고수 네티즌 분들이 제공하고 있습니다. 아래는 현재 실행 중에 있는 프로세스의 목록을 보여주는 'FindTheTopProcess.dll'이라는 플러그인입니다. 'Ctrl + Alt + Del' 키 조합을 이용하여 'Windows 작업 관리자'를 띄워야만 볼 수 있었던 것인데 간단히 Samurize 위젯에서 구현되게 만들어졌습니다.

사용자 삽입 이미지

▲ 실행 중에 있는 프로세스의 목록을 보여주는 'FindTheTopProcess.dll' 플러그인

 

 

◆ 스크립트와 플러그인 구하기

Samurize의 제작이나 수정에 필요한 스크립트와 플러그인은 제작사의 공식 사이트 'Scripts & Plugins'란에서 다운로드 받을 수 있습니다. (http://www.samurize.com/modules/mydownloads/viewcat.php?cid=2) 아래는 가장 인기 있는 스크립트와 플러그인들입니다. 'Weather 2004'는 실시간으로 날씨를 출력해 주는 것이고, 'External IP'는 자신의 인터넷 IP를 출력해 주는 것입니다. 재미있는 것은 'KeyMouseCount Plugin'으로 자신의 마우스가 움직인 거리와 클릭한 횟수 등을 표시해 줍니다. 여기서 예시한 'Axiom Vertical'에도 포함되어 있습니다.

사용자 삽입 이미지

▲ Samurize 사이트에서 다운로드 가능한 스크립트와 플러그인

 

 

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지
사용자 삽입 이미지

3. 컨피그 수정/추가/편집하기

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지

이번에는 간단한 컨피그를 이용하여 자신이 원하는 대로 수정하는 작업을 예시하여 보겠습니다. 예제로 사용할 컨피그 파일은 Samurize의 공식 사이트에서 받은 Trivial로서, 텍스트 위주로 구성되어 있어서 시스템 리소스도 그리 많이 잡아먹지 않는 컨피그입니다. 리뷰를 위해 필자가 사용하기 쉽게 수정한 후 "Trivial_Summer"라고 명명하였습니다. [여기]를 클릭하여 "Trivial_Summer.zip" 파일을 다운로드 받은 후 Samurize가 설치된 폴더(C:\Program Files\Samurize)에 덮어 씌웁니다.

이 후 Samurize를 실행시킨 후 트레이 아이콘을 마우스 오른쪽으로 클릭하여 'Edit Config Files...'를 선택하여 컨피그 에디터를 띄워서 Trivial_Summer.ini 파일을 열어 들입니다. 그러면 아래 그림과 같은 컨피그 화면이 나타나게 됩니다. 필자가 예시하는 이 컨피그는 1280x1024 해상도로 구현된 것이므로 그 이하의 해상도를 사용중인 유저라면 'Edit → Select All'을 차례로 클릭한 후 키보드의 화살표 방향키를 이용하여 상하좌우로 위치를 바꿔주면 됩니다.

사용자 삽입 이미지

▲ Trivial_Summer 컨피그 불러오기

 

 

배경 투명도 조절하기

이 컨피그는 배경이 이미지를 PNG로 제작하였습니다. 따라서 배경의 농도를 조정하여 투명도를 임의로 정할 수 있습니다.


사용자 삽입 이미지


(1)

컨피그 화면의 테두리를 마우스로 클릭하여 배경 이미지를 선택합니다.


(2)

배경 이미지의 경로는 여기에 표시됩니다.


(3)

배경 이미지의 투명도를 조절하는 부분입니다. 수치를 높일수록 투명도는 낮아지며, 수치가 적을수록 투명해집니다. 여기서는 100으로 정했습니다. 독자들이 직접 수정하여 보길 바랍니다.


 

 

◆ 하드 디스크 드라이브의 실시간 용량 표시하기

현재 자신의 PC에 장착된 모든 드라이브의 실시간 용량을 표시할 수 있습니다. 물리적인 것뿐 아니라 논리적인 드라이브도 실시간으로 용량의 변화를 모니터링이 가능하므로 파티션을 나눈 사용자도 쉽게 쓸 수 있습니다.


사용자 삽입 이미지


(1)

'Free' 뒤의 숫자를 한번 클릭하고, 'Source' 탭을 보면 'Drive Space Properties'나 나타납니다.


(2)

자신의 PC에 장착된 모든 드라이브의 목록이 나열됩니다. 여기서는 C 드라이브를 모니터링 하는 것이므로 'C:\'를 선택했습니다.


(3)

남은 용량을 표시하려면 'Free Space', 현재까지 사용한 공간을 표시하려면 'Used Space', 드라이브의 총 용량을 표시하려면 'Total Space'를 지정하면 됩니다.


 

 

◆ 플러그인 사용하기

이번에는 플러그인을 사용하는 법에 대해 알아봅니다. 'Crtl + Alt +Del' 키를 조합하여 불러올 수 있는 '윈도우즈 작업 관리자' 상의 실행중인 프로세스 목록을 이 컨피그에 표시하는 것입니다. 먼저 회색 바탕을 한번 클릭한 후 'Add Meter → Add Plugin'을 차례로 선택합니다.

사용자 삽입 이미지

▲ Add Plugin

 

아래 그림의 (1)에서처럼 플러그인 삽입난이 나타납니다. (2)의 'Select Plugin'에서는 자신의 Samurize 폴더(C:\Program Files\Samurize\Plugins)에 저장되어 있는 플러그인들이 나열됩니다. '윈도우즈 작업 관리자' 상의 실행중인 프로세스 목록을 나열하는 것을 추가하기로 했으므로 'FindTheTopProcess.dll'을 지정한 후 (3)의 'SortByName'을 선택합니다. 이는 현재 실행 중인 프로세스를 이름순으로 나열한다는 의미입니다. 그 아래 'SortByMemory'를 선택하면, 메모리 리소스를 많이 점유하고 있는 순으로 나열되는 것임을 의미합니다.

사용자 삽입 이미지

▲ 플러그인 선택하기

 

(4)의 'Global Settings...'를 누르면 아래와 같은 몇 가지 옵션설정 창이 뜹니다. 'Refresh Rate'는 갱신주기를 정하는 것으로 적은 수치일수록 업데이트가 빨라집니다. 특정 프로세스는 모니터링 하지 않거나, 아이들 시의 프로세스는 모니터링 하지 않는 기능, 그리고 메모리 표시단위를 지정하는 기능도 있습니다.

사용자 삽입 이미지

▲ Global Settings...

 

이젠 Meter의 위치와 크기를 지정해 보는 것에 대해 알아봅니다. (1) 마우스 드래그를 이용하여 주어진 배경 그림에 맞게 크기를 조절해 줍니다. (2)의 네모로 강조된 곳은 해당 Meter의 위치 좌표와 크기를 나타내 주는 곳입니다. 'X'는 가로 픽셀, 'Y'는 세로 픽셀로 나타낸 위치 좌표이며, 'Width'는 넓이를, 'Height'는 높이를 말합니다.

사용자 삽입 이미지

▲ Meter 위치와 크기 조절

 

이제 (1) 디스플레이 탭으로 이동합니다. (2)에서처럼 Plugin이란 글자를 지워버립니다. 참고로 (3)에서는 글꼴이나 글자 색상을 지정하는 부분입니다. 만약 메타 값에 글자가 들어간다면 여기서 수정해 주면 됩니다.

사용자 삽입 이미지

▲ 디스플레이 탭

 

마지막으로 저장하고, 트레이 아이콘의 'Reload Config'를 클릭하면 수정된 최종 화면이 보여집니다.

사용자 삽입 이미지

▲ 최종 수정된 Trivial_Summer 컨피그

 

 

글/여호종(skysummer.com@gmail.com)


신고
Posted by The.민군
1. MnCast, 다음 동영상 캡쳐하기
2. Youtube를 비롯한 해외 동영상 사이트 캡쳐하기
 

1) 수동으로 캡쳐하기

2) 전용 사이트나 프로그램으로 다운로드 받기
3. 다운로드 받은 동영상 감상하기
  1) KMP
2) FLV Player 1.3.3
3) Riva FLV Player 1.2

 

최근 동영상 UCC(UGC)가 열풍처럼 일면서, 국내외를 막론하고 사용자들간의 동영상을 공유하게 해 주는 사이트들이 많이 증가하고 있습니다. 해외에서는 YouTube(http://www.youtube.com), Google Video(http://video.google.com), Veoh(http://www.veoh.com), MetaCafe(http://www.metacafe.com) 등이, 국내에서는 다음, mncast, 판도라 등이 동영상 공유 서비스를 제공 중입니다. 그런데 이 사이트들의 상당 수는 동영상을 원본 그대로 제공하는 것이 아니라 FLV 형식으로 변경하고 있습니다. 굳이 이렇게 변경하는 이유는, 동영상 파일의 경우 용량이 크기 때문에 다운로드 하여 감상하기까지의 시간이 오래 걸리기 때문입니다. FLV로 전송 시에는 파일 용량도 적어지고, 다운로드 하면서 감상하는 스트리밍 전송까지 가능하다는 장점 외에 플래쉬 플레이어만 설치한다면, 별다른 코덱이 필요치 않기 때문입니다.

사용자 삽입 이미지FLV란?

FLV는 FLash Video에서 따온 명칭으로, 넓은 의미에서는 AVI나 MPG, WMV와 같은 동영상 포맷입니다. 따라서 인코딩을 위해서는 코덱이 필요한데, 현재 가장 많이 쓰는 것은 H.263이며, 최근 들어 On2 Technologies사의 VP6 코덱을 이용하는 경우도 많습니다. Flash 8부터 공식적으로 채택된 VP6 코덱은 상대적으로 크기가 작고 화질이 좋다고 알려져 있습니다.

FLV는 세가지 방식으로 전송이 가능합니다. 첫째는 SWF에 임베디드(embedded) 된 전송, 둘째는 Flash Media Server나 Red5 Server를 통한 RTMP(Real Time Messaging Protocol) 스트리밍 전송, 셋째는 HTTP를 기반으로 한 프로그레시브 다운로드(Progressive Download)입니다. YouTube나 다음 등의 동영상 공유사이트에서는 위 세가지 방식 중 하나를 이용하거나 또는 복합적으로 이용하여 서비스하고 있습니다.

웹 상에서 FLV를 감상하기 위해서는 Adobe Flash Player 버전 6.0 이상이 설치되어 있어야 하며, 이 경우 다운로드를 하는(재생하는, 디코딩하는) 쪽에서는 별다른 코덱이 필요 없다는 장점이 있습니다.로컬 시스템에서 감상하려면 전용 플레이어가 있어야 하지만, 일부 동영상 플레이어도 재생을 지원합니다. 대표적인 것으로KMP가 있습니다.


일반인들에게는 다소 생소한 이 FLV 포맷 때문에 다운로드를 원하는 사용자들의 상당 수가 난해함을 호소하는 경우가 있습니다. 이번 웹진에서는, 동영상 공유 사이트들로부터 이 FLV를 다운로드 받고, 자신의 PC에서 재생하는 것까지 알아보도록 하겠습니다.
 

사용자 삽입 이미지필요한 소프트웨어
URL Helper 2.6[추천] - 다운로드:StreamingStar Technology Inc(쉐어웨어)
Smart Sniff 1.3- 다운로드:Nir Soft(프리웨어)
Net Transport 2.02[옵션] - 다운로드 :Xi Software(쉐어웨어)

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지
사용자 삽입 이미지

1.MnCast, 다음 동영상 캡쳐하기

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지

일전에 포스팅한 바 있는 "인터넷의 동영상을 내 컴퓨터로, 스트리밍 동영상 저장 팁" 에서 소개한 URL Helper (쉐어웨어) 또는 Smart Sniff (프리웨어)를 설치합니다. 여기서는 URL Helper를 이용하겠으며 자세한 설치법에 대한 것은 링크한 글을 참고바랍니다. 단, 주의할 것으로 WinPcap이란 패킷 캡쳐 툴(라이브러리)제대로 설치하여야 한다는 점을 다시 한번 강조합니다.

사용자 삽입 이미지

▲ WinPcap 설치

동영상을 감상하기 전에 프로그램부터 먼저 실행한 후, 아래 그림처럼 "Select Adapter"를 눌러서 물리적인 랜카드를 선택 해 줍니다.

사용자 삽입 이미지


랜카드가 제대로 잡혀지면, 아래 그림처럼 현재 시스템의 랜카드 정보가 나타납니다. 필자의 경우 리얼텍사의 8139 기종임을 알 수 있습니다.

사용자 삽입 이미지


이제 "Sniff Network"를 누른 후 해당 동영상 공유 사이트로 이동하여 다운 받을 동영상을 감상하는 것 만으로 간단하게 FLV 주소를 캡쳐할 수 있습니다. 아래 그림은 mncast에서 동영상을 감상하면서 FLV 주소가 캡쳐되는 장면입니다. 다음 동영상도 마찬가지로 URL Helper만 실행하여 주고 감상하면 자동으로 FLV 주소가 캡쳐됩니다.

사용자 삽입 이미지


다운로드 받기 위하여 FLV 주소에 대고 마우스 오른쪽 버튼을 클릭하여 클립보드에 복사해 둡니다.

사용자 삽입 이미지


마지막으로 인터넷 익스플로러나 파이어폭스 등... 사용하는 브라우저 주소표시줄에 붙여넣기 하여 엔터키만 힘차게 쳐 주면 간단하게 다운로드가 시작됩니다.

사용자 삽입 이미지


예외적인 시스템 설정 때문에 다운로드가 되지 않는다면 다운로드 매니저를 이용하면 됩니다. 역시 일전에 소개한 적이 있는 Net Transport를 추천합니다.

사용자 삽입 이미지


사용자 삽입 이미지목차로 돌아가기

 

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지
사용자 삽입 이미지

2.Youtube를 비롯한 해외 동영상 사이트 캡쳐하기

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지

1) 수동으로 캡쳐하기

Youtube의 경우 동영상에 따라 FLV 없이 SWF 주소만 캡쳐되는 경우가 있습니다. 이 때에는 video_id와 table(또는 track)을 이용하여 수동으로 캡쳐해야 합니다. 아래 동영상 샘플은 필자가 이번 강좌를 위해 올려 둔 것으로 YouTube 주소는 "http://www.youtube.com/watch?v=b-tz6Zcz5UE"입니다.

사용자 삽입 이미지

URL Helper에서 그림처럼 확장자가 SWF인 행을 봅니다. 이 주소를 마우스 오른쪽 버튼을 이용하여 복사한 후, 메모장 등의 에디터에 붙여 넣습니다.

사용자 삽입 이미지


여기서 "video_id=" 뒤의 11자리 문자들이 ID이며, "...&t=" 와 "&nc..." 사이의 문자들이 이 동영상의 table(track)입니다. 참고로 11자리의 ID는 Youtube 주소상 맨 뒤 문자와 언제나 일치합니다. 따라서 Youtube 동영상 주소가 "http://www.youtube.com/watch?v=b-tz6Zcz5UE"이므로 ID는b-tz6Zcz5UE가 될 것이며, table(track)은OEgsToPDskIlbvkKEfIEmYRk_IheEE-Y입니다.

사용자 삽입 이미지


이렇게 얻은 video_id과 table을 "http://www.youtube.com/get_video?video_id=비디오아이디&t=테이블" 주소에 아래처럼 대입한 후 Net Transport로 다운로드 받습니다. 표에서는 구분하기 쉽도록 편의상 두 줄로 보여지지만 각 문자들 사이에 빈칸은 없어야 합니다.

http://www.youtube.com/get_video?video_id=b-tz6Zcz5UE
&t=OEgsToPDskIlbvkKEfIEmYRk_IheEE-Y

다운로드가 끝나면 get_video라는 파일이 생성되는데, 이것의 확장자를 get_video.flv로 바꿔주면 FLV 포맷의 동영상입니다. 물론, 굳이 get_video라는 이름을 이용하지 않아도 됩니다. 확장자만 FLV라면 video1.flv, video2.flv... 등과 같이 파일 이름을 변경해도 동영상을 감상하거나 저장하는 데에는 지장이 없습니다.


사용자 삽입 이미지목차로 돌아가기

 

2) 전용 사이트나 프로그램으로 다운로드 받기

전용 사이트를 통하여 동영상을 저장할 수 있습니다. 기본적으로 다운로드 받는 방식은 위에 설명한 것과 동일하지만 일반 사용자들이 이용하기 쉽도록 웹 사이트나 프로그램을 기반으로 변경해 준 것입니다. 따라서 다운로드 되는 파일 역시 get_video이며, FLV 확장자를 추가하여 get_video.flv로 바꿔주어야 합니다.

여기서 소개할 곳은http://keepvid.com라는 곳입니다. YouTube뿐 아니라 Google Video, Break.com, iFilm, Putfile, MetaCafe 등... 현존하는 거의 모든 외국 사이트를 지원하고 있습니다.

사용자 삽입 이미지

http://keepvid.com

 

사용법도 간단하여, 원하는 동영상의 주소만을 기입한 후 우측에서 서비스 사이트를 선택하고 "Download" 버튼을 눌러줍니다. 여기서는 앞에서 예시에 사용한 "http://www.youtube.com/watch?v=b-tz6Zcz5UE"를 이용했습니다.

사용자 삽입 이미지

▲ keepvid.com에서 동영상 다운로드 하기 (클릭하면 확대됩니다.)


"Loading..."이라는 메시지와 함께 잠시 기다리면 아래와 같은 >>Download Link<< 가 제공됩니다. 이것을 클릭하거나 오른쪽 팝업 메뉴를 이용하여 "다른 이름으로 저장"을 선택하면 get_video라는 파일이 다운로드 됩니다.

사용자 삽입 이미지


만약 파이어폭스를 이용 중인 유저라면 확장 기능으로도 다운로드가 가능합니다. 현재Video Download 1.0.1DownloadHelper 1.91두 가지가 제공되고 있습니다. 사용자들의 수는 전자가 더 많습니다.
필자는 Video Download 1.0.1을 이용하였으며, 설치가 끝나면 하단 작업 트레이 우측으로 아이콘이 하나 만들어집니다.

사용자 삽입 이미지


사용법은 여태껏 알아본 방법 중 가장 간단합니다. 원하는 동영상을 감상하면서 아이콘을 한 번 클릭해 주면 즉시 다운로드 창이 뜹니다. "Download Link"를 클릭하거나 오른쪽 팝업 메뉴를 이용하여 "다른 이름으로 저장"을 선택하면 역시 get_video 파일이 생성됩니다.

사용자 삽입 이미지


사용자 삽입 이미지목차로 돌아가기

 

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지
사용자 삽입 이미지

3.다운로드 받은 동영상 감상하기

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지

이렇게 다운로드 받은 FLV 동영상은 로컬에서 재생하기가 힘듭니다. 기본적으로 이를 지원하는 미디어 재생기가 드물기 때문이죠. 따라서 FLV 전용 재생기가 필요한데, 현재 사용자가 가장 많고, 스파이웨어나 애드웨어도 없는 무료 프로그램 몇 가지를 추천합니다.

1) KMP

제작사

KMPlayer

최신 버전

v2.9.2.1100

라이센스

프리웨어

인스톨

필요 또는 불필요 선택 가능

프로그램 다운로드

KMP 20060903 바이너리 Beta 버전

너무나도 유명한 국산 동영상 플레이어입니다. 비디오 파일, 음악 파일, 그림 파일을 막론하고 모두 재생해 주는 강력한 프로그램으로, FLV 역시 문제 없이 재생해 줍니다. 여러 파일을 재생목록에 추가하는 것과, 넘겨보기(Skip), 불룸 조절이 가능합니다. 물론, 화면 크기도 임으로 바꿀 수 있습니다. 하지만 재생목록에 추가 시 드래그는 불가하며, 목록 관리 창에서 파일을 추가 해 줘야 합니다.

KMP만 있으면 별 다른 프로그램을 설치 하지 않아도 된다는 장점이 있습니다. 게다가 인스톨이 필요 없는 Binary 버전도 제공하고 있다는 것 역시 무시 못하는 장점입니다.

사용자 삽입 이미지


2) FLV Player 1.3.3

제작사

martijndevisser.com

최신 버전

v1.3.3

라이센스

프리웨어

인스톨

필요

프로그램 다운로드

FLV Player v1.3.3

현재 해외에서 많이 사용 중인 FLV 전용 재생기입니다. 로컬에 있는 파일을 직접 재생하거나 URL을 이용하여 원격으로 재생도 가능합니다. 넘겨보기(Skip), 볼룸조절을 지원하며, 1배 또는 2배 화면 크기 조절이 가능하고 사용자 임의로 자유롭게 늘릴 수도 있습니다. 그러나 한 번에 한 파일만 재생이 가능하기 때문에 목록화 하는 것이 불가하다는 단점이 있습니다. 또한, 반드시 인스톨 해야 한다는 아쉬움도 있습니다.

사용자 삽입 이미지


3) Riva FLV Player 1.2

제작사

Riva

최신 버전

v1.2

라이센스

프리웨어

인스톨

필요

프로그램 다운로드 FLV Player v1.2

AVI, MPG등의 동영상 파일을 FLV로 변환하여 주는 Riva FLV Encoder라는 유명한 프로그램을 제공하고 있는 Riva의 제품입니다. 대부분의 FLV Encoder가 쉐어웨어인데 반해 프리웨어로 제공하고 있습니다. 물론, 그 성능은 다소 떨어지지만 많은 사용자들이 유용하게 쓰고 있습니다.

사용자 삽입 이미지


FLV Player 역시 프리웨어입니다. 넘겨보기와 볼룸 조절이 가능하고 반복 재생까지 지원합니다. 특히, 인스톨 시 아래와 같이 자동으로 FLV 파일을 프로그램에 연결시켜 주므로, 파일을 클릭하는 것 만으로 손쉽게 재생이 가능합니다. 하지만 인스톨이 필요하고, 크기 조절이 불가하다는 단점이 있습니다. 모든 동영상은 원본 크기로만 감상할 수 있으며, 목록을 지정하여 다수의 파일을 감상하는 것도 불가합니다.

사용자 삽입 이미지

사용자 삽입 이미지목차로 돌아가기

 

글/여호종
사용자 삽입 이미지
신고
Posted by The.민군
파일 동기화 테크닉

중요한 비즈니스 문서는 별도로 백업하고 저장해두는 것이 좋다. 디지털 파일은 자칫 실수로 DEL를 해버리면 다시는 복원할 수 없게 될 수 있기 때문이다. 그런 이유로 중요한 데이터는 별도의 드라이브에 이중으로 저장해두고 관리하는 것이 좋다. 필자의 경우만 해도 회사 PC, 집 PC 그리고 파일서버 이렇게 3곳에 파일을 나누어서 저장해두고 있다. 이때 파일을 보다 편리하게 나누어 저장하고 자동으로 파일을 동기화해주는 유틸리티를 이용하면 편리하다. Always Sync라는 유틸리티를 이용하면 쉽게 파일을 동기화할 수 있다. 단, 이 프로그램은 3.0.3 이전의 버전은 무료이지만 그 이후의 버전은 쉐어웨어이다. 기능상의 큰 차이가 없으므로 3.0.3 버전을 이용해서 무료로 파일 동기화 기능을 이용해보자.

다운로드:
http://pds.hanafos.com/NPViewPds.asp?fileSeq=171175

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지
사용자 삽입 이미지
1. Allway Sync를 이용해 두 폴더의 파일 비교하기
사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지

Allway Sync라는 유틸리티는 무료로 사용할 수 있는 것으로 2곳의 폴더에 저장된 파일을 비교해서 동기화해준다. 즉 한 곳의 폴더에 저장된 파일을 다른 곳에 동일하게 저장을 해주며 만일 한 곳에서 파일이 수정되면 자동으로 다른 곳의 파일도 수정되도록 해준다.

Allway Sync의 화면 구성은 간단하게 되어 있다. 좌측에서 원본이 저장된 폴더를 지정해주고 우측에는 동기화할 폴더를 지정해주면 그것으로 두 개의 폴더가 동기화 설정된다. 우선 좌측의 ‘Browse’를 클릭한다.

사용자 삽입 이미지

동기화하고자 하는 파일이 저장된 폴더를 선택해준다. ‘확인’을 클릭한다.

사용자 삽입 이미지

좌측에 선택된 폴더가 등록되었다. 이제 우측에서 폴더를 지정해줄 차례이다. 같은 방법을 이용해 ‘Browse’를 클릭한다.

사용자 삽입 이미지

앞서 선택된 폴더와 항상 같은 파일을 유지할 폴더를 선택해준다. 이때 네트워크 환경에 있는 다른 컴퓨터의 공유 폴더를 지정해주는 것도 가능하다. 만일 폴더를 새롭게 만들려면 ‘새 폴더 만들기’를 클릭한다.

사용자 삽입 이미지

새 폴더의 이름을 기입한 후에 ‘확인’을 클릭한다.

사용자 삽입 이미지

이렇게 해서 2개의 폴더를 동기화 설정했다. 이제 두 폴더에서 변경된 파일, 삭제된 파일, 생성된 파일은 항상 일치하게 된다.

사용자 삽입 이미지

왼쪽의 원본이 저장된 폴더의 ‘View’를 클릭한다. 해당 폴더에 저장된 파일 목록을 탐색기를 이용해서 확인할 수 있다.

사용자 삽입 이미지

같은 방법으로 우측의 폴더에서 ‘View’ 폴더를 클릭한다. 이 폴더는 새롭게 생성한 폴더라서 아무 파일도 저장되어 있지 않다. 이제 동기화 명령을 실행하면 두 폴더의 내용이 항상 똑같게 된다.

사용자 삽입 이미지

‘View > Option’을 클릭하면 Allway Sync의 환경설정 메뉴가 나타난다. 아쉽게도 한국어는 지원하지 않고 있다. 동기화를 자동으로 특정 시간마다 수행하도록 지정(Every)할 수 있으며 Allway Sync를 실행하면 자동으로 동기화가 실행되도록(On application start)할 수도 있다. 기본값으로 지정하고 사용해도 불편하지 않게 사용이 가능하다.

사용자 삽입 이미지


※TIP : 윈도우의 서류가방을 이용한 파일 동기화
윈도우에도 파일을 동기화 해주는 기능이 제공된다. 서류가방이라는 이름으로 제공되는 이 기능을 이용하면 특정 폴더간에 저장된 파일들을 자동으로 비교해서 항상 최신의 내용으로 두 폴더를 동기화해준다. 이 기능을 이용하려면 서류가방이라는 폴더를 만들어야 하며 이 폴더는 마우스 오른쪽 버튼을 클릭해서 나오는 메뉴에서 ‘새로 만들기 > 서류 가방’이라는 메뉴를 이용한다. 이렇게 해서 생성된 서류가방에 원본 폴더 혹은 원본 파일을 복사해두면 서류가방에 저장된 폴더, 파일과 원본 폴더, 파일을 비교해 항상 최신 버전으로 두 폴더, 파일을 동기화 해준다.

사용자 삽입 이미지

사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지
사용자 삽입 이미지
2. 최신 파일로 비교해서 동기화 하기
사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지

원본 파일과 동기화할 폴더를 지정했다면 이제 두 곳에 저장된 파일들이 서로 일치하도록 동기화해줄 차례이다. 동기화는 간단한 방법으로 수행할 수 있다. 특히 Allway Sync는 여러개의 작업을 생성해두어 여러 폴더를 동기화할 수 있다.

두 폴더를 동기화하려면 하단의 ‘Synchronize’라는 메뉴를 클릭한다. 잠시 후에 화면에 두 폴더에 저장된 파일을 비교해 동기화를 수행하고 그 결과가 표시된다. ‘All Files’는 폴더에 저장된 파일의 전체 개수를 말해주며 ‘Unchanged Files’는 동기화하지 않은 파일, ‘New Fles’는 동기화를 통해 새롭게 복사한 파일의 수를 말한다.

사용자 삽입 이미지

동기화 후에 앞서 우측의 ‘View’를 클릭하면 원본 파일이 그대로 우측의 폴더에 저장되어 있음을 확인할 수 있다.

사용자 삽입 이미지

‘Changed Files’를 클릭하면 변경된 파일에 대한 정보와 동기화한 내역을 확인할 수 있다. 화살표가 좌측에서 우측으로 된 것은 왼쪽 폴더의 파일이 오른쪽 폴더의 파일로 대체된 것을 말하며, 우측에서 좌측으로 된 화살표는 우측 폴더의 파일이 좌측 폴더의 파일로 대체된 것을 말한다. 이렇게 변경된 파일은 ‘Updated’라고 표기된다.

사용자 삽입 이미지

만일 파일이 생성되었다면 ‘Updated’라는 이름 표기없이 생성된 파일이 해당 폴더로 복사된다. 이렇게 해서 양쪽 폴더는 똑같은 파일로 동기화된다.

사용자 삽입 이미지

Allway Sync는 여러 개의 폴더를 동기화 설정해둘 수도 있다. 즉 2개의 폴더를 동기화하는 것이 아니라 3곳, 4곳에 폴더를 만들어 동기화할 수도 있다. 새롭게 동기화할 폴더를 지정하려면 ‘Advanced > Add Sync Folder’를 클릭한다.

사용자 삽입 이미지

이렇게 해서 새로운 폴더를 지정하면 총 3곳의 폴더를 동기화할 수 있다. 중요한 데이터는 여러 곳에 나누어 저장하는 것이 안전하다. 단, 이렇게 여러개의 폴더에 파일을 나누어 동기화할 때는 같은 드라이브가 아닌 다른 드라이브로 폴더를 나누어 저장하는 것이 좋다.

사용자 삽입 이미지

상단의 ‘Change’를 클릭하면 동기화할 때의 방향을 지정할 수 있다. 양쪽 화살표가 모두 표기된 것을 선택하면 양쪽 폴더에서 변경된 파일을 추적해 양 폴더를 동기화해준다. 반면 한쪽 방향으로만 표기된 화살표를 선택하면 한 쪽 폴더의 내용을 다른 폴더로 일치시켜준다. 즉, ‘→’를 선택하면 왼쪽 폴더의 파일이 항상 오른쪽 폴더에 복사된다. 오른쪽 폴더에서 변경된 파일은 동기화되지 않고 왼쪽 폴더의 파일만이 오른쪽 폴더로 동기화된다.

사용자 삽입 이미지

‘New Job’를 클릭하면 새로운 동기화 작업을 등록할 수 있다. 이러한 방법으로 여러개의 폴더를 동기화 설정할 수 있다.

사용자 삽입 이미지

글/김지현(oojoo@oojoo.co.kr)

신고
Posted by The.민군
CVS 서버 구축과 CVS를 통한 프로젝트 수행(2002.11.13)사용자 삽입 이미지

JLab 편집실 kinta, jini (류균태, 허원진)

  • CVS 서버 구축
  • 이크립스 CVS 설정
  • 이크립스를 통한 팀프로젝트

    CVS는 2인 이상의 개발자를 내트워크로 묶어주는 개발환경을 제공합니다. 여러분이 쓰고 있는 거의 대부분의 오픈 프로젝트의 결과물 (톰켓, JBOSS, MySQL ...)모두가 CVS를 이용 프로젝트를 완성한 예입니다. 여기에서 우리는 CVSNT로 CVS서버를 구축하고 이를 이크립스를 통해 팀개발을 하는 방법에 대하여 알아 봅니다. 이 글은 CVS 서버 구축과 CVS 사용 두개의 파트로 나누어져 있습니다. CVS서버를 구축 할 필요가 없는 분들은 첫 번째 파트를 건너 뛰셔도 상관 없습니다.

    NOTE:여기에서 설명하는 CVSNT는 윈도우XP와 NT에서만 작동합니다.

    CVS 서버 구축

    1.CVSNT를 통해 CVS 서버를 구축하기 위해서는 파일 시스템을 NTFS로 바꾸어야 합니다. 관리자로 접속 후에 파일 관리툴로 바꾸세요.

    사용자 삽입 이미지

    2.우리가 사용할 CVSNT를 다운 받아야 합니다.

    http://www.cvsnt.org/에서 받을 수 있으며 11월 11일 현재 최신 버젼은 1.11.1.3 build 57i 입니다.

    3.CVSNT가 사용할 프로젝트 디렉토리와 템프 디렉토리를 만듭니다.

    여기서 주의 할 점은 c:\WINNT\Temp나 C:\Documents and Settings의 하위 디렉토리로 만들면 않됩니다. XP와 NT는 그 디렉토리의 경우 권한을 특정 계정에 국한 시키기 때문입니다.

    c:\cvsrepo, c:\cvstemp 이런 식으로 만드시면 됩니다.(위의 경우를 제외한 어떤 NTFS안의 경로도 상관 없습니다.)

    4.CVSNT를 인스톨 합니다.

    사용자 삽입 이미지

    간혹 CVSNT의 패스를 자동으로 설정 못했다는 메세지가 나옵니다. 이때에는 수동으로 설정해 주시면 됩니다.

    사용자 삽입 이미지

    5.CVSNT를 실행합니다.

    사용자 삽입 이미지

    CVS 서비스가 작동 중이면 중지시키세요(위와 같은 상태로 만드세요) 이제 앞서 만든 구개의 디렉토리(프로젝트와 템프)를 설정합니다.Repositories와 Advanced 탭에 있습니다.

    사용자 삽입 이미지

    그런 후 Repositories 탭에서 테스트에 사용할 임시 프로젝트를 Add 버튼을 눌러서 생성합니다. 실제 루트는 /test 와 같은 방식의 유닉스 방식으로 접근되므로 다른 시스템에서 사용시에도 문제 없습니다.

    6.설정은 모두 끝났습니다. 서버를 시작합니다. 5번의 그림에서 CVS Service와 CVS Locking service 모두 Start를 누르세요.(적용을 누르지 않으면 서버가 시작 되지 않습니다) 설치 후에 리부팅 하면 서비스에 등록 되어 윈도우 시작 시 자동 실행됩니다.

    7.사용자 추가

    프롬프트를 열고 아래의 <computer name>에 실제 컴퓨터 이름으로 대체 해서 입력합니다.

    set cvsroot=:ntserver:<computer name>:/<cvsroot name>

    이제 NT 계정으로 사용자를 하나 추가합니다

    cvs passwd -a <user>

    만일 이 과정에서 cvs 명령어를 알 수 없다는 애러가 발생한다면 아직 패스 설정이 적용이 안돼서 그럽니다. 다른 애러 라면 workstation 서비스가 시작이 안됐거나, 방화벽문제 입니다. 방화벽을 사용할 경우

    set cvsroot=:sspi:protocol:<computername>:/<cvsroot name>이렇게 바꾸어 주십시오.

    사용자 삽입 이미지

    NT 계정이 아닌 다른 이름으로 추가 할 경우, 아래와 같이 하시면 됩니다.

    cvs passwd -r <NT user> -a <user>

    이크립스 CVS 설정

    CVS서버를 Eclipse에서 사용하기 위해 세팅 하는 법을 살펴보겠습니다.

    1. CVS Repository 창을 윈도우에 표시합니다
    Window ->Open Perspective ->Other ->CVS Repository Exploring
    만약 이미 CVS Repository Exploring을 사용한적이 있다면 화면처럼 바로 표시가 되므로 직접 선택하면 됩니다.

    사용자 삽입 이미지



    사용자 삽입 이미지

    이제 화면에 다음과 같이 CVS Repositories가 표시됩니다. 혹시 안보이면 왼쪽에 cvs라는 글자가 보이는 아이콘을 클릭하면 됩니다.

    이제 사용할 CVS 저장소(Repository)를 설정해야 하는데요. 위에서 CVSNT를 설치하면서 test라는 이름으로 저장소를 만들었다면 다음과 같이 따라 하시면 됩니다.

    사용자 삽입 이미지

    2. CVS Repositories 창에서 오른쪽버튼 클릭 - New ->Repository Location
    - 호스트이름 또는 IP, 저장소(Repository) 위치 그리고 CVS User ID와 패스워드가 필요합니다.


    사용자 삽입 이미지

    3. Check Out As Project 를 이용하면 전체 프로젝트를 로컬컴퓨터에 옮겨서 작업할 수 있습니다.


    사용자 삽입 이미지

    4. Share Project 를 이용하면 로컬에 있는 프로젝트를 CVS 서버로 옮길 수 있습니다



    사용자 삽입 이미지

    사용자 삽입 이미지

    5. 소스파일을 수정한 후 Team ->Commit 을 선택하면 수정된 부분을 CVS서버에 반영합니다.


    6. 반대로 Team ? Update 를 선택하면 CVS서버로부터 수정된 부분을 가져옵니다.


    사용자 삽입 이미지

    commit할때 변경사항이나 다른 부가정보를 적어주면 다음에 참고할 수 있습니다.

    사용자 삽입 이미지

     

    실제 프로젝트에서 CVS를 사용하는 부분은 다음 기사에서 다루겠습니다.


    www.jlab.net

    이 글은 정보 공유를 위해 쓰여 졌으며JLab 정보 공유 약관

    신고
    Posted by The.민군
  • 사용자 삽입 이미지

    Eclipse와 CVS를 이용하여 팀 작업하기


    Summary :
    지금까지 진행한 Eclipse강좌의 대부분은 Eclipse IDE에 플러그인을 설치하는 방법에 대하여 살펴보았다. 이번 강좌에서는 Eclipse에 설치되어 있는 CVS를 이용하여 팀작업과 소스관리를 어떻게 할 수 있는지에 대하여 살펴보도록 하겠다. CVS의 필요성에 대해서는 많은 개발자들이 알고 있지만 정작 Eclipse내에서 CVS의 사용방법을 잘 몰라 사용하지 못하는 개발자들이 많은 것 같다. 따라서 이번 강좌에서는 Eclipse IDE기반하에서 CVS를 이용하는 방법에 대하여 살펴보도록 하겠다.

    사용자 삽입 이미지  Requirements
    CVS는 소스의 버전관리가 가능하도록 해주는 잘 알려진 오픈 소스중의 하나이다. CVS외에도 많은 버전관리 시스템이 존재하고 있다. 필자는 그냥 편하게 버전관리를 지원하는 툴이라고 말하는데 좀 더 유식한 사람들은 소스를 형상관리 한다는 말을 사용하곤 한다. 하지만 필자가 형상 관리에 대하여 전문적으로 공부하거나 가지고 있는 지식이 없기 때문에 필자가 처음부터 사용해오던 말인 버전 관리 시스템이라는 말로 강좌를 진행하려고 한다. 물론 버전 관리 이외에도 더 많은 기능들을 제공할 것이라고 생각한다. 하지만 아직까지 필자가 주로 사용하고 있는 부분이 버전관리의 기능이 주된 기능이고, 이 기능만으로도 버전관리 시스템이 왜 필요한지를 뼈저리게 느끼고 있다.

    프로그래밍 초기에는 한 명의 개발자가 하나의 솔루션이나 애플리케이션을 개발하는 것이 다반사였다. 그러나 점차 시스템이 커지면서 하나의 프로젝트를 진행하기 위하여 여러명의 개발자가 공동 개발하게 되었다. 최근에는 거의 대부분의 프로젝트가 3-4명에서부터 수십명의 개발자들이 관여하고 있다. 이처럼 수 많은 개발자들이 하나의 프로젝트에 관여하다 보니 소스를 관리하고 통합하는데 너무나 많은 시간을 소요하게 되었다.

    많은 개발자들이 프로젝트를 진행하면서 다음과 같은 경험을 한적이 있을 것으로 생각한다. 분명 어제 발생했던 버그를 고생고생하면서 수정한 다음 서버에 반영하였다. 그런데 다음날 체크리스트에 똑같은 버그가 그대로 남아 있다. 확인해본 결과 다른 개발자가 자신이 수정하기 전 소스를 수정한 다음 덮어씌워 버린 경우가 종종 있을 것이다. 처음에는 "뭐 나도 가끔 그러는데..", "누구나 실수는 할 수 있지.." 하면서 넘어간다. 그러나 그 횟수가 증가하면서 점점 개발자간에 짜증이 늘어나게 되며, 만약 되던 잘 실행되던 소스가 문제가 생길경우 자신의 소스문제라고 생각하기보다는 누군가 내 소스를 건드리지 않았을까하는 의심부터 하게 된다. 또한, 개발했던 소스를 또 다시 개발하면서 발생하는 의욕 저하와 적지 않은 시간을 중복투자해야 한다.

    이처럼 하나의 프로젝트에 여러명의 개발자가 관여하게 되면서 소스관리와 소스통합은 프로젝트의 성패에 중요한 한 요인이 되고 있다. 필자는 버전 관리 시스템을 적용한 프로젝트와 그렇지 않은 프로젝트를 모두 경험했는데, 적용한 프로젝트와 그렇지 않은 프로젝트 간에 상당한 차이점을 느낄 수 있었다. 아무리 작은 프로젝트이더라도 버전관리 시스템을 적용하지 않았을 경우 소스의 유실로 인해 발생하는 문제가 예상보다 컸다. 개발자간의 짜증 또한 증가할 수 밖에 없었으며, 그로 인해 발생하는 시간낭비와 개발자의 의욕저하는 이로 말할 수 없었다.

    따라서 필자는 프로젝트에서 버전관리 시스템의 중요성을 느끼고 아직 이 시스템을 경험하지 못한 많은 개발자들이 이 시스템을 이용했으면 하는 바람으로 이 강좌를 준비했다. 버전 관리 시스템은 오픈 소스를 많이 알려져 있는 CVS를 선택했으며, 개발툴은 Eclipse를 이용하였다. CVS서버를 설치하고 Eclipse IDE와 연동하는 방법에 대해서는 이전 강좌를 참고하기 바란다.

    이 강좌에서는 Eclipse 기반하에서 CVS 서버를 이용하여 어떻게 효율적으로 팀작업을 진행할 수 있는지에 대하여 살펴보도록 하겠다. CVS를 사용할 경우 발생하는 생소한 용어들과 다른 개발자들과의 소스 충돌이 발생할 경우 어떻게 해결할 수 있는지에 대하여 살펴보도록 하겠다.

    사용자 삽입 이미지  CVS 테스트 환경 구축하기

    CVS는 팀작업이 가능하도록 하기 위하여 버전관리 시스템이다. 따라서 CVS작업을 테스트하기 위해서는 한명의 개발자만으로는 테스트하기 힘들다. 만약 이 강좌를 팀 내에서 테스트한다면 하나의 CVS서버에 다른 계정을 가지는 여러명의 개발자들이 접속하여 테스트를 진행할 수 있다. 또한 그처럼 테스트 해야 소스의 충돌과 같은 문제가 발생할 경우 어떻게 해결할 수 있는지에 대해서도 테스트가 가능하다. 그러나 자신 혼자 테스트할 수 밖에 없는 환경이라도 너무 걱정하지 마라. 그 또한 테스트가 가능하다.

    지금 구축하는 환경은 CVS서버를 설치하고 테스트할 개발자가 자신 혼자일 경우라고 가정하고 환경을 세팅하도록 하겠다. 만약 팀 내에서 테스트한다면 지금 강좌에서 하나의 컴에 설치하는 환경을 각각의 개발자들이 하나의 역할을 맡아서 진행하면 된다.

    CVS서버는 CVSNT서버가 설치되어 있다는 가정하에서 진행하도록 하겠다. CVSNT 서버의 콘솔 화면을 띄운다음 Repositories패스를 다음 그림과 같이 설정한다. 즉 현재 Repository로 설정되어 있는 디렉토리를 D:\Repository\CICRepository\Project로 사용하고자 한다면 Valid Repository Roots에 절대 경로를 추가해준다. 만약 Repository Prefix기능을 이용할 경우 Eclipse의 모든 기능을 이용할 수 없게 된다. 그러나 Jbuilder와 같은 IDE에서는 Repository Prefix기능을 이용해도 큰 문제가 발생하지 않는 것을 확인할 수 있었다. CVS의 Repository Path정보를 위와 같이 설정하고 CVSNT 서버를 시작한다.

    사용자 삽입 이미지
    CVSNT 서버 Repositories 화면 : CVSNT 서버 Repositories 화면에서 Valid Repository Roots 값을 D:\Repository\CICRepository\Project로 설정하는 화면


    다음은 OS에 두명의 사용자를 추가한다. 이 강좌에서는 cvsuser1과 cvsuser2를 추가하여 진행하도록 하겠다.

    Eclipse의 CVS Repositories로 이동하여 cvsuser1과 cvsuser2의 사용자 계정으로 각각 CVS 서버로 Repository 연결을 한다.

    사용자 삽입 이미지
    새로운 CVS Repositories 연결화면 : cvsuser1 계정으로 CVSNT서버에 새로운 Repository를 연결하는 화면

    사용자 삽입 이미지
    새로운 CVS Repositories 연결화면 : cvsuser2 계정으로 CVSNT서버에 새로운 Repository를 연결하는 화면




    cvsuser1과 cvsuser2로 CVS Repository연결이 정상적으로 진행되었다면 다음과 같은 화면을 볼 수 있다.

    사용자 삽입 이미지
    CVS Repositories 화면 : cvsuser1과 cvsuser2의 사용자로 CVS Repository에 연결되었을 때의 화면


    이처럼 cvsuser1과 cvsuser2 계정으로 CVS Repository 연결을 진행한 다음 각각의 계정을 위한 프로젝트를 생성한다. 이 강좌에서는 cvsuser1project와 cvsuser2project라는 이름으로 두개의 java 프로젝트를 생성하겠다.

    사용자 삽입 이미지
    Navigator화면 : cvsuser1과 cvsuser2 계정 각각을 위해 cvsuser1project와 cvsuser2project프로젝트를 생성한 화면


    cvsuser1project프로젝트를 cvsuser1계정으로 연결한 CVS Repository와 연결시킨다. 연결시키는 과정은 먼저 cvsuser1project프로젝트에서 오른쪽 클릭 >> Team >> Share Project를 선택한 다음 앞에서 생성한 CVS Repository중 cvsuser1계정으로 연결한 Repository를 선택한다.

    사용자 삽입 이미지
    프로젝트를 CVS 서버와 연결하는 화면 : cvsuser1project프로젝트를 cvsuser1 계정으로 생성한 CVS Repository와 연결시키는 화면


    CVS와의 연결시 사용할 이름을 지정한다. 이 강좌에서는 CVSTEST라는 이름을 사용하겠다.

    사용자 삽입 이미지
    CVS 모듈 이름 설정화면 : cvsuser1project프로젝트를 CVS와 연결시 모듈이름을 CVSTEST로 지정하는 화면


    cvsuser1project프로젝트를 CVS와 연결하는 과정이 완료되면 같은 방법으로 cvsuser2project프로젝트를 cvsuser2 계정으로 생성한 CVS Repository와 연결한다.

    사용자 삽입 이미지
    프로젝트를 CVS 서버와 연결하는 화면 : cvsuser2project프로젝트를 cvsuser2 계정으로 생성한 CVS Repository와 연결시키는 화면


    CVS와의 연결시 사용할 이름을 지정한다. 이 강좌에서는 CVSTEST라는 이름을 사용하겠다.

    사용자 삽입 이미지
    CVS 모듈 이름 설정화면 : cvsuser2project프로젝트를 CVS와 연결시 모듈이름을 CVSTEST로 지정하는 화면


    이상으로 Eclipse 기반하에서 CVS 서버를 사용하기 위한 환경구축을 모두 마쳤다. 다음 절에서는 cvsusr1과 cvsuser2 계정으로 새로운 소스를 생성하여 CVS에 추가하고 CVS에 추가한 소스를 로컬 프로젝트로 가져오는 방법에 대하여 살펴보도록 하겠다.

    사용자 삽입 이미지  CVS에 새로운 소스 추가하기

    CVS와의 테스트에서 처음으로 진행할 부분은 로컬에서 생성한 소스를 CVS 서버에 추가하는 과정이다. 먼저 cvsuser1project프로젝트의 src폴더 에 HelloWorld1.java와 HelloWorld2.java를 생성한다.

    package net.javajigi;/** * @author Administrator */public class HelloWorld1 {}

    HelloWorld1.java : HelloWorld1.java

    package net.javajigi;/** * @author Administrator */public class HelloWorld2 {}

    HelloWorld2.java : HelloWorld2.java


    이와 같이 로컬에 처음으로 생성된 소스파일은 로컬에만 존재하게 된다. 이처럼 로컬에 존재하는 파일을 CVS서버에 추가하기 위해서는 CVS의 Add기능을 이용하면 된다. 이를 실행하기 위해서는 src폴더에서 오른쪽 클릭 >> team >> add to version control을 이용하여 추가할 수 있다.

    사용자 삽입 이미지
    CVS의 Add to version control 메뉴 : cvsuser1project프로젝트에 생성한 소스를 CVS에 추가하고 있는 화면


    add to version control 기능을 이용하여 CVS에 소스를 추가했다고 해서 CVS에 새로운 버전의 소스가 생성되는 것은 아니다. CVS 서버에 최종적으로 새로운 버전을 생성하기 위해서는 CVS의 commit기능을 이용해야 한다. CVS의 commit은 commit하고자 하는 소스를 선택한 다음 오른쪽 클릭 >> team >> commit 명령어를 실행하여 진행할 수 있다.

    사용자 삽입 이미지
    CVS의 commit 메뉴 : cvsuser1project프로젝트에 생성한 소스를 CVS에 Commit하고 있는 화면

    사용자 삽입 이미지
    CVS의 commit시 부연 설명 추가화면 : cvsuser1project프로젝트에 생성한 소스를 CVS에 Commit할 때 부연 설명을 추가할 수 있는 화면


    CVS에 소스가 정상적으로 추가되었는지를 확인하고 싶다면 HelloWorld.java파일 오른쪽 클릭 >> team >> Show in Resource History 명령을 실행하여 확인할 수 있다. 다음 화면과 같이 Revision 1.1을 가지는 소스가 Repository History에 추가되어 있는 것을 확인할 수 있다.

    사용자 삽입 이미지
    CVS의 Show in Resource History을 실행한 화면 : cvsuser1project프로젝트에 생성한 HelloWorld.java소스가 CVS에 정상적으로 추가되었는지를 확인하기 위한 소스.


    이상으로 cvsuser1project프로젝트에서 생성한 소스를 CVS에 추가하는 과정을 마쳤다. cvsuser2project프로젝트도 cvsuser1project프로젝트와 마찬가지로 과정으로 HelloWorld3.java, HelloWorld4.java를 추가한 다음 CVS에 추가한다. 이렇게 추가된 소스는 다음 절에서 각각의 프로젝트로 가져올 수 있다.

    사용자 삽입 이미지  CVS에 추가되어 있는 소스 가져오기

    다음은 각각의 개발자들에 의하여 추가된 소스를 자신의 로컬로 가져오는 방법이다. 자신의 로컬에서 새로 생성된 소스를 추가하는 것 또한 중요하지만 남이 개발해 놓은 소스를 가져오는 것 또한 중요하다. 앞 절에서 추가한 소스들을 다시한번 살펴보면 cvsuser1project프로젝트에서 HelloWorld1.java와 HelloWorld2.java를 추가하였고, cvsuser2project프로젝트에서 HelloWorld3.java와 HelloWorld4.java 소스를 추가하였다.

    cvsuser1은 HelloWorld1.java와 HelloWorld2.java 두개의 소스에 자신이 구현하고자하는 소스들을 구현하느라 몇시간의 시간이 흘러 버렸다. cvsuser1은 CVS에 새로운 소스가 있는지 확인한다음, 만약 새로운 소스가 추가되었다면 새로 추가된 소스를 자신의 프로젝트로 가져오고 싶다.

    이와 같이 다른 개발자가 추가한 소스를 자신의 로컬로 가져오기 위해서는 먼저 CVS와 자신의 로컬 프로젝트를 동기화할 필요가 있다. 이와 같이 프로젝트와 CVS를 동기화하는 방법은 동기화하고자하는 디렉토리에서 오른쪽 클릭 >> team >> Synchronize with Repository 명령을 실행한다.

    사용자 삽입 이미지
    CVS의 Synchronize with Repository 메뉴 : cvsuser1project프로젝트의 소스 폴더를 CVS와 동기화하는 화면


    Synchronize with Repository 명령을 실행하여 CVS와 동기화를 진행하면 현재 프로젝트에 있는 소스와 CVS 서버 사이의 소스에서 동기화되어 있지 않은 소스들의 정보를 다음 화면과 같이 Synchorinze 화면에 제공하게 된다.

    사용자 삽입 이미지
    CVS와 동기화를 진행한 후 Synchronize화면 : cvsuser1project프로젝트의 소스 폴더를 CVS와 동기화한 후 동기화 정보를 제공하는 Synchronize 화면


    cvsuser1project프로젝트의 소스폴더에서 동기화를 하면 cvsuser2project프로젝트에서 추가한 HelloWorld3.java와 HelloWorld4.java 소스가 Synchronize의 Incoming Mode에서 나타나는 것을 확인할 수 있다. 이것의 의미는 동기화한 결과 CVS에는 HelloWorld3.java와 HelloWorld4.java 소스가 추가되었기 때문에 자신의 로컬 디렉토리에 추가하라는 의미이다.
    따라서 HelloWorld3.java와 HelloWorld4.java 소스를 선택한 다음 오른쪽 클릭 >> Update From Repository 명령을 이용하여 CVS에 추가되어 있는 두개의 소스를 로컬로 가져올 수 있다. 이와 같이 CVS서버에 있는 소스를 로컬로 가져오는 것을 Checkout이라고 한다.

    사용자 삽입 이미지
    Synchronize화면에서 Update From Repository 명령 : CVS와 동기화한 후 동기화 정보를 제공하는 Synchronize 화면에서 추가된 소스를 로컬로 Checkout하는 화면

    사용자 삽입 이미지
    CVS에서 Checkout한 다음 cvsuser1project프로젝트 : CVS의 소스를 Checkout한 다음 HelloWorld3.java와 HelloWorld4.java파일을 cvsuser1project프로젝트에 추가되어 있는 것을 확인할 수 있는 화면.


    이상으로 cvsuser2project프로젝트에서 추가한 소스를 cvsuser1project프로젝트에서 Checkout하는 과정에 대하여 살펴보았다. cvsuser2project 프로젝트에서도 이상의 과정과 같은 방법으로 cvsuser1project 프로젝트에서 추가한 HelloWorld1.java와 HelloWorld2.java파일을 Checkout해보기 바란다.

    다음 절에서는 기존에 존재하는 소스를 수정한 다음 CVS에 반영하는 방법에 대하여 살펴보도록 하겠다.

    사용자 삽입 이미지  CVS에 수정된 소스 반영하기

    cvsuser1과 cvsuser2 계정을 가진 개발자는 자신이 생성한 소스파일을 이용하여 프로그램을 개발하였다. 개발을 진행하던 중 하나의 모듈이 완성되어 CVS 서버에 반영하고자 한다. 이처럼 로컬에서 진행하던 작업중 한 파트가 완료하게 되면 대부분의 개발자들은 CVS서버에 완료된 부분을 추가하게 된다. 이처럼 수정된 소스를 CVS서버에 반영하는 방법에 대하여 살펴보도록 하겠다.

    먼저 cvsuser1 계정을 가진 개발자는 HelloWorld1.java와 HelloWorld2.java 소스를 다음과 같이 수정하였다.

    package net.javajigi;/** * @author Administrator */public class HelloWorld1 { public String helloWorld1(String name){  return name + " Hello World"; }}

    HelloWorld1.java : helloWorld1() 메써드를 추가한 HelloWorld1.java 소스파일

    package net.javajigi;/** * @author Administrator */public class HelloWorld2 { public String helloWorld2(String name){  return name + " Hello World"; }}

    HelloWorld2.java : helloWorld2() 메써드를 추가한 HelloWorld2.java 소스파일


    예제의 단순화를 위하여 각각의 클래스에 helloWorld1(), helloWorld2() 메써드를 하나씩 추가하였다. 이처럼 자신이 작업하고자하는 소스의 개발이 완료되었다고 가정하자. 다음은 CVS서버에 지금까지 개발한 소스를 반영하고자 한다. 이를 위한 첫번째 단계는 로컬 프로젝트와 CVS서버를 동기화하는 것이다.

    앞절에서 했던 방법과 같이 src폴더에서 오른쪽 클릭 >> team >> Synchronize with Repository 명령을 이용하여 동기화를 진행한다. 이 명령을 실행하면 Synchronize 화면에 Outgoing Mode가 표시되면서 HelloWorld1.java, HelloWorld2.java 소스가 리스트에 보이기 된다. 이는 cvsuser1project 프로젝트와 CVS 서버의 동기화 결과 위 두개의 파일이 불일치하고 있으며, 로컬의 소스가 수정되었다는 것을 보여주고 있는 것이다.

    사용자 삽입 이미지
    동기화 결과 Synchronize화면 : CVS와 동기화한 후 동기화 정보를 제공하는 Synchronize 화면에서 수정된 소스를 보여주고 있는 화면


    이처럼 Synchronize 화면의 Outgoing Mode에 리스트되는 파일들은 로컬에서 변경되었기 때문에 CVS서버에 수정된 내용을 반영해주어야 한다는 것을 의미한다. HelloWorld1.java, HelloWorld2.java 소스를 CVS서버에 반영하기 위한 방법은 먼저 이 두개의 파일을 선택한 다음 오른쪽 클릭(해당 소스의 상위 디렉토리인 javajigi 디렉토리에서 오른쪽 클릭도 가능) >> commit 명령을 실행한다.

    사용자 삽입 이미지
    Synchronize화면에서 commit 명령 : CVS와 동기화한 후 동기화 정보를 제공하는 Synchronize 화면에서 commit 명령을 실행하는 화면


    commit 명령을 실행하면 CVS에 새로운 버전의 소스가 생성된다. 처음 소스가 생성되었을 때 1.1버전이라면 위와 같이 소스를 수정한 다음 commit 명령을 실행 후 1.2 버전을 가지는 소스가 생성된다.

    사용자 삽입 이미지
    Show in Resource History 명령을 실행했을 때의 화면 : HelloWorld1.java파일에 대하여 Resource History를 확인했을 때 1.2 버전까지 생성되었음을 확인할 수 있는 화면


    cvsuser2project 프로젝트의 HelloWorld3.java와 HelloWorld4.java 소스에도 위와 같이 새로운 메써드를 추가한 다음 commit해 보기 바란다. 각각의 프로젝트에서 commit을 진행한 다음 동기화를 통하여 수정된 소스들을 다시 Check out해보기 바란다.

    지금은 CVS를 어떻게 사용하는지 익히는 과정이기 때문에 가능한 여러번 각각의 기능을 계속해서 사용해보는 것이 중요하다. 따라서 기회가 생길 때마다 동기화를 하고 각각의 소스를 동기화해보기 바란다.

    다음 절에서는 여러명의 개발자가 같은 소스를 수정했을 경우 발생하는 소스 충돌을 어떻게 해결하는지에 대하여 살펴보도록 하겠다.

    사용자 삽입 이미지  소스 충돌이 발생할 경우 해결하는 방법

    버전 관리 시스템을 사용하다 보면 종종 발생하는 문제가 같은 소스를 두명 이상의 개발자가 동시에 수정했을 때의 충돌 문제이다. 사실 버전 관리 시스템을 사용할 때 가장 큰 이슈가 되는 부분이고 개발자들간에 약속 및 규칙이 있어야 하는 부분이다. 따라서 프로젝트를 시작할 때 CVS 소스 충돌시 어떻게 해결할지에 대한 규칙을 정해야 한다.

    프로젝트를 진행하다보면 같은 소스를 2명 이상의 개발자가 동시에 개발하는 경우가 종종 발생하게 된다. 이럴 경우 각각의 개발자가 수정한 소스가 충돌을 일으킬 수 있다.

    이 강좌에서는 cvsuser1과 cvsuser2가 HelloWorld1.java를 동시에 개발한다는 가정하에서 진행하도록 하겠다. 먼저 각각의 개발자가 개발한 소스는 다음과 같다.

    package net.javajigi;/** * @author Administrator */public class HelloWorld1 { private String name = null; public String helloWorld1(String name){  this.name = name;    return name + " Hello World"; } /**  * @return  */ public String getName() {  return name; } /**  * @param string  */ public void setName(String string) {  name = string; }}

    cvsuser1 개발자가 수정한 HelloWorld1.java : cvsuser1 개발자가 수정한 HelloWorld1.java 소스파일. name property를 추가하고 setter와 getter를 생성.


    위 소스와 같이 수정한 다음 CVS서버에 commit한다.

    cvsuser2 개발자는 자신이 원하는 방향으로 HelloWorld1.java 소스를 수정한 다음 CVS서버와 동기화를 진행한다. cvsuser2 개발자가 수정한 소스는 다음과 같다.

    package net.javajigi;/** * @author Administrator */public class HelloWorld1 { private String address = null; public HelloWorld1(String address){  this.address = address; } public String helloWorld1(String name){  return name + " Hello World"; } /**  * @return  */ public String getAddress() {  return address; } /**  * @param string  */ public void setAddress(String string) {  address = string; }}

    cvsuser2 개발자가 수정한 HelloWorld1.java : cvsuser2 개발자가 수정한 HelloWorld1.java 소스파일. address property를 추가하고 setter와 getter를 생성. 생성자를 통해 address정보를 받는다.


    cvsuser2는 위와 같이 소스를 수정하였다. cvsuser1 개발한 소스와 완전히 다른 방향으로 소스를 수정하였다. cvsuser1은 자신이 개발한 소스를 이미 commit하여 cvs 서버에 반영되어 있다. 이처럼 두명의 개발자가 하나의 소스에 대하여 동시에 작업을 진행할 경우 소스의 충돌이 발생하게 된다.

    위와 같이 소스를 수정한 다음 cvsuser2project 프로젝트의 HelloWorld1.java를 CVS 서버와 동기화를 한다. 동기화를 하게 되면 아래 화면과 같은 붉은 색의 양쪽 화살표가 생기면서 CVS서버의 소스와 로컬 소스에서 충돌이 발생했음을 표시해 준다.

    사용자 삽입 이미지
    동기화 결과 Synchronize화면 : CVS와 동기화한 후 동기화 정보를 제공하는 Synchronize 화면에서 충돌이 난 소스를 보여주고 있는 화면


    이와 같이 충돌이 발생하면 CVS 서버의 소스와 로컬 소스를 비교할 필요가 있다. Eclipse에서는 이와 같이 충돌이 발생했을 경우를 위해 Java Source Compare Editor를 제공하고 있다. Synchronize화면에서 HelloWorld1.java를 선택한 후 Double Click 하면 Synchronize 화면 하단에 CVS서버와 로컬 파일 중 서로 불일치하는 부분을 표시해 준다.

    사용자 삽입 이미지
    Java Source Compare Editor 화면 : Synchronize 화면에서 HelloWorld1.java파일을 Double Click할 경우 CVS서버와 로컬 파일에서 불일치하는 부분을 표시해주고 있는 화면


    이처럼 소스의 충돌이 발생했을 때 cvsuser2 개발자가 임의적으로 자신의 소스를 commit하게 되면 cvsuser1이 개발한 소스는 없어지게 된다. 이 경우에는 먼저 cvsuser1 개발자에게 소스의 충돌이 발생했음을 공지한 다음 cvsuser1 개발자와 협조하여 소스를 유지해야 할 부분과 삭제해도 무방한 부분을 찾아야 한다. 그와 같이 CVS 서버의 소스와 로컬 소스의 통합 작업이 완료된 다음 commit작업을 진행해야 한다.

    이 강좌의 HelloWorld1.java파일의 충돌 문제는 cvsuser1과 협의한 결과 cvsuser1이 개발한 모든 소스가 반영되어야 한다고 협조가 이루어 졌다. 이처럼 CVS 서버에 있는 소스를 모두 유지하고 싶을 경우 Java Source Compare Editor를 이용하여 쉽게 통합할 수 있다. Java Source Compare Editor를 보면 소스의 충돌이 발생하는 부분의 소스에 대하여 CVS 서버에서 로컬로 복사할 수 있도록 기능을 제공하고 있다.

    사용자 삽입 이미지
    Java Source Compare Editor에서 통합한 후의 결과 화면 : Java Source Compare Editor에서 CVS 서버의 HelloWorld1.java소스를 로컬 소스와 통합 한 후의 결과 화면


    이처럼 소스의 통합이 완료된 다음 commit을 진행할 수 있다. 충돌이 난 소스에 대한 commit 작업은 Synchronize화면에서 HelloWorld1.java 소스파일 오른쪽 클릭 >> override and commit 명령을 이용하여 실행할 수 있다.

    사용자 삽입 이미지
    소스 통합 후 commit하는 화면 : Java Source Compare Editor에서 HelloWorld1.java 소스의 통합을 완료한 다음 CVS서버에 commit을 실행하는 화면


    위 화면을 보면 override and commit외에 override and update 명령이 있는 것을 볼 수 있다. 이 명령을 실행하면 로컬에 통합된 소스가 CVS서버에 반영되는 것이 아니라 CVS 서버에 있는 소스가 Checkout 되면서 로컬 소스가 Update 되는 기능이다. 따라서 소스 충돌이 발생할 경우 개발자들이 원하는 작업을 다양한 방법으로 진행할 수 있다.
    따라서 소스의 충돌이 발생했을 경우에는 마지막으로 commit개발자가 누구인지를 파악한 다음 해당 개발자와 업무 협조를 하는 것이 가장 좋은 방법이다. 이와 같은 규칙을 지키지 않을 경우 다른 개발자가 개발한 소스를 overwrite할 수 있는 가능성은 존재할 수 밖에 없다. 그러나 CVS의 장점은 버전관리가 된다는 것이다. 따라서 소스의 버전이 증가하면서 누구에 의해 소스가 overwrite되었는지를 찾는 것은 정말 쉬운 일이다. 따라서 남의 소스를 수정해 놓고 오리발을 내밀기는 힘들 것이다. 그러므로 소스의 충돌이 발생했을 때는 항상 해당 개발자와 협조하는 습관을 들이는 것이 좋은 개발 습관이다.

    위에서 생성한 다른 소스파일을 이용하여 충돌이 발생하는 다양한 경우를 만들어보고, 해결해보는 연습을 진행해보기 바란다. 소스의 충돌에 대해서는 다양한 경우가 발생할 수 있기 때문에 충분한 연습을 해보는 것이 좋으며, Eclipse에서 제공하는 다양한 기능들을 이용하여 쉽게 해결할 수 있는 방법을 찾는 것이 중요하다.

    필자 또한 아직 Eclipse에서 제공하는 모든 기능을 활용하지 못하고 있다. 필자 또한 계속해서 새로운 방법들을 찾을 것이며, 이 글을 읽는 개발자들 또한 찾을 수 있을 것이다. 새로운 충돌 해결 방법을 찾을 경우 이 사이트를 통하여 공유해준다면 정말 감사하겠다.

    사용자 삽입 이미지  Eclipse 기반하에서 CVS 사용시 필자의 의견

    이 절에서는 필자가 Eclipse 기반하에서 CVS를 사용하면서 느낀 부분에 대하여 간단하게 언급할 부분들을 모아보았다. 유용할 수도 있지만 필요없는 부분도 많을 것으로 생각되니 이해해주기 바란다.

    1. CVS를 이용하여 프로젝트를 진행할 경우 지금까지 살펴본 기능만으로 충분히 프로젝트를 진행할 수 있다. 그러나 Eclipse는 더 많은 기능을 제공하고 있으며, 필자도 아직 사용해보지 못했기 때문에 다른 부분들이 얼마나 유용한지에 대해서는 언급할 수 없다. 그러나 Eclipse의 더 많은 기능들을 활용해서 프로젝트를 진행해보았으면 하는 바람이다. 좀 더 효율적인 소스관리가 될 것으로 생각한다. Eclipse는 매뉴얼이 정말 잘되어 있기 때문에 매뉴얼을 책 읽듯이 한번 읽으면서 따라해 보면 CVS의 더 고급스러운 기능들을 이용할 수 있을 것이다.

    2. 많은 개발자들이 지금까지 CVS와 같은 버전관리 시스템을 사용하지 않다보니 종종 CVS와 동기화하는 작업을 잊어버리곤 한다. 동기화작업 시간이 길어지면 길어질수록 충돌이 나는 소스파일이 많아지게 되며, 통합하는데만 상당한 시간을 소요하게 된다. 그러므로 종종 CVS와의 동기화 작업을 진행하여 CVS서버에 반영된 소스를 Checkout하기 바란다. 많은 개발방법론에서는 하루에 한번 이상은 하라고 이야기하더군요. 그러나 프로젝트의 성격에 따라 다를 것이라 생각된다. 하지만 컴파일도 되지 않는 소스를 Commit하지는 말아야 한다. 자신이 개발하고 있는 파트가 어느 정도 완료된 다음 Commit하는 습관을 들여야 한다. 따라서 Commit을 하기 전에는 항상 CVS서버의 소스를 Checkout한 다음 소스상에서 컴파일 에러가 발생하지 않는지 확인한 다음 소스를 Commit하시기 바란다.

    3. CVS에 소스를 Commit하거나 Update를 진행할 때 CVS서버와의 동기화없이 해당 작업을 바로 진행하는 개발자들이 있다. 이는 정말 중요한 CVS서버나 자신의 로컬 소스를 날려버릴 수 있다. 따라서 해당 작업을 진행하기 전에 CVS서버와 동기화를 진행한 다음 Synchronize화면에서 Commit과 Update작업을 진행하는 습관을 들여야 한다. 동기화 없이 작업을 진행하다가 후회하는 개발자들을 종종 본다.

    4. CVS를 이용할 경우 CVS에서 다운 받은 소스를 바로 애플리케이션 서버에 디플로이하는 경우를 종종 본다. 이처럼 CVS소스를 바로 디플로이할 경우 CVS와 관련된 정보등 수 많은 필요없는 파일들이 같이 디플로이되게 된다. 따라서 CVS에서 소스를 Checkout한 다음 필요한 소스들만 웹 애플리케이션 구조로 만든 다음에 디플로이하는 것이 소스 관리차원에서 좋다. 이를 가능하게 하고 싶으면 ANT와 같은 툴을 이용하는 것이 좋은 방법이라고 생각한다. CVS이용할 때는 가능하면 ANT를 이용하여 Checkout과 컴파일, 디플로이 등을 작업을 배치로 실행하도록 하는 것이 개발 속도의 향상에 많은 도움이 된다는 것을 느낄 수 있었다.

    5. 아직 국내에는 CVS를 이용하여 프로젝트를 진행하는 곳이 생각보다 많지 않다. 따라서 프로젝트 초기에 모든 개발자들에게 충분한 교육이 선행되어야 한다. 그렇지 않을 경우 개발자들이 CVS를 잘 사용하지 않게되며, 사용한다고 해도 잘못된 사용으로 인해 소스를 날려버리는 문제는 똑같이 발생하게 된다. 따라서 프로젝트 초기에 CVS 관리자를 따로 두어 교육 및 소스관리를 담당하도록 하는 것이 좋다. 많은 개발 방법론에서는 소스관리를 전담으로 하는 사람을 두라고 하지만 국내의 개발 여건상 이는 말도 안되는 소스로 밖에 들리지 않을거 같으며, 개발자들이 이 역할을 겸직할 수 밖에 없을거 같다. PL, TL이 능력이 된다면 이 사람들이 그 역할을 해준다면 더할나위 없이 좋을 것으로 생각된다.

    이상으로 필자가 CVS를 프로젝트에 적용하면서 느꼈던 부분이다. 더 많은 부분들이 있겠지만 필자가 정리하는 습관이 없다보니 모두 기억하지 못하겠다. CVS를 사용했던, 사용하고 있는 개발자들이 있다면 한마디씩 추가해주시면 CVS를 사용하게 될 많은 개발자들에게 도움이 될거라 생각한다. 이 강좌에서 다룬 내용은 CVS의 극히 일부분만을 설명하고 있다. CVS를 제대로 사용하기 위해서는 개발자들의 더 많은 노력이 필요할 것이다. Eclipse의 매뉴얼을 참고하여 더 많은 기능을 프로젝트에 적용해보기 바란다.

    저도 Eclipse에서 제공하는 더 많은 CVS기능을 사용해본 다음에 이 강좌의 후속편을 만들어 보도록 노력하겠다. 이 강좌와 관련된 질문은 이 사이트의 Q&A란을 이용해 주시기 바란다. 가끔 메일과 메신저를 이용해 질문을 하는 개발자들이 있는데 이에 대해서는 답변을 하지 않음을 유념하기 바란다.

    이 강좌를 통해 CVS의 기초를 다지고 더 나은 Process를 알게된다면 이 사이트의 다양한 게시판을 통하여 다른 개발자들과 공유할 수 있었으면 한다. 모든 개발자들이 그럴만한 충분한 능력을 가지고 있으며, 열정 또한 있을 것이라 믿는다.

    저자에 대하여 : 
    박재성 2001년부터 자바지기 사이트를 운영하면서 Java와 XML에 대한 활용방안에 대하여 고민하고 있다. 또한 Eclipse와 Eclipse플러그인을 이용하여 개발속도를 향상시킬 수 있는 방법을 찾고 있다. 현재 프리랜서로 활동 중이다. 주된 관심 분야는 모델2 FrameWork과 JDO를 이용한 MVC 모델구현과 효율적인 개발 Process를 통하여 좀 더 빠르게 프로그램을 개발하는 방법에 대하여 고민하고 있다. 더불어 수 많은 오픈소스 프레임워크을 이용한 개발 방법론에도 많은 관심을 가지고 있다. 현재 공부하고 있는 부분은 AspectJ를 이용한 AOP 개발 방법론에 대하여 공부하고 있다.
    참고 자료  :
    신고
    Posted by The.민군
    Log4J 적용 사례(따라하기) - 손정호
    사용자 삽입 이미지
    사용자 삽입 이미지
    아래 글은 SKT 소액 결재 개발팀 손정호 님이 작성하신 글임을 알려 드립니다.

    참고로 preparedStatement에서 적용한 예 입니다.

    ====================================================================

    Log4j Summary

    이번 WebChannel 개발시에 적용된 Log4j 환경을 바탕으로 작성한 간단한 summary입니다.

    1. 다운로드

    다운로드http://logging.apache.org/log4j/docs/download.html
    매뉴얼http://logging.apache.org/log4j/docs/documentation.html
    API spechttp://logging.apache.org/log4j/docs/api/index.html

    2. 구조
    Log4j는 크게 3가지 요소로 구성되어 있습니다.
    ① Logger : logging 메시지를 Appender에 전달합니다.
    ② Appender : 전달받은 logging 메시지를 원하는 곳으로 보내는 매개체의 역할을 합니다.
       아래 표는 Appender의 종류입니다. API에서 보고 이해가 된 선에서 적었습니다.
    ConsoleAppender        로그 메시지를 콘솔에 출력합니다.
    DailyRollingFileAppender        로그 메시지를 파일로 저장합니다.
    DatePattern 옵션에 따라 원하는 기간마다 로그파일을 갱신합니다.
    ExternallyRolledFileAppender        
    FileAppender        직접적으로 사용되지 않고 DailyRollingFileAppender와 RollingFileAppender의 superclass로 사용되는듯 합니다.
    JDBCAppender        로그 메시지를 DB에 저장합니다. 현재는 완벽하지 않으니 왠만하면 차기 버전에서 사용하라고 하는 것 같습니다.
    JMSAppender        로그 메시지를 JMS Topic으로 보냅니다.
    NTEventLogAppender        NT 이벤트 로그를 위한 Appender. 윈도우에서만 사용가능합니다.
    NullAppender        내부적으로만 사용되는 Appender입니다.
    RollingFileAppender        로그 메시지를 파일로 저장합니다. 설정된 size를 초과하면 로그파일이 갱신됩니다.
    SMTPAppender        로그 메시지를 지정된 이메일로 발송합니다.
    SocketAppender        로그 메시지를 socket을 이용해서 지정된 곳으로 보냅니다.
    SocketHubAppender        위와 비슷하게 사용하는듯 합니다.
    SyslogAppender        로그 메시지를 원격 syslog deamon으로 보냅니다.
    TelnetAppender        로그 메시지를 telnet을 통해 보낸다는 것 같습니다. 원격 모니터링, 특히 servlet의 모니터링에 유용하다고 합니다.
    WriterAppender        FileAppender처럼 주로 superclass로서 사용되는듯 합니다.
    ③ Layout : logging 메시지의 출력 형식을 지정합니다.
            - 아래에서 설명.

    3. 로깅레벨
    FATAL : 가장 크리티컬한 에러가 발생했을 때 사용합니다.
    ERROR : 일반적인 에러가 발생했을 때 사용합니다.
    WARN : 에러는 아니지만 주의가 필요할 때 사용합니다.
    INFO : 일반적인 정보가 필요할 때 사용합니다.
    DEBUG : 일반적인 정보를 상세히 나타낼 때 사용합니다.

    로깅레벨의 우선순위는 FATAL이 가장 높고 DEBUG가 가장 낮습니다.
    예를 들어 레벨을 WARN으로 설정하면 WARN이상되는 로그(FATAL, ERROR, WARN)만
    출력합니다.

    4. 환경설정
    - Log4j의 환경설정은 직접 코드에서 메서드를 이용하는 방법과 properties 파일을 이용하는 방법, XML파일을 이용하는 방법이 있습니다.
    ① 코드에서 설정
    String layout = "%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n";
    String logfilename = "DailyLog.log";
    String datePattern = ".yyyy-MM-dd ";

    PatternLayout patternlayout = new PatternLayout(layout);
    DailyRollingFileAppender appender = new DailyRollingFileAppender(patternlayout, logfilename, datePattern);
    logger.addAppender(appender);
    logger.setLevel(Level.INFO);
    logger.fatal("fatal!!");

    위 코드처럼 설정하시면 됩니다.


    ② properties 파일로 설정
    #---------- file logging ----------
    log4j.rootLogger=INFO, rolling
    #---------- consol logging -----------
    #log4j.rootLogger=INFO, stdout
    #---------- file, console logging -----------
    #log4j.rootLogger=INFO, stdout, rolling
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=[%d] %-5p  at %C{3}.%M(%13F:%L) %3x - %m%n
    log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.rolling.File=/WEB_BACKUP1/pgw_log/webchannel.log
    log4j.appender.rolling.Append=true
    #---------- every day renew ------------
    log4j.appender.rolling.DatePattern='.'yyyy-MM-dd
    #---------- every month renew ------------
    #log4j.appender.rolling.DatePattern='.'yyyy-MM
    #---------- every week renew ------------
    #log4j.appender.rolling.DatePattern='.'yyyy-MM-ww
    #---------- every 12hours renew -------------
    #log4j.appender.rolling.DatePattern='.'yyyy-MM-dd-a
    #---------- every hour renew --------------
    #log4j.appender.rolling.DatePattern='.'yyyy-MM-dd-HH
    #---------- every min renew --------------
    #log4j.appender.rolling.DatePattern='.'yyyy-MM-dd-HH-mm
    log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
    log4j.appender.rolling.layout.ConversionPattern=[%d] %-5p  at %C{3}.%M(%13F:%L) %3x - %m%n


    위 properties 파일은 실제 WebChannel에 적용한 파일입니다.
    - log4j.rootLogger=INFO, rolling
            : 로깅레벨을 ‘INFO’로 하고 ‘rolling’이라는 이름의 Appender를 사용한다.
            위 properties파일에는 ConsoleAppender(stdout)와 DailyRollingFileAppender(rolling)가
    정의되어 있습니다.
    - log4j.rootLogger=INFO, stdout : console에만 출력
    - log4j.rootLogger=INFO, stdout, rolling : console과 file 로 출력
    위처럼 설정이 가능합니다.
    - log4j.appender.stdout=org.apache.log4j.ConsoleAppender
            : ConsoleAppender의 이름은 ‘stdout’으로 한다.
    - log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender
            : DailyRollingFileAppender의 이름은 ‘rollong’으로 한다.
    - log4j.appender.rolling.File=/WEB_BACKUP1/pgw_log/webchannel.log
            : 로그파일의 위치와 파일명을 지정한다.
    - log4j.appender.rolling.Append=true
            : 서버 restart시에도 파일이 reset되지 않는다.
    - log4j.appender.rolling.DatePattern='.'yyyy-MM-dd
            : DatePattern 을 ‘매일갱신’으로 설정. 매일 자정이 지나면
    파일명 뒤에 날짜가 붙는다.
            ex) webchannel.log.2005-11-21
    - log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
            : layout을 PatternLayout으로 설정.
    - log4j.appender.rolling.layout.ConversionPattern=[%d] %-5p  at %C{3}.%M(%13F:%L) %3x - %m%n
            : 로그의 출력 형식을 설정. 아래 설명.

    # log4j.appender.rolling.MaxFileSize=500KB
    : 파일의 최대size 설정하는 부분인데 서버 기동시 최초에 이 부분의 property를 읽지 못했다는 경고가 자꾸 떠서 삭제 했습니다. 설정하지 않으면 Default로 10MB가 설정된다고 합니다.

    #### properties 파일의 변경사항은 server restart시에 적용됩니다. ####
    ③ XML 파일로 설정
    현재 잘 모르니 넘어가겠습니다.-_-


    5. 설정 포맷
    ① DatePattern 설정 포맷
    '.'yyyy-MM         매달 첫번째날에 로그파일을 변경합니다
    '.'yyyy-ww         매주의 시작시 로그파일을 변경합니다.
    '.'yyyy-MM-dd        매일 자정에 로그파일을 변경합니다.
    '.'yyyy-MM-dd-a        자정과 정오에 로그파일을 변경합니다.
    '.'yyyy-MM-dd-HH        매 시간의 시작마다 로그파일을 변경합니다.
    '.'yyyy-MM-dd-HH-mm        매분마다 로그파일을 변경합니다.




    ② PatternLayout 설정 포맷
    %p        debug, info, warn, error, fatal 등의 로깅레벨이 출력된다.
    %m        로그내용(코드상에서 설정한 내용)이 출력됩니다.
    ex) logger.info("log"); 라고 코딩했다면 ‘log’가 로그 내용임.
    %d        로깅 이벤트가 발생한 시간을 기록합니다.
    포맷은 %d{HH:mm:ss, SSS}, %d{yyyy MMM dd HH:mm:ss, SSS}
    같은 형태로 사용하며 SimpleDateFormat에 따른 포맷팅을 하면 된다
    %t        로그이벤트가 발생된 쓰레드의 이름을 출력합니다.
    %%        % 표시를 출력하기 위해 사용한다.
    %n        플랫폼 종속적인 개행문자가 출력된다. \r\n 또는 \n 일것이다.
    %c        카테고리를 표시합니다.
    ex) 카테고리가 a.b.c 처럼 되어있다면
    %c{2}로 설정하면 b.c 가 출력됩니다.
    %C        클래스명을 포시합니다.
    ex) 클래스구조가 org.apache.xyz.SomeClass 처럼 되어있다면
    %C{2}는 xyz.SomeClass 가 출력됩니다
    %F        로깅이 발생한 프로그램 파일명을 나타냅니다.
    %l        로깅이 발생한 caller의 정보를 나타냅니다
    %L        로깅이 발생한 caller의 라인수를 나타냅니다
    %M        로깅이 발생한 method 이름을 나타냅니다.
    %r        어플리케이션 시작 이후 부터 로깅이 발생한 시점의 시간(milliseconds)
    %x        로깅이 발생한 thread와 관련된 NDC(nested diagnostic context)를
    출력합니다.
    %X        로깅이 발생한 thread와 관련된 MDC(mapped diagnostic context)를
    출력합니다.

    ex) [%d] %-5p  at %C{3}.%M(%13F:%L) %3x - %m%n
     [2005-11-23 10:43:21,560] INFO   at
    pgw.database.PGWBoardDAO.selectList(PGWBoardDAO.java:146) -
    ========== PGWBoardDAO#selectList ==========

    포맷의 각 색깔별로 출력되는 실제 예입니다. 포맷 중간에 원하는 단어(at)나
    기호(`.` , `-`)등을 넣으면 그대로 출력됩니다.



    6. 실제 적용 예

    다운받은 log4j.jar파일을 원하는 디렉토리에 복사하고 weblogic의
    startWebLogic.cmd내의 classpath에 잡아줍니다. buildpath도 잡아주셔야 합니다.

    ① PGWBoardDAO.java
    //import 해줍니다.
    import org.apache.log4j.Logger;
    .
    //중략/

    public class PGWBoardDAO extends PGWDAO {
        //parameter로 받은 이름의 instance를 생성합니다.
        static Logger logger = Logger.getLogger("PGWBoardDAO");
    .
    //중략/
    .
    public PGWBean selectList(HashMap hashMap) throws Exception {
    .
    /중략/
    .
    //            pstmt = conn.prepareStatement(sql.toString());
    //LoggableStatement instance생성. LoggableStatement는 아래에서 설명.
                pstmt = new LoggableStatement(conn, sql.toString());

    .
    //중략/
    .
    pstmt.setInt(nIdx++, ((curPage-1)*listSize) + 9);
              pstmt.setInt(nIdx++, (curPage-1)*listSize);
            //주어진 로그내용을 ‘INFO’레벨로 출력합니다.
    getQueryString()으로 ‘?’가 실제데이터로 치환된 query를 출력합니다.
                logger.info("\n======== PGWBoardDAO#selectList ========\n "
                            + ((LoggableStatement)pstmt).getQueryString() +
                           "\n========================================\n");


    .
    //중략/
    .

            } catch (SQLException se) {
                System.out.println("PGWBoardDAO.selectList SQLException ====" + se);
               //Exception은 ‘ERROR’레벨로 출력합니다.
                logger.error("\n==== PGWBoardDAO#selectList Exception ====" , se );
                return null;
            } catch (Exception e) {
                System.out.println("PGWBoardDAO.selectList Exception ====" + e);
                logger.error("\n==== PGWBoardDAO#selectList Exception ====" , e );
                return null;
            } finally {
    .
    //중략/


    - 위 코드에서 INFO 레벨의 로그는 주어진 로그를 출력하고,
    ERROR 레벨의 로그는 발생한 Exception을 로그로 출력합니다.

    로그의 출력메서드는 2가지 형식을 지원합니다.
    logger.fatal(Object message)        logger.fatal(Object message, Throwable t)
    logger.error(Object message)        logger.error(Object message, Throwable t)
    logger.warn(Object message)          logger.warn(Object message, Throwable t)  
    logger.info(Object message)          logger.info(Object message, Throwable t)  
    logger.debug(Object message)         logger.debug(Object message, Throwable t)

    - Throwble 타입의 변수를 parameter로 받는 메서드를 이용하면 원하는 위치에서
    원하는 Exception을 발생시킬 수도 있습니다.

    - 위 코드에서 INFO 레벨의 로그는 주어진 내용를 출력하고,
    ERROR 레벨의 로그는 발생한 Exception을 로그로 출력합니다.


    ② LoggableStatement.java
    - 이 클래스는 query를 로그로 출력할 때 부가적으로 필요한 클래스로 PreparedStatement의 ‘?’를 실제 데이터로 치환해서 출력하는 기능을 합니다.
    이 클래스는 Interface인 PreparedStatement를 구현하는 클래스로 파일이름은 임의로 정하셔도 됩니다.
    클래스내에는 PrepareddStatement의 메서드를 오버라이딩한 메서드와 넘어온 데이터를 ArrayList에 넣어주는 메서드, 그리고 query의 ‘?’를 치환해 리턴해주는 메서드를 구현합니다.

    //PreparedStatement 와 ArrayList를 import 해줍니다.
    //메서드 오버라이딩시에 필요한 클래스도 추가적으로 import 해줍니다.
    import java.sql.PreparedStatement;
    import java.util.ArrayList;

    public class LoggableStatement implements PreparedStatement {

            private ArrayList parameterValues;
     
        private String sqlTemplate;

        private PreparedStatement wrappedStatement;

    //connection.prepareStatement(String sql) 대신에 사용할 생성자 입니다.
    //PreparedStatement Object를 생성, query를 String에 담고 ArrayList를 생성합니다.
        public LoggableStatement(Connection connection, String sql)
                throws SQLException {
                wrappedStatement = connection.prepareStatement(sql);
                sqlTemplate = sql;
                parameterValues = new ArrayList();
        }

    .
    //중략/
    .



    //실제로 필요한 메서드만 오버라이딩 하고, 나머지는 auto generate하시면 됩니다.
    //여기서는 query문 실행관련 메서드와 setInt, setString, setDate, setCharacterStream 을 오버라이딩 했습니다.
            public boolean execute() throws java.sql.SQLException {
                return wrappedStatement.execute();
        }

            public boolean execute(String sql) throws java.sql.SQLException {
                return wrappedStatement.execute(sql);
        }

            public int[] executeBatch() throws java.sql.SQLException {
                return wrappedStatement.executeBatch();
        }

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

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

            public int executeUpdate() throws java.sql.SQLException {
                return wrappedStatement.executeUpdate();
        }

            public int executeUpdate(String sql) throws java.sql.SQLException {
                return wrappedStatement.executeUpdate(sql);
        }

            public java.sql.Connection getConnection() throws java.sql.SQLException {
                return wrappedStatement.getConnection();
        }

           
    public void setCharacterStream(
                int parameterIndex,
                java.io.Reader reader,
                int length)
                throws java.sql.SQLException {
                wrappedStatement.setCharacterStream(parameterIndex, reader, length);
                saveQueryParamValue(parameterIndex, reader);

        }

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

            public void setDate(
                int parameterIndex,
                java.sql.Date x,
                java.util.Calendar cal)
                throws java.sql.SQLException {
                wrappedStatement.setDate(parameterIndex, x, cal);
                saveQueryParamValue(parameterIndex, x);
        }

            public void setInt(int parameterIndex, int x)
                            throws java.sql.SQLException {
                            wrappedStatement.setInt(parameterIndex, x);
                saveQueryParamValue(parameterIndex, new Integer(x));
            }

            public void setString(int parameterIndex, String x)
                            throws java.sql.SQLException {        
                            wrappedStatement.setString(parameterIndex, x);
                saveQueryParamValue(parameterIndex, x);
            }
           
    //넘어온 데이터를 ArrayList에 담아주는 메서드입니다.
            private void saveQueryParamValue(int position, Object obj) {
                    String strValue;
                    if (obj instanceof String || obj instanceof Date) {
                            strValue = "'" + obj + "'";
                    } else {
                            if (obj == null) {
                                  strValue = "null";
                            } else {
                                    strValue = obj.toString();
                            }
                    }
                    while (position >= parameterValues.size()) {
                    parameterValues.add(null);
                    }
                    parameterValues.set(position, strValue);
            }
           
            //instance생성시 String에 넣어둔 query의 ‘?’를 ArrayList에 담긴 실제 데이터로
    //치환해서 리턴해 줍니다.
            public String getQueryString() {
                    //여기서 query를 String에도 담아준 이유는 webLogic의 jdk가 1.3 버전으로
    //StringBuffer의 indexOf(String str) 메서드를 사용할 수 없었기 때문입니다.
    //다른 방법이 있으시면 알려주세요..
                    String sql = sqlTemplate;
                    StringBuffer query = new StringBuffer(sqlTemplate);
                    int idx = 0;
                   
                    if(!parameterValues.isEmpty())
                    {
                            for(int i=1;i < parameterValues.size();i++)
                            {
                                    idx = sql.indexOf("?");
                                    query.replace(idx, idx+1, (String)parameterValues.get(i));
                                    sql = query.toString();
                            }
                            parameterValues = null;
                            return query.toString();
                    }
                    else
                    {
                            parameterValues = null;
                            return query.toString();
                    }
            }
    }


    - 다음은 실제 출력문입니다.

    [2005-11-23 13:50:19,030] INFO at pgw.database.listDAO.modify(listDAO.java:543)   -
    ========== llistDAO#modify#if Customer ==========
    UPDATE ACKLIST
       SET NM_USER = 'aaaaaaaaaa',
           NO_SSN = '2222222222222',
           NO_MINHEADER = '222',
           NO_MINNUMBER = '22222222',
           REASON = '22222222222222222444444444444444444444',
           ID_MODIFY = 'pbadmin',
           DT_MODIFY = SYSDATE
    WHERE SEQ_NUM = '460'
       AND TYPE = '2'


    신고
    Posted by The.민군

    날씨도 덥구 짜증도 나구....곧 휴가철이 오는구나~~~~

     

    Log4jReLoadConfigure.java

    package my.eclipse4j.web.util;

    import java.io.File;

    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;

    import org.apache.log4j.PropertyConfigurator;

    /**
     *
     * @author Administrator
     */
    public class Log4jReLoadConfigure extends HttpServlet {
        /** Initializa el servlet.
         */
        public void init(ServletConfig config) throws ServletException {
            super.init(config);
            String prefix =  getServletContext().getRealPath("/")+"WEB-INF"+File.separatorChar+"classes"+File.separatorChar;

            String file = getInitParameter("log4j-init-file");

            if(file == null || file.length() == 0 ||
                    !(new File(prefix+file)).isFile()){
                    System.err.println("[ERROR]: File not found log4j.properties OR log4j.xml ");
                    throw new ServletException();
            }
           
            // reload time set
            String watch = config.getInitParameter("watch");
            String timeWatch = config.getInitParameter("time-watch");

            if (watch != null && watch.equalsIgnoreCase("true")) {
                if (timeWatch != null) {
                    PropertyConfigurator.configureAndWatch(prefix+file,Long.parseLong(timeWatch));
                } else {
                    PropertyConfigurator.configureAndWatch(prefix+file);
                }
            } else {
                PropertyConfigurator.configure(prefix+file);
            }

        }

        public void destroy() {
            super.destroy();
        }

    }

    web.xml에 다음을 추가 하세요.

      <servlet>
        <servlet-name>log4j-init</servlet-name>
        <servlet-class>
          my.eclipse4j.web.util.Log4jReLoadConfigure
        </servlet-class>
        <init-param>
          <param-name>log4j-init-file</param-name>
          <param-value>log4j.properties</param-value>
        </init-param>
        <init-param>
          <param-name>watch</param-name>
          <param-value>true</param-value>
        </init-param>
        <init-param>
          <param-name>time-watch</param-name>
          <param-value>1000</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>


    time-watch : 현재 1초에 한번씩 변경여부 확인
    신고
    Posted by The.민군