반응형
Robert Eckstein 및 JavaFX Programming Language Reference 필진 작성, 2007년 7월

JavaFX Script 프로그래밍 언어(JavaFX)는 Sun Microsystems, Inc.에서 제공하는 선언적, 정적 형식의 스크립팅 언어이다. Open JavaFX (OpenJFX) 웹 사이트에서 언급한 대로, JavaFX 기술은 Java 기술 API에 직접 호출하는 것을 비롯한 다양한 기능으로 구성되어 있다. JavaFX 스크립트는 정적 형식인 만큼 동일한 코드 구조, 재사용 및 캡슐화 기능을 갖추고 있어(예: 패키지, 클래스, 상속, 별도의 컴파일 및 배포 단위) Java 기술을 사용해 매우 큰 규모의 프로그램을 만들고 유지 관리할 수 있다.

시리즈로 구성된 이 세 기사는 JavaFX 프로그래밍 언어를 처음 사용하는 데 도움이 될 것이다. 이번 시리즈의 1부에서는 JavaFX 프로그래밍 언어를 소개하며, 이미 Java 기술과 스크립팅 언어의 기초를 알고 있는 독자를 대상으로 한다. 시리즈 2부와 3부에서는 JavaFX 기술과 RMI(Remote Method Invocation)JAX-WS(Java API for XML Web Services)와 같은 기술을 이용하여 원격 서버에 연결하는 방법을 소개한다.

JavaFX Pad 응용 프로그램

시스템에 JRE(Java Runtime Environment)가 있는 경우, JavaFX 기술을 가장 손쉽게 시작하는 방법은 Java Web Start 사용 가능 데모 프로그램인 JavaFX Pad를 시작하는 것이다. 응용 프로그램을 시작했으면 그림 1과 같은 화면이 나타나야 한다.

그림 1. Microsoft Windows Vista, JDK 6에서 실행 중인 JavaFX Pad 응용 프로그램
그림 1. Microsoft Windows Vista, JDK 6에서 실행 중인 JavaFX Pad 응용 프로그램

JavaFX Pad는 기본 응용 프로그램이 로드된 상태에서 시작하므로, 이 응용 프로그램이 즉시 실행된다. 그러나 이 기사의 JavaFX 소스 코드를 잘라내어 샘플에 붙여 넣어 수정 사항을 볼 수도 있다. 또한 JavaFX 소스 예제를 로컬 디스크에 저장하고 로드할 수 있다. JavaFX Pad 응용 프로그램은 런타임 시 수행하는 작업을 정확하게 파악하고 진행 과정에서 수정하며 즉시 그 결과를 볼 수 있는 효과적인 방법이다.

JavaFX 기술: 정적 형식 언어

JavaFX 프로그래밍 언어는 정적(static) 형식의 스크립팅 언어이다. 이는 정확하게 어떤 의미인가? 다음을 살펴 본다.

var myVariable = "Hello";

JavaScript 기술에서 볼 수 있는 것과 비슷한 이 선언에서는 myVariable이라는 변수를 만들고 여기에 string 값 Hello를 지정한다. 그러나 변수를 선언한 다음에 string이 아닌 다른 것을 지정해 보겠다.

myVariable = 12345;

이 코드에서는 12345를 따옴표로 묶지 않았기 때문에 이 변수에는 string이 아니라 integer가 지정된다. JavaScript 기술에서는 변수 형식을 동적으로 재지정할 수 있다. 그러나 JavaFX와 같은 정적 형식의 언어는 이를 허용하지 않는다. myVariable은 원래 String 형식으로 선언되었지만, 나중에 코드에서 이 변수를 integer로 재지정하려고 하기 때문이다. JavaFX를 사용할 경우, String으로 선언된 변수는 String으로 유지되어야 한다.

실제로 JavaFX Pad 데모에서 이 두 줄의 코드를 만나면 그림 2와 같이 창 맨 아래에 즉시 오류가 표시된다.

그림 2. JavaFX 기술에서는 정적 형식의 변수에서 형식을 변경할 수 없다.
그림 2. JavaFX 기술에서는 정적 형식의 변수에서 형식을 변경할 수 없다.



JavaFX 기술: 선언적 스크립팅 언어

JavaFX 기술은 선언적 스크립팅 언어이기도 하다. 그러나 선언적이란 정확하게 무엇을 의미하는가? 이 질문에 답하기 위해 OpenJFX 웹 사이트의 이 Hello World 프로그램을 살펴 보겠다.

class HelloWorldModel {
    attribute saying: String;
}

var model = HelloWorldModel {
    saying: "Hello World"
};

var win = Frame {
    title: bind "{model.saying} JavaFX"
    width: 200
    content: TextField {
        value: bind model.saying
    }
    visible: true
};

Java 프로그래밍 언어를 비롯하여 대부분의 컴파일된 언어는 imperative 프로그래밍 언어로 간주된다. 무엇보다도 이는 Java 기술의 main() 메소드와 같은 시작점에 의존한다는 뜻이다. 이 시작점으로부터 다른 클래스나 필드를 인스턴스화거나 변수 또는 프로그램 상태에 따라 자원을 처리한다. 이 예제를 다소 확장하자면, imperative 프로그래밍 언어에서는 런타임 시 "공식을 통해(formulaically)" 실행 경로를 결정한다고 할 수 있다. 이 공식이 각 실행에서 동일한 경로를 만들 수도 있으나, 이 언어에서는 그 실행 경로를 런타임에 결정한다.

그러나 앞서 소개한 Hello World와 같은 JavaFX 프로그램에는 main() 메소드가 없다. 그 대신, 스크립팅 엔진에서는 실행 직전에 전체 프로그램을 읽으므로, 인터프리터가 프로그램을 올바르게 실행하는 데 필요한 모든 단계를 적용할 수 있다. 더 정확하게 말하자면, 스크립팅 엔진에 필요한 것은 실행 시작 전에 선언되며 모든 선언이 주어진 가운데 엔진은 명시된 목표 달성에 필요한 작업을 결정한다.

JavaFX Pad에서 System.out.println() 사용

곧 살펴 보겠지만, JavaFX는 기존의 Java 라이브러리를 호출할 수 있다. 그러나 JavaFX Pad 응용 프로그램에서 System.out.println()를 사용하려면 콘솔 지원을 활성화해야 한다. 그 방법은 다음과 같다.

  • Microsoft Windows XP 또는 Vista를 사용 중이라면 제어판에서 Java 아이콘을 클릭하고 고급 탭을 선택한 다음 Java 콘솔 항목에서 콘솔 표시를 선택한다.

  • Solaris 사용자라면 Preferences 탭에서 Java 아이콘을 클릭하고 Advanced 탭을 선택한 다음 Java Console 항목에서 Show Console을 선택한다. Preferences 탭에 Java 아이콘이 없으면 Java 배포판의 bin 디렉토리에서 ControlPanel 응용 프로그램(또는 jcontrol)을 실행한다.

  • Linux 사용자라면 해당 Java 배포판의 bin 디렉토리에서 ControlPanel(또는 jcontrol)이라는 응용 프로그램을 찾는다. 이를 실행한 다음 Preferences 탭에서 Java 아이콘을 클릭하고 Advanced 탭을 선택한 다음 Java Console 항목에서 Show Console을 선택한다.

  • Mac OS X 사용자라면 /Applications/Utilities/Java/[Java version]/ 아래에서 Java Preferences 응용 프로그램을 연다. 그런 다음 Advanced 탭을 선택하고 Java Console 항목에서 Show Console을 선택한다. 참고: Intel Mac에서 Java Preferences를 변경한 후 Java Web Start가 제대로 시작하지 않을 경우, 홈 디렉토리에서 Library/Caches/Java/deployment.properties 파일을 열고 모든 osarch 변수를 i386에서 ppc로 다시 변경해 본다.

JavaFX Pad에서 Run 메뉴의 Run Automatically 설정을 비활성화하고, JavaFX 응용 프로그램을 수동으로 실행하기 직전에 Java Console을 지운다. 응용 프로그램을 수동으로 실행하려면 JavaFX Pad 응용 프로그램의 Run 메뉴에서 Run 메뉴 항목을 사용한다.

그림 3에서는 Intel 기반 Macintosh에서 콘솔이 열린 상태로 실행 중인 JavaFX Pad를 보여 준다. 그림 4는 OpenSolaris에서 실행 중인 JavaFX Pad이다.

그림 3. Mac OS X, JDK 1.5.0_07의 Java Console에서 실행 중인 JavaFX Pad

그림 3. Mac OS X, JDK 1.5.0_07의 Java Console에서 실행 중인 JavaFX Pad


그림 4. OpenSolaris, JDK 6의 Java Console에서 실행 중인 JavaFX Pad 

그림 4. OpenSolaris, JDK 6의 Java Console에서 실행 중인 JavaFX Pad

마지막으로, JavaFX에서 string 내부에 변수를 포함시킨 경우(주로 Java 프로그래밍 언어에서 System.out.println()을 사용), 알맞은 JavaFX 구문은 다음과 같다.

    import java.lang.System;

    System.out.println("Text {variable} and more text");

이는 Java 언어 구문과 다르다.

    import java.lang.System;

    System.out.println("Text " + variable + " and more text");

JavaFX 기술 알아보기

이 섹션에서는 JavaFX 기술의 기본 사항을 살펴 본다. 이 정보의 대부분은 정식 JavaFX Programming Language Reference에서 직접 발췌한 것이며, 단 이 기사의 작성자들이 Java 프로그래머를 위해 그 내용을 수정했다.

Primitive

JavaFX 프로그래밍 언어는 String, Boolean, NumberInteger의 4개 primitive 형식만 제공한다. Java 프로그래밍 언어와 달리 primitive는 대문자로 시작한다. 표 1에서는 JavaFX 인터프리터 내부의 형식 그리고 이 형식이 매핑되는 Java 객체를 보여 준다.

JavaFX의 Primitive 매핑 JavaFX 기술의 Primitive 대표적인 Java 기술 Primitive 또는 클래스
String     java.lang.String
Boolean Number          java.lang.Number
Integer byte, short, int, long,          java.math.BigInteger

참고: 복잡성을 피하기 위해, Integer는 작은 수와 큰 수를 모두 나타내는데, Java 프로그래머는 short 또는 long과 같이 서로 다른 primitive 형식을 사용하기도 한다. Java 기술의 부동 소수점 숫자(예: float, double)는 Number 형식으로 표현한다.

Java 객체가 이 primitive를 나타내므로 이 형식 각각에서 기존의 Java 메소드를 호출할 수 있다.

var s:String = "Hello";
s.toUpperCase();      // String method that yields "HELLO";
s.substring(1);       // String method that yields "ello";

var n:Number = 1.5;
n.intValue();         // Number method that yields integer 1
(1.5).intValue();     // Number method that yields integer 1

var b:Boolean = true;
b instanceof Boolean; // Boolean method that yields true

그 결과를 보고 싶다면 System.out.println() 문에서 각 표현식을 줄바꿈하고 반드시 맨 위에 java.lang.System을 가져온다. 또한 호환되지 않는 형식이라는 오류가 발생할 경우, 우선 스크립트 끝에 null return을 추가하면 된다. 예제 코드는 다음과 같다.

import java.lang.System;

var s:String = "Hello";
System.out.println(s.toUpperCase());      // String method that yields "HELLO";
System.out.println(s.substring(1));       // String method that yields "ello";

var n:Number = 1.5;
System.out.println(n.intValue());         // Number method that yields integer 1
System.out.println((1.5).intValue());     // Number method that yields integer 1

var b:Boolean = true;
System.out.println(b instanceof Boolean); // Boolean method that yields true

return null;                              // Final node returned for JavaFX Pad display

역시 var 키워드 사용에 주목한다. Java 기술에서 사용되지는 않지만 var는 JavaFX 및 그 밖의 스크립팅 언어에서 새 변수를 선언할 때 쓰인다. JavaFX는 정적 형식 언어이므로, 선언에서 변수의 형식을 지정할 수 있으며 그렇지 않으면 JavaFX 인터프리터는 변수의 쓰임새로부터 변수의 형식을 유추하려고 한다. 예를 들어, 다음 세 가지 모두 JavaFX에서 유효하다.

var s:String;
var s:String = "A New String";
var s = "A New String";

첫 번째와 두 번째 선언에서는 공식적으로 String 형식을 변수에 지정하지만, 세 번째에서는 등호 (=) 부호의 오른쪽에 있는 초기값으로부터 이를 String으로 유추한다. 이를 더 공식적으로 나타낸다면 JavaFX 변수 선언을 이렇게 표현할 수 있다.

var variableName [: typeName] [? | + | *] [= initializer];

물음표, 더하기 부호 및 별표는 카디널리티 연산자라고 부른다. 표현식 언어를 사용한 적이 있으면 이 용어가 익숙할 것이다. 이 세 연산자 중 하나를 사용하여 변수의 카디널리티(구성원 수)를 나타낼 수 있다. 표 2를 참조한다.

표. JavaFX 카디널리티 연산자
연산자
의미
?
선택 사항(예를 들어 null이 될 수 있음)
+
하나 이상
*
0 이상

다음은 그 예제이다.

var nums:Number* = [5, 9, 13];

이 예제에서 선언하는 새 변수, nums의 값은 Number 형식의 인스턴스 0개 이상으로 구성되도록 정의되며, 초기 값은 3개의 숫자, 5, 9 그리고 13이다.

선언에서 typeName, 카디널리티 연산자와 초기값은 선택 사항이므로, 다음은 위의 예제와 동일하다.

var nums = [5, 9, 13];

리터럴

JavaFX 기술에서 리터럴 문자 스트링은 작은 따옴표 또는 큰 따옴표로 지정된다.

var s = 'Hello';
var s = "Hello";

필자가 앞서 언급한 대로, 변수와 심지어 JavaFX 표현식 전체를 중괄호({})로 묶을 수 있다.

var name = 'Joe';
var s = "Hello {name}"; // s = 'Hello Joe'
 

포함된 표현식 자체가 따옴표가 붙은 스트링을 포함할 수 있으며, 이 스트링이 다시 표현식을 포함하기도 한다.

var answer = true;
var s = "The answer is {if answer then "Yes" else "No"}";
    // s = 'The answer is Yes'
 

마지막으로, Java 기술과 달리 큰 따옴표로 묶인 String 리터럴은 새 행을 포함할 수 있다.

var s = "This
         contains
         new lines";
 

어레이 및 리스트 이해

앞서 카디널리티 연산자가 어레이를 생성하는 것을 보았을 것이다. JavaFX에서 어레이는 대괄호와 쉼표로 표시한다. Java와 마찬가지로 JavaFX 어레이의 요소는 모두 형식이 동일해야 한다.

var weekdays = ["Mon","Tue","Wed","Thur","Fri"];
var days = [weekdays, ["Sat","Sun"]];
 

어레이는 객체의 시퀀스를 나타낸다. JavaFX에서는 어레이 자체가 객체는 아니다. 또한 중첩된 어레이를 만드는 표현식(예: 앞의 코드 예제에서 두 번째 변수 days의 초기화)은 자동으로 평면화된다.

days == ["Mon","Tue","Wed","Thur","Fri","Sat","Sun"];
     // returns true
 

System.out.println(days)을 실행하면 어레이의 첫 번째 요소만 표시된다. 나머지 요소를 얻으려면 어레이 색인을 사용한다. 또한 존재하지 않는 색인을 지정할 경우, Java와 같이 ArrayIndexOutOfBoundsException이 발생하지 않고 0을 얻을 뿐이다.

sizeof 연산자를 사용하여 현재 어레이 크기를 구할 수 있다.

var n = sizeof days;     // n = 7
 

또한 간략하게 마침표 2개(..)를 사용하여 요소가 산술적 시리즈를 형성하는 어레이를 나타낼 수 있다. 예를 들어, 다음은 100개 요소의 어레이를 생성한다.

var oneToAHundred = [1..100];
var arraySize = sizeof oneToAHundred;   // size == 100
 

JavaFX 기술은 어레이 사용 시 insertdelete 문도 지원한다. 다음은 값을 어레이에 삽입하는 예제이다.

var x = [1,2,3];
insert 12 into x;                 // yields [1,2,3,12]
insert 10 as first into x;        // yields [10,1,2,3,12]
insert [99,100] as last into x;   // yields [10,1,2,3,12,99,100]
 

into 외에도 다음과 같이 beforeafter 키워드를 사용할 수 있다.

var x = [1,2,3];
insert 10 after x[. == 3];        // yields [1,2,3,10]
insert 12 before x[1];            // yields [1,12,2,3,10]
insert 13 after x[. == 2];        // yields [1, 12, 2, 13, 3, 10];
 

대괄호로 묶인 표현식 중 일부가 이상하게 보이더라도 신경 쓸 필요 없다. 실제로 Xquery-Update(XPath와 유사) 술어이다. 여기서 괄호 안의 마침표는 색인을 나타내지 않지만, 대신 색인의 을 의미한다. JavaFX 기술은 표현식이 true가 될 때까지 어레이의 요소 각각을 테스트한 다음 insert를 적용한다. 예를 들어, 마지막 문은 어레이의 각 값을 거치면서 반복되다가 (어레이의 세 번째 요소인)2와 동일한 어레이 값을 찾아내면 숫자 13을 삽입한다. 여기서 중지한다.

delete 문도 거의 동일하게 실행된다. 대괄호 안의 표현식이 생략될 경우, 어레이 전체가 지워진다.

var x = [1,2,3];
insert 10 into x;          // yields [1,2,3,10]
insert 12 before x[1];     // yields [1,12,2,3,10]
delete x[. == 12];         // yields [1,2,3,10]
delete x[. >= 3];          // yields [1,2]
insert 5 after x[. == 1];  // yields [1,5,2];
insert 13 as first into x; // yields [13, 1, 5, 2];
delete x;                  // clears the array and yields []
 

마지막으로, selectfor each 연산자를 사용하여 어레이에 대해 더 복잡한 쿼리를 수행할 수 있다. 이를 list comprehension이라고 한다. 다음은 select를 사용하는 간단한 예제이다.

var a:Integer* = select n*n from n in [1..10];
    //  yields [1,4,9,16,25,36,49,64,81,100]
 

이는 "[1..10] 어레이의 각 숫자를 루프하면서 로컬 변수 n에 지정한 다음 각 n에 대해 새 요소 n squared를 생성하고 이를 Integers a의 어레이에 추가한다"는 뜻이다.

또한 다음을 사용하여 필터를 추가할 수 있다.

var a:Integer* = select n*n from n in [1..10] where (n%2 == 0);
    //  yields [4,16,36,64,100]
 

이는 정의를 다음으로 변경한다. "[1..10] 어레이의 각 숫자를 루프하면서 그 숫자를 2로 나눈 나머지가 0인 경우에만, 즉 짝수인 경우에만 로컬 변수 n에 지정한다. 그런 다음 결과 값인 n 각각에 대해 새 요소 n squared를 생성하고 이를 Integers a의 어레이에 추가한다."

마지막으로, 여러 개의 리스트를 선택에 추가할 수도 있다.

var a:Integer* = select n*m from n in [1..4], m in [100,200] where (n%2 == 0);
    //  yields [200, 400, 400, 800]
 

실제로 이는 루프 안에 루프가 존재하면서 공식적으로는 Cartesian Product.를 생성한다. 먼저 첫 번째 유효한 n(즉 2)을 얻고 여기에 첫 번째 유효한 m인 100을 곱한다. 그런 다음 다시 2를 다음 번 유효한 m인 200과 곱한다. 그리고 나서 다음 번 유효한 n인 4를 사용하여 유효한 m의 리스트(100과 200)를 반복해 400과 800을 얻는다. 여기서 선택이 종료하며, 최종 어레이(어레이 a)가 얻어진다.

동일한 내용을 foreach 연산자를 사용하여 표현할 수 있다.

var a:Integer* =
    foreach(n in [1..4], m in [100,200] where (n%2 == 0) )
            n*m;      // also yields [200, 400, 400, 800]
 
형식 지정

format as 연산자는 표 3에서 보여주는 것처럼 몇 가지 형식 지정 지시문을 지원한다.

JavaFX 형식 지정 지시문 지시문사용된 형식의 클래스%로 시작하는 형식 지정 지시문java.util.Formatter표현식이 Numberjava.text.DecimalFormat표현식이 java.util.Date java.text.SimpleDateFormat

다음은 몇 가지 예제이다.

import java.util.Date;

100.896 format as <<%f>>; // yields '100.896000'
31.intValue() format as <<%02X>>;
    // yields '1F'
var d = new Date();
d format as <<yyyy-MM-dd'T'HH:mm:ss.SSSZ>>;
    // yields '2005-10-31T08:04:31.323-0800'
0.00123 format as <<00.###E0>>;
    // yields '12.3E-4'

이 예제에서는 프랑스어 따옴표, 즉 guillemets(<< >>)가 사용되었다. JavaFX 기술에서는 공백을 비롯하여 프랑스어 따옴표로 묶인 문자 시퀀스를 모두 식별자로 취급한다. 따라서 JavaFX 키워드 또는 평소에는 적합하지 않은 식별자를 클래스, 변수, 함수 또는 속성 이름으로 사용할 수 있다. 다음은 그 예제이다.

var <<while>> = 100;

이 기능에서는 이 예제와 같이 JavaFX 키워드와 이름이 같은 Java 메소드를 호출할 수 있다.

import javax.swing.JTextArea;

var textArea = new JTextArea();
textArea.<<insert>>("Hello", 0);
클래스 선언

클래스를 지정하는 JavaFX 구문은 class 키워드 다음에 클래스 이름, extends 키워드(옵션) 그리고 쉼표로 구분된 기본 클래스 이름의 목록이다. Java와 달리 JavaFX 기술에서는 둘 이상의 클래스를 확장할 수 있다. 그 다음에 여는 중괄호, 속성, 함수 및 연산의 목록(각각 세미콜론(;)으로 끝남)과 닫는 중괄호가 이어진다. 다음은 그 예제이다.

class Person {

    attribute name: String;
    attribute parent: Person;
    attribute children: Person*;

    function getFamilyIncome(): Number;
    function getNumberOfChildren(): Number;

    operation marry(spouse: Person): Boolean;
}

속성, 함수 및 연산

세 가지 형식의 클래스 구성원 선언을 자세히 살펴 보도록 한다. 속성(Attribute)attribute 키워드 다음에 속성의 이름, 콜론(:), 속성의 형식, 카디널리티 사양(옵션) 및 역 절(inverse clause)(옵션)을 사용하여 선언한다. 카디널리티를 사용하는 경우, 종종 그 속성을 다중 값 속성(multivalued attribute)이라고 한다.

더 공식적으로 설명하자면, 속성이 다음 구문을 사용한다.

attribute AttributeName [: AttributeType] [? | + | *] [inverse ClassName.InverseAttributeName];
>

맨 끝에 오는 inverse 절 옵션은 다른 속성과의 양방향 관계를 지정한다. inverse 절이 존재하는 경우, JavaFX 기술은 inverse 절에 지정된 속성이 수정될 때마다 그 속성을 자동으로 업데이트한다(속성의 업데이트 및 카디널리티 종류에 따라 insert, delete 또는 replace 사용).

함수(Function)는 JavaFX 프로그래밍 언어의 순수 기능 하위 집합을 나타낸다. 즉 함수의 본문은 일련의 변수 선언과 return 문만 포함할 수 있다. 그 정도면 속성 접근자(getter) 및 간단한 수학적 절차 구현에 충분하다. 몇 가지 함수의 예제를 소개한다.

function z(a,b) {
    var x = a + b;
    var y = a - b;
    return sq(x) / sq (y);
}

function sq(n) {return n * n; }

JavaFX에서 연산(Operation)operation 키워드를 사용하여 선언한다. 함수와 달리 연산은 조건문, 루핑문, trycatch 문을 비롯하여 개수 제한 없이 문을 포함할 수 있다. 따라서 Java 기술의 클래스 메소드와 더 비슷하다. 본문을 선언할 때 연산의 이름이 주어지고 괄호로 묶인 입력 변수, 콜론 그리고 반환 변수 형식이 이어진다. 다음은 그 예제이다.

operation substring(s:String, n:Number): String {
    try {
        return s.substring(n);
    } catch (e:StringIndexOutOfBoundsException) {
        throw "sorry, index out of bounds";
    }
}

선언된 속성 초기화

함수 및 프로시저와 마찬가지로, 속성의 초기 값은 클래스 정의의 외부에서 선언한다. 이 초기값은 새로 생성된 객체의 컨텍스트에 따라 클래스 선언에서 속성이 지정된 순서대로 평가된다.

import java.lang.System;

class X {
    attribute a: Number;
    attribute b: Number;
}

attribute X.a = 10;
attribute X.b = -1;

var x = new X();
System.out.println(x.a); // prints 10
System.out.println(x.b); // prints -1
>

JavaFX 객체는 선언적 구문을 사용하여 초기화할 수도 있다. 이 구문은 클래스 이름, 중괄호로 구분된 속성 초기값 목록의 순서로 구성된다. 각 초기값은 속성 이름, 콜론 그리고 그 값을 정의하는 표현식의 순서로 구성된다. new 키워드는 생략된다. 다음은 동일한 예제이다.

var myXClass = X {
    a: 10
    b: -1
};

선언적 구문은 JavaFX 기술에서 자주 사용된다. 그러나 Java 객체 할당 구문도 지원된다. Java 클래스의 경우, Java 기술에서처럼 클래스의 구성자에 인수를 전달하거나 선언적 구문을 사용할 수 있다.

import java.util.Date;
import java.lang.System;

var date1 = new Date(95, 4, 23);    // call a Java constructor
var date2 = Date {  // create the same date as an object literal
    month: 4
    date: 23
    year: 95
};

System.out.println(date1 == date2);   // prints true

함수 및 연산 정의

Java 메소드와 달리 모든 구성원 함수 및 연산의 본문은 클래스 선언의 외부에서 정의된다. 이 구문이 맨 처음에는 Java 프로그래머에게 약간 이상하게 보일 수 있으나 비교적 간단하게 이해할 수 있다. JavaFX 기술을 사용하면 연산 이름의 함수 앞에 그 함수가 속한 클래스 및 마침표가 온다. 반환 값은 함수 또는 연산 이름 뒤에 나열할 수 있으며, 앞에 콜론이 온다. 매개 변수는 서명 괄호에 포함시킬 수 있다. 이들은 쉼표로 구분되며, 앞서 연산에 대해 설명했던 name:type 구문을 따른다. 다음은 그 예제이다.

operation Person.marry(spouse: Person): Boolean {
    //  Body of operation
}

클래스 선언 내부의 연산 및 함수 선언에서는 괄호와 반환 값이 반드시 필요하다. 그러나 외부 정의에서는 생략 가능하다. 따라서 다음과 같이 쉽게 표현할 수 있다.

operation Person.marry() {
    //  Body of operation
}

트리거

Java 기술과 달리, JavaFX 클래스는 구성자가 없으며 JavaFX 속성은 일반적으로 "setter" 메소드를 갖지 않는다. 그 대신 JavaFX 기술에서는 데이터 수정 이벤트를 처리할 수 있도록 SQL과 비슷한 트리거를 제공한다. 이 트리거에서는 trigger 키워드를 사용한다.

객체 생성 트리거

생성 트리거를 지정하는 방법으로 새로 생성된 객체의 컨텍스트에서 작업을 트리거할 수 있다.

import java.lang.System;

class X {
     attribute nums: Number*;
}

trigger on new X {
     insert [3,4] into this.nums;
}

var x = new X();
System.out.println(x.nums == [3,4]); // prints true

이 예제에서는 X 클래스의 새 인스턴스가 만들어질 때마다 실행될 트리거를 정의한다. 여기서는 두 개의 숫자를 nums 속성에 삽입한다. 트리거의 컨텍스트에서 현재 객체를 가리킬 때 this 키워드를 사용한다.

Insert 트리거

또한 다중 값 속성에 요소가 삽입될 때마다 어떤 작업을 트리거할 수 있다.

import java.lang.System;

class X {
     attribute nums: Number*;
}

trigger on insert num into X.nums {
     System.out.println("just inserted {num} into X.nums at position {indexof num}");
}

var x = new X();
insert 12 into x.nums;
    // prints just inserted 12 into X.nums at position 0
insert 13 into x.nums;
    // prints just inserted 13 into X.nums at position 1

Delete 트리거

동일한 방법으로 다중 값 속성에서 요소가 삭제될 때마다 어떤 작업을 트리거할 수 있다.

import java.lang.System;

class X {
     attribute nums: Number*;
}

trigger on delete num from X.nums {
     System.out.println("just deleted {num} from X.nums at position {indexof num}");
}

var x = X {
     nums: [12, 13]
};

delete x.nums[1];
    // prints just deleted 13 from X.nums at position 1
delete x.nums[0];
    // prints just deleted 12 from X.nums at position 0

Replace 트리거

마지막으로, 속성의 값이 대체될 때마다 작업을 수행할 수 있다. 다음 예제에서 oldValuenewValue는 대체되는 요소의 이전 값과 현재 값을 참조하는 임의의 변수 이름이다. 다른 변수 이름을 자유롭게 선택할 수 있다.

import java.lang.System;

class X {
     attribute nums: Number*;
     attribute num: Number?;
}

trigger on X.nums[oldValue] = newValue {
     System.out.println("X.nums: replaced {oldValue} with {newValue} at position {indexof newValue}");
}

trigger on X.num[oldValue] = newValue {
     System.out.println("X.num: replaced {oldValue} with {newValue}");
}

var x = X {
     nums: [12, 13]
     num: 100
};

x.nums[1] = 5;
    // prints replaced 13 with 5 at position 1 in X.nums
x.num = 3;
    // prints X.num: replaced 100 with 3
x.num = null;
    // prints X.num: replaced 3 with null

JavaFX 기술에서는 Java 기술의 해당 문과 비슷하지만 똑같지 않은 몇몇 문을 지원한다. 이 섹션에서는 그 차이점을 간략하게 소개한다.

If

JavaFX if 문은 중괄호가 필요하다는 점을 제외하고 Java 기술의 문과 같다.

if (condition1) {
    System.out.println("Condition 1");
} else if (condition2) {
    System.out.println("Condition2");
} else {
    System.out.println("not Condition 1 or Condition 2");
}

While

JavaFX while 문 역시 본문을 중괄호로 묶어야 한다.

var i = 0;
while (i < 10) {
    if (i > 5) {
       break;
    }
    System.out.println("i = {i}");
    i += 1;
}

Try, CatchThrow

JavaFX trycatch 문은 JavaFX 변수 선언 구문을 제외하고 Java 기술의 문과 같다. JavaFX 기술에서는 java.lang.Throwable을 확장하는 객체 외에 어떤 객체도 throw 및 catch할 수 있다.

try {
   throw "Hello";
} catch (s:String) {
   System.out.println("caught a String: {s}");
} catch (any) {
   System.out.println("caught something not a String: {any}");
} finally {
   System.out.println("finally...");
}

For

JavaFX for 문의 헤더에서는 앞서 설명한 foreach list-comprehension 연산자와 동일한 구문을 사용한다. 문의 본문은 list comprehension에서 생성한 각 요소에 대해 실행된다. 다음은 그 예제이다.

for (i in [0..10]) {
     System.out.println("i = {i}");
}

// print only the even numbers using a filter
for (i in [0..10] where (i%2 == 0) ) {
     System.out.println("i = {i}");
}

// print only the odd numbers using a range expression
for (i in [1,3..10]) {
     System.out.println("i = {i}");
}

// print the cartesian product
for (i in [0..10], j in [0..10]) {
     System.out.println(i);
     System.out.println(j);
}

Return

JavaFX return 문은 Java 기술의 문과 동일하다.

operation add(x, y) {
    return x + y;
}

BreakContinue

JavaFX breakcontinue 문은 레이블이 지원되지 않는다는 점을 제외하고 Java 기술의 문과 같다. Java 프로그래밍에서처럼 breakcontinuewhile 또는 for 문의 본문에 나타나야 한다.

operation foo() {
   for (i in [0..10]) {
       if (i > 5) {
           break;
       }
       if (i % 2 == 0) {
           continue;
       }
       System.out.println(i);
   }
}

operation bar() {
    var i = 0;
    while (i < 10) {
        if (i > 5) {
            break;
        }
        if (i % 2 == 0) {
            continue;
        }
        System.out.println(i);
        i += 1;
    }
}

DoDo Later

JavaFX do 문에서는 JavaFX 코드의 블록을 실행할 수 있다. 그러나 do 본문은 항상 백그라운드 스레드에서 실행된다. 일반적으로 JavaFX 코드는 AWT EDT(Event Dispatch Thread)에서 실행된다. do 문의 본문에 포함된 코드만 다른 스레드에서 실행 가능하다. 다음 예제를 살펴 본다.

import java.net.URL;
import java.lang.StringBuffer;
import java.lang.System;
import java.io.InputStreamReader;
import java.io.BufferedReader;

// in the AWT Event Dispatch Thread (EDT)
var result = new StringBuffer();

do {
    // now in a background thread
     var url = new URL("http://www.foo.com/abc.xml");
     var is = url.openStream();
     var reader = new BufferedReader(new InputStreamReader(is));
     var line;
     while (true) {
          line = reader.readLine();
          if (line == null) {
               break;
          }
          result.append(line);
          result.append("\n");
     }
}

// now back in the EDT
System.out.println("result = {result}");

do 문의 본문이 실행되는 중에 EDT 블록에서 실행되는 코드이다. 그러나 백그라운드 스레드가 완료될 때까지 기다리는 동안 스택에서 새로운 이벤트 디스패치 루프가 생성된다. 그 결과, do 문이 실행되는 동안 GUI(graphical user interface) 이벤트는 계속 처리된다.

do 문은 do later라는 두 번째 형식을 갖는데, 이 형식은 java.awt.EventQueue.invokeLater()의 기능과 비슷하게, 백그라운드 스레드에서 동시 실행이 아니라 EDT에서 본문이 비동기식으로 실행될 수 있게 한다. 다음은 그 예제이다.

import java.lang.System;
var saying1 = "Hello World!";
var saying2 = "Goodbye Cruel World!";
do later {
     System.out.println(saying1);
}
System.out.println(saying2);

이 코드를 실행하면 다음과 같은 출력이 생성된다.

Goodbye Cruel World!
Hello World!

증분 평가

증분 평가(incremental evaluation)는 JavaFX 기술에서 가장 강력한 기능 중 하나이다. 프로그래머가 복잡한 동적 GUI를 선언적으로 정의할 수 있다. JavaFX 기술에서는 bind 연산자를 사용하면 속성 초기값을 증분식으로 평가할 수 있다. 바인딩된 이 속성은 스프레드시트의 셀처럼 작동하면서 리터럴 값 대신 공식을 포함한다. 초기값 표현식의 오른쪽에서 참조하는 객체가 변경될 때마다 속성의 값인 왼쪽이 자동으로 업데이트된다.

다음은 간단한 예제이다.

import java.lang.System;

class X {
    attribute a: Number;
    attribute b: Number;
}

var x1 = X {
    a: 1
    b: 2
};

var x2 = X {
    a: x1.a           // nonincremental
    b: bind x1.b      // incremental
};

System.out.println(x2.a); // prints 1
System.out.println(x2.b); // prints 2

x1.a = 5;
x1.b = 5;

System.out.println(x2.a); // prints 1
System.out.println(x2.b); // prints 5

이 예제에서 x2b 속성은 x1b 속성에 바인딩된다. 즉 x1b 속성이 업데이트될 때마다 x2b 속성도 업데이트된다.

함수의 본문은 bind 연산자가 없더라도 항상 증분식으로 평가되지만, 연산의 본문은 그렇지 않다. 함수와 달리, 연산 내부의 로컬 변수가 변경되더라도 증분 평가가 트리거되지 않는다. 표현식 앞에 명시적으로 bind라는 접두어가 붙지 않는 한 연산의 본문 내부에서는 증분 평가가 수행되지 않는다.

느린 증분 평가(lazy incremental evaluation)도 사용하도록 예제를 수정할 수 있다. 여기서는 attribute 값이 맨 처음 액세스될 때까지 바인딩이 적용되지 않는다.

import java.lang.System;

class X {
    attribute a: Number;
}

var x1 = X {
    a: 1
};

var x2 = X {
    a: bind lazy x1.a
    // no value is assigned yet
};

System.out.println(x2.a);
// Now the value is accessed, so x2.a is set to 1

느린 증분 평가 기능은 재귀적 데이터 구조(예: 트리, 그래프)를 처리할 때 자주 쓰인다.

결론

이 기사에서는 JavaFX 플랫폼을 간단하게 소개했다. 2부와 3부에서는 JavaFX 기술 기반의 GUI를 사용하여 클라이언트 서버 통신을 처리하는 방법을 다룬다.

자세한 정보
반응형
 

소개

JavaFX는 SUN에서 새로 발표한 스크립트 언어이다. JavaFX는 다음과 같은 특징들을 가지고 있다.

  1. 서술형 언어
  2. 정적 자료형
  3. 함수형 언어
  4. 리스트 내포(list comprehension)
  5. 점증식 의존성 기반 평가(incremental dependency-based evaluation)

개발환경 꾸미기

JavaFX를 간단히 접해보기 위한 용도로는 JavaFXPad가 괜찮다.

IDE에서 JavaFX를 사용하는 방법은 아래 링크들을 참조

  1. eclipse에서 개발환경을 꾸미는 방법
  2. NetBeans에서 개발환경을 꾸미는 방법

튜토리얼

  1. Hello World JavaFX 시작하기
  2. 클래스, 객체 그리고 bind

참고 자료

아직까지 JavaFX에 대한 참고 자료는 많지 않지만 JavaFX 공식 커뮤니티의 튜토리얼과 레퍼런스만 봐도 충분하다. 나머지는 Java 클래스를 가져다 쓰는 것 뿐이기 때문에 Java 문서들을 참조하면 된다.

JavaFX 홈
JavaFX 공식 커뮤니티

< 출처: http://functional.or.kr/javafx/tutorial/intro >

반응형
Enumeration : 자바 초기버젼에서 개발된 것으로, HashTable과 Vector에서 사용가능
Iterator : jdk 1.2버젼에서 개발된 것으로, Collection 인터페이스를 구현한 모든 클래스에서 사용가능.

API를 확인해볼까요??
Vector와 HashTable에 있는 메소드입니다.
사용자 삽입 이미지
(HashTable에는 Enumeration 타입의 key()메소드도 존재합니다.)

하지만, ArrayList, LinkedList, HashMap에서는 찾아볼 수 없었습니다.
iterator는 HashTable이든 HashMap이든, Vector든 LinkedList든,
모두 쓸 수 있다는 것이죠.

Iterator에 대해서는 저번에 알아봤으니까.
이번에는 Enumeration의 API를 간단하게 뒤져봐야겠네요.

hasMoreElements

boolean hasMoreElements()
Tests if this enumeration contains more elements.
Returns:
true if and only if this enumeration object contains at least one more element to provide; false otherwise
Iterator의 hasNext()와 똑같은 일을 하는군요!!

nextElement

E nextElement()
Returns the next element of this enumeration if this enumeration object has at least one more element to provide.
Returns:
the next element of this enumeration.
Throws:
NoSuchElementException - if no more elements exist.
다음 elements가 존재하지 않으면, Exception이 발생하네요 ㅋㅋ
이것 또한 Iterator의 next()와 같아 보이는군요..ㅋㅋ

Vector의 요소들을 출력하는 예제입니다.
for (Enumeration e = v.elements() ; e.hasMoreElements() ;) {
System.out.println(e.nextElement());

}
자 그럼, 본격적으로 차이점에 대해서 알아보도록 합시다.

Snap Shot
Enumeration과 Iterator의 가장 큰 차이점은 바로 Snap Shot입니다.
Enumeration은 Snap Shot을 지원합니다. 여기서 'fail-fast
'라는 개념이 나오는데요.
Iterator는 fail-fast 방식이라고 합니다.
그럼 과연 이것이 무엇일까요?? 좋은 아티클이 있어서 긁어왔습니다...-ㅅ-;;

Fail-Fast

자바 2 이전 버전에서 사용되던 Vector, Hashtable의 뷰 객체인 Enumeration은 fail-fast 방식이 아니었으나, 자바 2의 콜렉션 프레임워크에서 콜렉션 뷰인 Iterator, ListIterator 객체는 fail-fast 방식이라는 점이다.

콜렉션 뷰는 콜렉션 객체에 저장된 객체들에 대한 순차적 접근을 제공한다. 그러나, 뷰 객체인 Enumeration 또는 Iterator 객체를 얻고 나서 순차적 접근이 끝나기 전에 뷰 객체를 얻은 하부 콜렉션 객체에 변경이 일어날 경우, 순차적 접근에 실패하게 된다. 여기서 변경이라는 것은 콜렉션에 객체가 추가되거나 제거되는 것과 같이 콜렉션 구조의 변경이 일어나는 경우를 말한다.

이런 상황은 멀티쓰레드 구조와 이벤트 구동 모델에서 일어날 수 있으며, 개발자가 혼자 테스트할 경우 발견하기 어려운 문제이다. 따라서 정확한 이해와 예상이 필요하며, 이에 대한 대처 방안을 마련해야 한다.

하부 콜렉션 객체에 변경이 일어나 순차적 접근에 실패하면 Enumeration 객체는 실패를 무시하고 순차적 접근을 끝까지 제공한다. Iterator 객체는 하부 콜렉션 객체에 변경이 일어나 순차적 접근에 실패하면 ConcurrentModificationException 예외를 발생한다. 이처럼 순차적 접근에 실패하면 예외를 발생하도록 되어 있는 방식을 fail-fast라고 한다.

Iterator는 fail-fast 방식으로 하부 콜렉션에 변경이 발생했을 경우, 신속하고 결함이 없는 상태를 만들기 위해 Iterator의 탐색을 실패한 것으로 하여 예외를 발생하며, 이렇게 함으로써 안전하지 않을지도 모르는 행위를 수행하는 위험을 막는다. 왜냐하면 이러한 위험은 실행 중 불특정한 시간에 멋대로 결정되지 않은 행위를 할 가능성이 있기 때문에 안전하지 않다고 할 수 있기 때문이다.


멋집니다..-ㅅ-; 더이상 설명할 것이 없을 것 같은데요.
흐음.. 일단 정리를 해보면, iterator는 일종의 pointer가 있습니다.
예전에 LinkedList 개념을 그림으로 그려보았는데요.
그거랑 매우 비슷합니다.

사용자 삽입 이미지

포인터가 Array의 자기자신과 다음 객체를 가르키는 것이지요.
그래서 위에 아티클에 써져있는 것처럼 삭제나 수정시 참조무결성 원칙에 어긋나게되어서
에러가 발생하게 되는것입니다.

아티클에 SnapShot에 대한 좋은 내용도 있더군요.

스냅샷

이처럼 콜렉션 뷰 객체인 Iterator와 ListIterator 객체가 fail-fast 방식인 이유는 스냅샷에 대한 보장을 포함하고 있지 않기 때문이다. 즉, 콜렉션의 뷰인 Iterator 객체를 얻었을 때 그 순간의 상태를 따로 생성하지 않기 때문에, Iterator 객체에서 순차적으로 하부 콜렉션 객체를 접근할 때, 콜렉션 객체의 변경에 영향을 받게 된다.

따라서, 콜렉션 뷰인 Iterator를 통하여 순차적 접근을 행하는 도중에 하부 콜렉션 객체에 변경이 일어날 가능성이 있는 경우 스냅샷을 명시적으로 생성하는 것이 좋다. ArrayList를 이용한 스냅샷을 생성하는 간단한 방법은 다음과 같다.

public Iterator snapshotIterator(Collection collection) {
return new ArrayList(collection).iterator();
}

이렇게 스냅샷을 이용하면 스냅샷이 생성된 순간의 상태에 대하여 Iterator를 통한
콜렉션의 뷰를 생성하게 된다. 하부 콜렉션의 변경이 일어나도 스냅샷에는 반영되지
않으며 Iterator는 실패없이 실행된다.

콜렉션 계열 중에 ArrayList를 사용하는 이유는 다른 콜렉션 계열 클래스들보다 생성과순차적 접근이 빠르기 때문이다. ArrayList가 삽입과 삭제에 불리한 단점이 있으나
이 경우에는 삽입과 삭제가 없으므로 고려되지 않는다.


조금 더 쉽게 정리를 해보자면,
Enumeration은 객체를 복사해서 저장합니다. 그래서 삭제나 수정시 전체 콜렉션에는
큰 타격을 주지 않는다는 것이죠.

Synchronization

제가 원래 알고 있던 지식은 "Enumeration은 동기화를 지원한다." 였는데,
어디서 잘 못 주워들었나봅니다. -ㅛ-
아티클에 <<콜렉션 프레임워크의 클래스들은 동기화를 보장하고 있지 않다.>>
라고 써져있더라구요. 그래서 찾아보았습니다. 역시 API에서 Enumeration부분을 봐도,
동기화에 대해서 정의된 부분은 찾을 수 없더라구요.
하지만,  Vector와 ArrayList의 차이점을 공부한 글에서도 알 수 있을 듯이
Vector는 동기화가 아주 넘쳐납니다-ㅅ-; 그러한 사실은 이클립스에서 Vector.class를
가보면, synchronized로 도배가 되어있는 것을 볼 수 있답니다.

아티클에는,
<<멀티쓰레드에서 콜렉션 구현 객체에 동시에 추가, 삭제를 위한 접근이 행해진다면, 그러한 행위가 발생하는 부분에 동기화를 명시적으로 해주어야 한다.>>
라고 써져있더군요. 방법까지 친절하게 명시해주고 있었습니다.
요렇게-ㅅ-;

Collection c = Collections.synchronizedCollection(myCollection);

결론적으로, Enumeration과 Iterator 사이에서 뚜렷하게 차이를 보이는 점은
 "스냅샷을 지원하느냐 하지 않느냐" 라는 것 같습니다.
아티클에서는 "Fail-fast Iterator에 대한 멀티쓰레드 무결성 해결방법" 이라는 제목아래 글을 써내려가고 있는데요. 스냅샷과 동기화가 Iterator의 멀티쓰레드 무결성 해결방법으로 제시되었고, 그와 함께 예시 소스코드도 보여주고 있습니다. 그러니까 Iterator가 스냅샷을 지원하지 않아도, 스냅샷을 명시적으로 생성할 수 있다는 것입니다. 이런건 한번 소스코드를 작성해봐야 확실히 알 수 있겠네요. 일단 이번 포스팅은 여기서 마치고, 소스코딩을 야심차게 시작해보도록 하겠습니다.-ㅛ-; 아자 +ㅅ+ ㅋ
< 출처: http://happystory.tistory.com >
반응형
Vector or ArrayList -- which is better and why?

Sometimes Vector is better; sometimes ArrayList is better; sometimes you don't want to use either. I hope you weren't looking for an easy answer because the answer depends upon what you are doing. There are four factors to consider:

Vector가 더 나을 때도 있고, ArrayList가 더 나을 때도 있다. 또는, 둘 다 사용하길 원하지 않을 수도 있다. 나는 당신이 쉬운 답을 찾으려 하지 않기를 바란다. 왜냐하면 답은 당신이 하는 방식에 따라 달라질 테니까 말이다. 고려해야 할 다음 4가지 요인이 있다.

API
In The Java Programming Language (Addison-Wesley, June 2000) Ken Arnold, James Gosling, and David Holmes describe the Vector as an analog to the ArrayList. So, from an API perspective, the two classes are very similar. However, there are still some major differences between the two classes.

The Java Programming Language 에 Ken Arnold, James Gosling, and David Holmes는 Vector를 ArrayList의 상사형(서로 형이 같은)이라고 묘사한다. 그래서 API를 보면 두 개의 클래스는 매우 유사하다. 하지만 여전히 두 클래스 사이에는 중요한 차이점이 존재한다.

Synchronization
Vectors are synchronized. Any method that touches the Vector's contents is thread safe. ArrayList, on the other hand, is unsynchronized, making them, therefore, not thread safe. With that difference in mind, using synchronization will incur a performance hit. So if you don't need a thread-safe collection, use the ArrayList. Why pay the price of synchronization unnecessarily?

Vector는 동기화 되어있다. Vector의 내용에 접근하는 어떤 메소드는 thread safe1 하다. 반면에ArrayList는 동기화 되어있지 않다. 그러므로 ArrayList로 만들어진 것은 thread safe하지 않다. thread safe가 필요하지 않는데도 동기화를 사용하면, performance hit2 을 초래할 것이다. 만약 당신이 thread-safe 컬렉션을 필요로 하지 않는다면, ArrayList를 사용하라. 왜 불필요하게 synchronization의 비용을 지불하려 하는가?

Data growth
Internally, both the ArrayList and Vector hold onto their contents using an Array. You need to keep this fact in mind while using either in your programs. When you insert an element into an ArrayList or a Vector, the object will need to expand its internal array if it runs out of room. A Vector defaults to doubling the size of its array, while the ArrayList increases its array size by 50 percent. Depending on how you use these classes, you could end up taking a large performance hit while adding new elements. It's always best to set the object's initial capacity to the largest capacity that your program will need. By carefully setting the capacity, you can avoid paying the penalty needed to resize the internal array later. If you don't know how much data you'll have, but you do know the rate at which it grows, Vector does possess a slight advantage since you can set the increment value.

내부적으로 ArrayList와 Vector둘 다 Array를 사용하면서 그들의 컨텐츠를 유지한다. 당신은 당신의 프로그램에서 둘 중에 하나를 사용하는 동안에 이 사실을 명심할 필요가 있다. ArrayList 또는 Vector의 요소를 삽입할 때, 만약 그 용량을 다 써버린다면, object는 자신의 내부array를 확장시킬 필요가 있다. Vector는 자신의 array 크기의 배수만큼 초기화 한다. 반면에 ArrayList는 자신의 array 크기의 50%만큼 씩 증가시킨다. 당신이 이러한 클래스들을 사용하는데 의존한다면, 당신은 새로운 요소들을 추가하는 동안에 결국 대량의 performance hit을 초래할 것이다. 당신의 프로그램이 필요로 하는 최대의 용량만큼 객체의 초기화 용량을 정하는 것이 최선이다. 신중하게 용량을 셋팅해라. 그러면, 당신은 후에 내부 array를 resize하는데 필요한 요금 지불을 피할 있다. 만약 당신이 얼마나 데이터를 가지게 될지는 모른지만, 그것이 증가하는 비율을 안다면 , Vector는 당신이 증가값을 정할 수 있기 때문에 근소한 이점을 가진다.(아래 글 참조)

Usage patterns
Both the ArrayList and Vector are good for retrieving elements from a specific position in the container or for adding and removing elements from the end of the container. All of these operations can be performed in constant time -- O(1). However, adding and removing elements from any other position proves more expensive -- linear to be exact: O(n-i), where n is the number of elements and i is the index of the element added or removed. These operations are more expensive because you have to shift all elements at index i and higher over by one element. So what does this all mean?

ArrayList와 Vector 둘 다 컨테이너의 특정 위치에서 요소들을 되찾거나, 컨테이너의 끝에서 요소들을 더하거나, 삭제할때 유용하다. 이러한 모든 기능은 constant time-- O(1)(맨 아래 글을 참조하세요.)에서 수행될 수 있다. 하지만, 어떤 다른 자리에서 요소들을 더하거나 제거하는 것은 더 많은 비용을 발생시킨다. 정확하게 직선위에서..-ㅅ-?? n은 요소들의 수이고, i는 더해지거나 제거되는 요소의 인덱스다. 당신이 하나의 요소에 때문에 모든 요소들을 index i에 그 이상의 값으로 옮겨야만 하기때문에 이러한 operation들은 더 많은 비용이 든다. 그렇다면 이러한 것은 무엇을 의미하는가?

It means that if you want to index elements or add and remove elements at the end of the array, use either a Vector or an ArrayList. If you want to do anything else to the contents, go find yourself another container class. For example, the LinkedList can add or remove an element at any position in constant time -- O(1). However, indexing an element is a bit slower -- O(i) where i is the index of the element. Traversing an ArrayList is also easier since you can simply use an index instead of having to create an iterator. The LinkedList also creates an internal object for each element inserted. So you have to be aware of the extra garbage being created.

그것은 만약 당신이 요소들을 검색하거나, Array의 끝에서 요소들을 더하거나 제거하기를 원한다면 Vector나 ArrayList 둘 중에 하나를 사용하라는 것을 의미한다. 만약 당신이 그 밖의 무엇인가를 하길 원한다면, 스스로 또다른 콘테이너 클래스를 찾아라. 예를들어, LikedList는 constant time -- O(1) 에서 어디서든지 요소를 더하거나 제거할 수 있다. 하지만, 요소를 찾는 것은 조금 느리다. ArrayList을 왔다갔다 하는 것은 또한 당신이 iterator을 만들어야만 하는 대신에 인덱스를 더욱 간편하게 사용할 수 있기 때문에 더 쉽다. LinkedList는 또한 삽인된 각각의 요소에 대한 내부 객체를 만든다. 그래서 당신은 만들어지는 여분의 garbage에 대해서 알아야만 한다.

Finally, in "PRAXIS 41" from Practical Java (Addison-Wesley, Feb. 2000) Peter Haggar suggests that you use a plain old array in place of either Vector or ArrayList -- especially for performance-critical code. By using an array you can avoid synchronization, extra method calls, and suboptimal resizing. You just pay the cost of extra development time.

마지막으로 Practical Java "PRAXIS 41"에 Peter Haggar은 당신이 Vector 나 ArrayList 둘 중의 하나 대신에 명백한 old array를 사용할 것을 제안한다. -특히 비판적인 코드에서??? array를 사용함으로써, 당신은 동기화, 여분의 메소드를 부르는 것. 그리고 차선의 resizing을 피할 수 있다. 당신은 단지 여분의 개발시간비용만을 지불할 것이다.

어허...ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 해석하는데 무지 오래걸렸습니다..-ㅅ-
하지만 해놓고보니.. 뿌듯하군요.
그러니까, ArrayList와 Vector의 가장 큰 차이점으로 꼽을 수 있는 것은 "동기화"입니다.
하지만, 여러 문서를 찾아보니 ArrayList가 동기화되는 Vector보다 퍼포먼스가 좋다는
이야기가 많더라구요. 즉, 적절한 곳에 ArrayList를 동기화 해서 사용하는게 더 좋다는 의견이
많았습니다. 저는 아직 Vector를 한번 더 사용해 본적도 없고, 개발 경험이 거의 전무해서
잘 와닿지는 않고, 공감할래야 공감할 수도 없지만... 이해했다는 것에 만족해야겠습니다.
-ㅅ-... 화잇힝...ㅋㅋ

Vector - Data Growth 추가사항(API문서)
The Vector class implements a growable array of objects. Like an array, it contains components that can be accessed using an integer index. However, the size of a Vector can grow or shrink as needed to accommodate adding and removing items after the Vector has been created.

Vector 클래스는, 오브젝트의 가변장 배열을 구현합니다. 여기에는 배열과 같이, 정수 인덱스를 사용해 액세스 할 수 있는 요소가 격납되고 있습니다. 그러나, Vector 의 사이즈는 작성 후에 추가 및 삭제된 객체를 격납할 수 있듯이 필요에 따라서 늘리거나 줄이거나 할 수가 있습니다.

Each vector tries to optimize storage management by maintaining a capacity and a capacityIncrement. The capacity is always at least as large as the vector size; it is usually larger because as components are added to the vector, the vector's storage increases in chunks the size of capacityIncrement. An application can increase the capacity of a vector before inserting a large number of components; this reduces the amount of incremental reallocation.

각 Vector 는,capacity (용량)와 capacityIncrement (증가량)를 관리하는 것으로써 기억 관리를 최적화하려고 합니다.capacity 는 항상 Vector 의 요소수에 가까운 값이며, 통상은 요소수부터 커집니다. 이것은 Vector 에 요소가 더해질 때, Vector 의 기억 영역은 capacityIncrement 만 늘려지기 때문입니다. 많은 요소를 삽입하기 전에 애플리케이션으로 용량을 필요한 값으로 설정해 두면, 메모리의 재배분의 회수를 줄일 수가 있습니다.


Constant Time : TimeComplexity 단위의 하나로, 입력 n의 크기의 증감과 상관 없이 주어진 문제를 푸는데에 걸리는 시간(See DiscreteTime)이 동일한 것을 말하며, BigONotation으로는 O(1)이라고 표시합니다. 예를 들어 배열의 i번째 원소를 가져오는 연산은 배열의 크기 n과 무관하게 동일한 속도로 수행됩니다.
TimeComplexity : 시간 복잡도. Automaton으로 어떠한 문제를 풀기 위해 소모되는 이산시간(See DiscreteTime)의 크기를 말합니다.
Automaton : 물리적/논리적인 자동기계-ㅅ-.. 라는데... 그냥 컴퓨터로 이해하겠습니다.
BigONotation : 아.. 이건 진짜 모르겠습니다-ㅅ-;;;;;;;;;;;;;;;;;;;;;;; 살려주세요 ㅠ
출처 : http://jania.pe.kr/wiki/jwiki/moin.cgi
반응형
일단, LikedList에 대하여 살펴봐야겠군요.
1학년때 교재로 썼던 <<Absolute Java>>를 보면,
Liked Data Strutures라는 챕터가 있습니다.
아래는 Introduction에 있는 내용인데요. 야심차게 해석을..-ㅅ-;

A linked data structure consists of capsules of data, known as nodes, that are connected via things called links. These links can be viewed as arrows and thought of as ane-way passages from one node to another. The simplest kind of liked data structure consists of a single chain of nodes, each connected to the next by a link; this is known as a linked list.

linked data structure은 node라고 알려져 있는 data의 캡슐로 구성되어 있다.
이는 link라 불리우는 것을 매개로 하여 연결된다. 이러한 link는 하나의 node에서 다른 node로 가는 한방향의 통로로 간주 될 수 있다. (화살표로 표시된다.)
linked data structure의 가장 간단한 종류는 일련의 단일 node로 구성되어 있다.
각각은 다음 link까지 연결된다. 이러한 것을 linked list라 한다.

워... 큰일났다... 무지하게.. 난해하네요..-ㅅ-;
이걸 이해하려면.. 자료구조를 공부해야겠는데요...-_ㅠ

그러는 와중에.. 저의 구세주 zerry82님께서 등장하셨습니다.!!!
친절한 설명 감사드립니다 ^ ^
그림으로 표현해보면,

사용자 삽입 이미지

멋진 것이었군요+ㅁ+
그렇다면, ArrayList와 LinkedList의 차이점 쯤은 금방 알 수 있겠군요.

여러가지 문서가 있었는데.. '속도'부분에서 많은 논란이 있더라구요.
일단은 괜찮은 영어문서가 있길래.. 무턱대고 해석 들어갔습니다-ㅅ-;

There are two general-purpose List implementations in the Collection Framework, ArrayList and LinkedList, which of the two List implementations you use depends on your specific needs. If you need to support random access, without inserting or removing elements from any place to other than the end, then ArrayList offers you the optimal collection, the LinkedList class provides uniformly named methods to get, remove and insert an element at the beginning and end of the list.

콜렉션 프레임워크에는 ArrayListLinkedList를 병용하는 List implementations1 이 있다.
이것은 당신이 특정한 필요에 의해서 사용하는 두가지 List implementations이다.
만약 당신이 끝 이외에 어디에서든지 요소를 제거하거나 삽입하는 것 없이 자유롭게 접근하는 것이 필요하다면, 그땐, ArrayList가 당신에게 최적의 콜렉션을 제공한다.
LinkedList
클래스는 리스트의 처음과 끝에 요소을 제거하거나 삽입하는 메소드를 제공한다.

Each ArrayList instance has a capacity. The capacity is the size of the array used to store the elements in the list. It is always at least as large as the list size. As elements are added an ArrayList, its capacity grows automatically. The details of the growth policy are not specified beyond the fact that adding an element has constant amortized time cost.

각각의 ArrayList 인스턴스는 용량을 가지고 있다. 용량은 리스트에서 요소들을 저장하기 위해 사용되는 array 크기이고, 언제나 적어도 리스트의 크기만큼 커야한다. 요소들이 ArrayList 추가될 , 그것의 용량은 자동으로 증가한다. 증가방법에 대한 자세한 사항은 지속적으로 상환되는 시간비용을 가진 요소를 더하는 사실 이외에는 기술되어지지 않는다.

An application can increase the capacity of an ArrayList instance before adding a large number of elements using the ensureCapacity operation. This may reduce the amount of incremental reallocation.

에플리케이션은 ensureCapacity 기능을 사용하여 많은 수의 요소들을 추가하기 전에ArrayList 인스턴스의 용량을 증가시킬 있다.
이러한 것은 많은량의 재배치 증가를 감소시킨다.

그렇지요!! 정리를 해보자면,

more..


출력은 다음과 같습니다.
                    [뎅뎅이, 붉은돼지, zerry, Real Study, java, jsp, html/css]
                    첫번째 : 뎅뎅이
                    [Real Study, 뎅뎅이, 붉은돼지, zerry, java, jsp, html/css]
                    [Real Study, 뎅뎅이, 붉은돼지, zerry, java, jsp]
                    첫번째 : Real Study

소스코드를 보면 아시겠지만, addFirst()라던지.. removeLast()라던지
LinkedList에는 ArrayList의 메소드 이외에 추가, 삭제하는 메소드가 추가되어있습니다.
(API 참조 !!)
제가 작성한 코드는 간단한 코드라 아직까지는 그 중요성이 와닿지 않지만,
이러한 것들이 모이다보면..-ㅅ-;
LinkedList가 ArrayList보다 훨씬 빠른성능을 보여주겠지요??
(스택,큐,양방향큐 를 만들 때 편리하게 사용된다는데요. 이건 뭔지 잘 몰라서-ㅅ-;)

< 출처: http://happystory.tistory.com >
반응형
Enum..일단 에자일 책에서 처음 본 신개념입니다.
일단 정의부터 살펴보도록 하지요.

Enum : 정해진 리스트에서만 값을 선택할 수 있다.
            컴파일러가 지원하는 primitive타입이 아니라
            컴퍼일러가 클래스로 변환시킨다.
            불필요한 상수값 부여를 막을 수 있다.
            코드가 간결해진다.

책에 나와있는 소스코드를 보면,
               
enum Grade { A, B, C, D, F };

요런식으로 선언해줍니다.
Grade enum은 다섯 개의 가능한 값을 가지고, 각각은 객체 인스턴스를 표현합니다.

               public static void main(String[] args){

                     System.out.println(Grade.A);

               }

이런식으로 써줄 수 있지요.

이렇듯, Enum은 기본적으로 static입니다.
Enum클래스를 상속받은 static final class로서,
내부요소들은 해당클래스의 인스턴스를 가지게 되지요.

이걸 증명해줄만한, 재밌는 내용이 있어서 가져와봤습니다.1

enum으로 작성한 것을 jad라는 역컴파일러를 통해서 풀어준 코드입니다.

사용자 삽입 이미지

↓ jad로 변환

사용자 삽입 이미지

신기하지요?+ㅅ+ 스터디시간에 발표했던 내용인데, 복습할 겸.. 다시한번 올려봅니다.ㅎ

그때 알고 넘어간 Enum에 관한 사항은 여기까지입니다.
정말 더 깊이 공부하고 싶은 마음은 굴뚝 같으나..-ㅅ-;
아직까지는 너무 어렵네요.
너무 신개념이라 API를봐도 메소드의 정확한 사용법을
알아내기가 쉽지가 않네요.

일단 저 멀리 안드로메다 수준까지 정리를 해 놓으신 zerry님의 글을
트랙백으로 남겨놓겠습니다.-ㅅ-;
두고두고,, 천천히 봐야지.. 한번에 보려니까 머리가 너무 아프네여 ㅠ ㅎ
반응형
아래 그림에서 볼 수 있듯이 Collection과 Map은 따로 존재합니다.
저는 지금까지 Collection 인터페이스를 Map, Set, List가 구현하는 줄 알았는데,
아니더라구요. 이러한 사실은 API를 봐도 알 수 있습니다.

사용자 삽입 이미지

                        List : 순서(index)를 알고 있는 컬렉션.
                        Set : 중복된 것을 허용하지 않는 컬렉션.
                        Map : 키-값 기능 제공.

이러한 개념은 실제 코드를 작성해봐야 알 수 있지요?
에자일 자바책 9장에 이러한 내용이 깊~~게 언급되므로
그때 좀 더 자세히 알아보도록 하겠습니다.-ㅅ-ㅎ


에자일자바 5장을 보면 Collections.sort()가 등장합니다.
Collection이라는 인터페이스 외에, Collections라는 유틸리스 클래스가 또 존재하는데요.
API를 살펴보니, Collection에는 메소드가 거의 없드라구요.
sort()또한 보이지 않았습니다.
sort()는 Collections에 static으로 선언이 되어있습니다.
아래 Collections API를 보면,
사용자 삽입 이미지
-ㅅ-;;;
난해합니다. -_ㅠ
일단 필드와 메소드를 보고 유추해본 결과,
Collections클래스는 List, Set, Map에서 공통으로 사용되는 메소드를 모은
유틸리티 클래스라고 볼 수 있지 않을까 생각됩니다.

static으로 선언되어 있기 때문에
Collections.sort()로 편하게 사용할 수 있습니다.

다음으로 정말 중요한 iterator()입니다.
iterator()는 Iterator타입의 객체를 리턴하는데요.
이 객체를 사용해서 콜렉션 안에 있는 모든 엘리먼트를 하나하나 검색할 수 있습니다.
참 바보스럽게도, 전 여지껏 Iterator라는 인터페이스 없이
iterator()메소드만 Collection, Set, List에 각각 존재하는 줄 알았습니다.-ㅅ-;
하지만, 아니더라구요 ㅎ API를 보면 알 수 있습니다.
Collection, Set, List 인터페이스는 Iterable인터페이스를 구현합니다.
Iterable인터페이스를 보면,
사용자 삽입 이미지
iterator()메소드가 있는것을 볼 수 있습니다.
보면, 메소드가 Iterator형인 것이 확인되지요??
짧은 영어로 해석을 해보면,
<T>타입 요소의 집합을 건네주는 iterator을 리턴한다.-ㅅ-;

그니까.. 제너릭으로 한정한 타입의 요소의 집합으로 iterator을 리턴한다는 뜻이겠지요?

그렇다면.. 이번에는 Iterator인터페이스를 뒤져보도록 하겠습니다.
다음과 같은 메소드들이 존재합니다.

사용자 삽입 이미지

boolean hasNext()
Returns true if the iteration has more elements. (In other words, returns true if next would return an element rather than throwing an exception.)
Returns:
true if the iterator has more elements.
=> 만약 다음에 불러올 요소들이 더 있다면 true을 리턴한다.
    다시말해서, 만약 next가 엘리먼트를 리턴하면 true를 리턴한다.

직역을 하면 도저히 이해하기가 힘들 것 같아서, 엄청난 의역을 해야만 했습니다.ㅎ
제가 이해한 바로는 next()메소드를 통해서 콜렉션에 저장된 다음 요소가 리턴되면
hasNext()는 true를 리턴하고, 리턴될 다음 요소가 없어도
exception은 발생하지 않는다는 것이지요..?ㅋㅋㅋ

그렇다면, 다음으로 next()메소드를 살펴봅시다. 이것을 보면 더욱 이해가 잘됩니다.
E next()
Returns the next element in the iteration. Calling this method repeatedly until the hasNext() method returns false will return each element in the underlying collection exactly once.
Returns:
the next element in the iteration.
Throws:
NoSuchElementException - iteration has no more elements.
=> 반복하면서 다음 요소를 리턴한다.
    이 메소드는 hasNext()메소드가 false를 리턴할 때까지
    기초가되는 콜렉션에 있는 각각의 요소를 정확하게 한번씩
    반복적으로 리턴할 것이다.

영어는 역시 어렵습니다..-_ㅠ
위에 hasNext()메소드에 대한 설명을 보충해주고 있지요??
이래서 API는 보고나면 재밌습니다... 볼때는 괴롭지만 ㅎㅎ

마지막으로 remove()가 있는데, 이것은 Collection안에 있는 모든 요소를 검색하여
어떤 조건을 만족하지 않는 요소를 제거할 때 사용하는데요.
좋은 소스코드가 있습니다.

                        static void filter(Collection c) {
                            Iterator i = c.iterator();
                            while (i.hasNext()) {
                            if (!cond(i.next())) i.remove();
                            }
                        }

                    hasNext()가 true일 경우에만 if문을 반복하는 것입니다.
                    제시하는 조건을 만족하지 않을경우,
                    remove()메소드를 사용해서 해당하는 요소를 제거해줍니다.

iterator() 이전에 Enumeration이 쓰였다고 하는데요.
iterator()와 Enumeration의 차이점은 다음에 포스팅 하도록 하겠습니다.
차이점을 공부하다보면, iterator()에 대해서도 더 자세히 알 수 있겠지요?
출처 : http://happystory.tistory.com
반응형
향상된 for 루프는 J2SE 5.0에서 새로운 언어기능으로 소개된 것으로,
Iterator를 생성하거나, 카운트 변수의 시작과 끝을 계산할 필요 없이
콜렉션을 반복 할 수 있게 해줍니다.

일단, 기본적인 Array에서 향상된 for loop의 사용을 비교해도록 하겠습니다.

more..


이전에 제가 작성한 코드입니다. 이것을 향상된 for문으로 더 간단하게 만들어보겠습니다.

more..



무지 신기하지요??+ㅛ+
자 이제, 이게 어떻게 변하는 가를 설명하도록 하겠습니다.
기존에 for문은 카운트 변수의 시작과 끝을 계산해야했습니다.
즉, row = 0 이고, row가 rowLand(지뢰의 세로 길이 - scanner을 통해서 받은 값)보다
작을 때까지만.. row을 계속 늘려줘라-ㅅ-; 뭐 요런 것인데요.
향상된 for문을 보면
for(String[] row : landMine)

이런식으로 간단하게 줄여졌습니다.
이는, "landMine이라는 이름의 배열의 엘리멘트(row) 반복"으로 읽을 수 있습니다.
자동으로 엘리멘트의 길이를 측정한다고 볼 수 있겠네요..

향상된 for loop의 기본 syntax을 다음과 같이 나타낼 수 있습니다.
   	EnhancedForStatement:
for ( Type Identifier : Expression )
Statement   
Expression must be an instance of a new interface
    called java.lang.Iterable, or an array.

Expression java.lang.Iterable 또는 array라 일컬어지는
새로운 인터페이스의 인스턴스야만 한다.


향상된 for loop에도 단점이 있다고 합니다.

So when should you use the for-each loop? Any time you can. It really beautifies your code. Unfortunately, you cannot use it everywhere. Consider, for example, the expurgate method. The program needs access to the iterator in order to remove the current element. The for-each loop hides the iterator, so you cannot call remove. Therefore, the for-each loop is not usable for filtering. Similarly it is not usable for loops where you need to replace elements in a list or array as you traverse it. Finally, it is not usable for loops that must iterate over multiple collections in parallel.

당신은 for-each(향상된 for) 루프를 언제 사용하여야 하는가?
언제든지 가능하다. 그러면 당신의 코드를 정말 아름답게 할 것 이다..-ㅅ-;;
허나.. 불행하게도, 당신은 for-each를 어디에서든지 사용할 수 없다.
예들들어, 삭제 메소드를 고려해보라. 프로그램은 현재 element을 제거하기 위해
iterator에 접근하는 것을 필요로 한다. for-each loop는 iterator를 숨긴다.
그래서 당신은 remove을 부를 수 없다.
그러므로, for-each loop는 필터링에 쓰기에는 편리하지 않다.
유사하게, 당신은 콜렉션을 순회하면서,
list나 array에 element들을 바꾸는 것을 필요로 할 때, 적합하지 않다.
마지막으로, 동시에 복합적인 콜렉션을 되풀이하는 것은 적합하지 않다.

아 ! 그리고,
지뢰찾기 자료 입력 부분도 for-each문으로 바꿔보고자 했는데,
잘되지가 않아서.. 많은 블로그를 돌아다니면서 예제를 찾아보았는데요.
자료를 입력해주는 부분에는
향상된 for 루프를 사용하지 않고, 일반 for loop를 사용하드라구요.
좀 더 조사해봐야 할 것 같네요.-ㅛ-

자자자자자 그렇다면,
ArrayList와 같은 콜렉션에서 향상된 for loop(for-each)는
어떻게 쓰여질까요??(제너릭과 iterator(), hasNext()등등의 사용..-ㅅ-)
그것은...............
콜렉션에 대해 좀 더 공부하는 시간을 가진 후에,
그때 다시 포스팅하도록 하겠습니다. +ㅅ+ ㅋㅋ
위에 해석한 단점도.. 그때서야 비로소 이해할 수 있을 것 같네요-ㅛ-;

< 출처: http://happystory.tistory.com >
반응형

게시판 연습

 

[여기서는 먼저 전 게시물의 JDBC 프로그래밍을 복습한 후에 내용을 보시기 바랍니다.]

 

JDBC 프로그래밍에서 실습한 GetEmp.java 순수 애플리케이션을 서블릿으로 바꾸고 실행해 보세요(servlet API 참고)

[참고] JDBC 드라이버 (ORACLE_HOME/jdbc/lib/classes12.jar, nls_charset12.jar)를 {애플리케이션홈}/WEB-INF/lib 에

복사합니다.

 

문제 1 답:

 

package example;

 

import java.sql.*;

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;


public class GetEmp extends HttpServlet {

         private String DB_URL;

         private String DB_USER;

         private String DB_PASSWORD;

        

         public void init(ServletConfig config) throws ServletException {

                  super.init(config);

                  //설치과정에서 자신이 정한 정보에 맞게 바꾼다.

                  DB_URL="jdbc:oracle:thin:@127.0.0.1:1521:orcl";

                  DB_USER="scott";

                  DB_PASSWORD="tiger";

                 

                  try {

                           //드라이버를 로딩합니다.

                           Class.forName("oracle.jdbc.driver.OracleDriver");

                  }catch(ClassNotFoundException e){

                           e.printStackTrace();

                  }

         }

 

         public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException,ServletException {

                  res.setContentType("text/html;charset=euc-kr");

                  PrintWriter out = res.getWriter();

                  Connection conn = null;

                  Statement stmt = null;

                  ResultSet rs = null;

                  String query="select * from emp";

                  try {

                           //데이터베이스의 연결을 설정합니다.

                           conn=DriverManager.getConnection(DB_URL,DB_USER,DB_PASSWORD);

                           //Statement를 가져온다.

                           stmt=conn.createStatement();

                           //SQL문을 실행합니다.

                           rs=stmt.executeQuery(query);

                           while(rs.next())  {

                                   String empno=rs.getString(1);

                                   String ename=rs.getString(2);

                                   String job=rs.getString(3);

                                   String mgr=rs.getString(4);

                                   String hiredate=rs.getString(5);

                                   String sal=rs.getString(6);

                                   String comm=rs.getString(7);

                                   String depno=rs.getString(8);

                                   //결과를 출력합니다.

                                    out.println(empno+" : "+ename+" : "+job+" : "+mgr+" : "+hiredate+" : "+sal+" : "+comm+" : "+depno+"<br>");

                           }

                  } catch (SQLException e) {

                           e.printStackTrace(out);

                  }

                  finally {

                           try{

                                   rs.close();

                                   stmt.close();

                                   conn.close();

                           }catch(SQLException e){}

                  }

         }

}


7369 : SMITH : CLERK : 7902 : 1980-12-17 00:00:00.0 : 800 : null : 20
7499 : ALLEN : SALESMAN : 7698 : 1981-02-20 00:00:00.0 : 1600 : 300 : 30
7521 : WARD : SALESMAN : 7698 : 1981-02-22 00:00:00.0 : 1250 : 500 : 30
7566 : JONES : MANAGER : 7839 : 1981-04-02 00:00:00.0 : 2975 : null : 20
7654 : MARTIN : SALESMAN : 7698 : 1981-09-28 00:00:00.0 : 1250 : 1400 : 30
7698 : BLAKE : MANAGER : 7839 : 1981-05-01 00:00:00.0 : 2850 : null : 30
7782 : CLARK : MANAGER : 7839 : 1981-06-09 00:00:00.0 : 2450 : null : 10
7788 : SCOTT : ANALYST : 7566 : 1987-04-19 00:00:00.0 : 3000 : null : 20
7839 : KING : PRESIDENT : null : 1981-11-17 00:00:00.0 : 5000 : null : 10
7844 : TURNER : SALESMAN : 7698 : 1981-09-08 00:00:00.0 : 1500 : 0 : 30
7876 : ADAMS : CLERK : 7788 : 1987-05-23 00:00:00.0 : 1100 : null : 20
7900 : JAMES : CLERK : 7698 : 1981-12-03 00:00:00.0 : 950 : null : 30
7902 : FORD : ANALYST : 7566 : 1981-12-03 00:00:00.0 : 3000 : null : 20
7934 : MILLER : CLERK : 7782 : 1982-01-23 00:00:00.0 : 1300 : null : 10

 

위와 같은 결과 화면을 웹 브라우저에서 확인하셨습니까?

GetEmp 서블릿의 57번째 줄에서 에러가 발생한다는 메시지를 보신다면 /bbs/WEB-INF/lib 에 classes12.jar 파일을 복사하지 않으셨거나 오라클이 가동중이 아니거나 리슨너가 가동중이 아니거나 한 경우입니다.

윈도우에서 오라클을 설치하셨다면 나중 2개는 무시하시고 JDBC 드라이버를 올바른 위치에 복사했는지 확인하시기 바랍니다.

 

문제 2 : 위 서블릿을 JSP로 바꾸어 보세요

 

문제 2 답: 파일명 getEmp.jsp

 

<%@ page contentType="text/html;charset=euc-kr" %>

<%@ page import="java.sql.*" %>

<%

         String DB_URL="jdbc:oracle:thin:@127.0.0.1:1521:orcl";

         String DB_USER="scott";

         String DB_PASSWORD="tiger";

         //설치과정에서 자신이 정한 정보에 맞게 바꾼다.

         try {

                  //드라이버를 로딩합니다.

                  Class.forName("oracle.jdbc.driver.OracleDriver");

         } catch(ClassNotFoundException e){

                           e.printStackTrace();

         }

 

         Connection conn = null;

         Statement stmt = null;

         ResultSet rs = null;

         String query="select * from emp";

         try {

                  //데이터베이스의 연결을 설정합니다.

                  conn=DriverManager.getConnection(DB_URL,DB_USER,DB_PASSWORD);

                  //Statement를 가져온다.

                  stmt=conn.createStatement();

                  //SQL문을 실행합니다.

                  rs=stmt.executeQuery(query);

                  while(rs.next()) {

                           String empno=rs.getString(1);

                           String ename=rs.getString(2);

                           String job=rs.getString(3);

                           String mgr=rs.getString(4);

                           String hiredate=rs.getString(5);

                           String sal=rs.getString(6);

                           String comm=rs.getString(7);

                           String depno=rs.getString(8);

                           //결과를 출력합니다.

                           out.println(empno+" : "+ename+" : "+job+" : "+mgr+" : "+hiredate+" : "+sal+" : "+comm+" : "+depno+"<br>");

                  }

         } catch (SQLException e) {

                  out.println("SQLException: " + e.getMessage());

         }

         finally {

                  try {

                           rs.close();

                           stmt.close();

                           conn.close();

                  } catch(SQLException e){}

         }

%>

 

문제 1 에서와 같은 결과를 확인했습니까?

문제1의 서블릿과 지금 JSP는 코드가 조금 다릅니다.

서블릿에서는 init 메소드에서 JDBC 드라이버에 대한 설정을 했습니다.

서블릿의 라이트 사이클에서 살펴 보았듯이 init 메소드안의 내용은 처음 사용자가 요청한 경우를 제외하고는 다음 요청 부터는 JDBC 드라이버 설정을 하지 않으므로 그렇지 않은 코드보다는 성능이 좋습니다.

JSP에서도 서블릿의 init 메소드와 같은 메소드가 있을 것입니다. 이것은 여러분이 찾아서 고치시기 바랍니다.

 

 

ConnectionPool 사용법

 

데이터베이스에 대한 Connection 을 관리하는 것은 매우 바람직한 일입니다.
왜냐하면 매 페이지마다 데이터베이스에 연결을 시도하는 것(= 커넥션 객체를 얻는것)은 시간이 많이 걸리는 작업입니다.
그럼에도 불구하고 위에서 우리는 Connection 을 얻은 후 사용 후 바로 자원을 반납했습니다
.

 

Database Connection Pool 이란 이전에 연결된 Connection 을 종료하지 않고 웹 클라이언트의 요구시 기존의 Connection을

통하여 데이터베이스에 접근할 수 있도록 하는 free Connection list 입니다.

즉, 시간이 오래 걸리는 커넥션 객체를 쓰고 난 다음에 리스트에서 넣고 관리하겠다는 의미입니다.

적절하게 리스트를 관리하면 시간을 절약하는데 크게 기여하게 하는 기법입니다.

 

. 전체 클래스 요약

 

Log.java
에러 메시지를 콘솔화면에 표시하기 위한 클래스입니다.

 

DBConnectionPoolManager.java

각 데이터베이스의 커넥션풀을 관리하기 위한 클래스입니다.

 

DBConnectionPool.java
DBConnectionPoolManager가 관리하는 데이터베이스당 가지는 커넥션 풀
실제로 이 클래스가 이미 생성된 커넥션을 관리
합니다.

 

ConnectionManager.java
DBConnectionPoolManager 클래스에 일을 시키는 클래스인데
여러 데이터베이스를 고려한 추상클래스입니다.
데이터베이스에 따라서 이 클래스를 상속받아 알맞는 클래스
를 만듭니다.

 

OracleConnectionManager.java 
데이터베이스 오라클을 사용하기 위해 ConnectionManager 클래스를 상속한 서브 클래스입니다.

 

oracle.properties
오라클 설정 파일입니다.
최대 커넥션 수, 초기 커넥션 수 등의 셋팅을 담당합니다.

이렇게 설정 내용을 코드가 아닌 파일에 저장하면 관리하기가 쉽습니다.

 

. 커넥션 풀 관련 소스

 

Log.java


package net.java_school.util;

 

import java.io.*;

import java.util.Date;

 

public class Log {

         public String dbgFile = "H:\\debug\\error.dbg"; // 윈도우  계열

         //public String dbgFile = "/debug/error.dbg"; // 유닉스 계열

 

         FileWriter fw = null;

         public Log() {

                  super();

                  try{

                           fw = new FileWriter(dbgFile, true);

                  }catch(IOException e){}

         }

 

         public void close() {

                  try{

                           fw.close();

                  }catch(IOException e){}

         }

 

         public void close(FileWriter fw) {

                  try{

                           fw.close();

                  }catch(IOException e){}

         }

 

         public void debug(String msg) {

                  try{

                           fw.write("-------------------------------------------------\r\n");

                           fw.write(new java.util.Date()+ " : ");

                           fw.write(msg + " \r\n");

                           fw.write("-------------------------------------------------\r\n");

                           fw.flush();

                  }catch(IOException e){

                           System.err.println("IOException.......!!");

                  }

         }

 

         public static void out(String msg) {

                  System.out.println(new Date() + ": "+msg);

         }

 

         public static void err(String msg) {

                  System.out.println(new Date() + ": "+msg);

         }

 

         public static void err(Throwable e,String msg) {

                  System.err.println(new Date() + ": "+msg);

                  e.printStackTrace(System.out);

         }

}


DBConnectionPool.java


package net.java_school.db.dbpool;


import java.util.*;
import java.sql.*;
import java.util.Date;
import net.java_school.util.Log;


// Connection Pool을 관리하는 클래스
class DBConnectionPool {
 
    // 현재 사용 중인 Connection 개수
    private int checkedOut;
 
    // Free Connection List
    private Vector freeConnections = new Vector();

    // Connection 최대 개수
    private int maxConn;

    // Connection 초기 개수
    private int initConn;

    // Waiting time (pool에 connection이 없을때 기다리는 최대시간)
    private int maxWait;

    // Connection Pool Name
    private String name;

    // DB Password
    private String password;

    // DB URL
    private String URL;

    // DB UserID
    private String user;

    // Constructor
    public DBConnectionPool(String name, String URL, String user, String password, int maxConn, int initConn, int waitTime) {
        this.name = name;
        this.URL = URL;
        this.user = user;
        this.password = password;
        this.maxConn = maxConn;
        this.maxWait = waitTime;
 
        for (int i = 0; i < initConn; i++) {
            freeConnections.addElement(newConnection());
        }
    }
 
    // Connection 반납
    // @param con : 반납할 Connection
    public synchronized void freeConnection(Connection con) {
        freeConnections.addElement(con);
        checkedOut--;
        // Connection을 얻기 위해 대기하고 있는 thread에 알림
        notifyAll();
    }
 
    // Connection 을 얻음
    public synchronized Connection getConnection() {
        Connection con = null;

        // Connection이 Free List에 있으면 List의 처음 것을 얻음
        if (freeConnections.size() > 0) {
            con = (Connection) freeConnections.firstElement();
            freeConnections.removeElementAt(0);
            try {
                // DBMS에 의해 Connection이 close 되었으면 다시 요구
                if (con.isClosed()) {
                    Log.err("Removed bad connection from " + name);
                    con = getConnection();
                }
            } // 요상한 Connection 발생하면 다시 요구
            catch (SQLException e) {
                Log.err(e, "Removed bad connection from " + name);
                con = getConnection();
            }
        } // Connection이 Free List에 없으면 새로 생성
        else if (maxConn == 0 || checkedOut < maxConn) {
            con = newConnection();
        }
        if (con != null) {
            checkedOut++;
        }
        return con;
    }
 
    // Connection을 얻음
    // @param timeout : Connection을 얻기 위한 최대 기다림 시간
    public synchronized Connection getConnection(long timeout) {
        long startTime = new Date().getTime();
        Connection con;

        while ((con = getConnection()) == null) {
            try {
                wait(timeout * maxWait);
            } catch (InterruptedException e) {}
            if ((new Date().getTime() - startTime) >= timeout) {
                // 기다림 시간 초과
                return null;
            }
        }
        return con;
    }
 
    // Connection 생성
    private Connection newConnection() {
        Connection con = null;

        try {
            if (user == null) {
                con = DriverManager.getConnection(URL);
            } else {
                con = DriverManager.getConnection(URL, user, password);
            }
            Log.out("Created a new connection in pool " + name);
        } catch (SQLException e) {
            Log.err(e,
                    "Can't create a new connection for " + URL + " user : "
                    + user + " passwd : " + password);
            return null;
        }
        return con;
    }
}



DBConnectionPoolManager.java


package net.java_school.db.dbpool;

 

import java.sql.*;

import java.util.*;

import net.java_school.util.Log;


public class DBConnectionPoolManager {

         // 인스턴스를 하나만 유지하기 위해 static 으로 선언

         static private DBConnectionPoolManager instance=null;

         //JDBC 드라이버 관리

         private Vector drivers = new Vector();

         // DB Connection Pool List

         private Hashtable pools = new Hashtable();

         // DBConnectionPoolManager의 instance를 얻음

         // @return DBConnectionManger

         static synchronized public DBConnectionPoolManager getInstance() {

                  if (instance == null) {

                           instance = new DBConnectionPoolManager();

                  }

                  return instance;

         }

        

         // Default Constructor

         private DBConnectionPoolManager() {}

 

         // 현재 Connection을 Free Connection List로 보냄

         // @param name : Pool Name

         // @param con : Connection

         public void freeConnection(String name, Connection con) {

                  DBConnectionPool pool = (DBConnectionPool) pools.get(name);

                  if (pool != null) {

                           pool.freeConnection(con);

                  }

                  Log.out("One Connection of " + name + " was freed");

         }

 

         // Open Connection을 얻음. 현재 열린 커넥션이 없고 최대 커넥션 개수가

         // 사용 중이 아닐 때는 새로운 커넥션을 생성. 현재 열린 커넥션이 없고

         // 최대 커넥션 개수가 사용 중일 때 기본 대기 시간을 기다림

         // @param name : Pool Name

         // @return Connection : The connection or null

         public Connection getConnection(String name) {

                  DBConnectionPool pool = (DBConnectionPool) pools.get(name);

                  if (pool != null) {

                           return pool.getConnection(10);

                  }

                  return null;

         }

 

         // Connection Pool을 생성

         // @param poolName : 생성할 Pool Name

         // @param url : DB URL

         // @param user : DB UserID

         // @param password : DB Password

         private void createPools(String poolName, String url, String user, String password, int maxConn, int initConn, int maxWait) {

                  DBConnectionPool pool = new DBConnectionPool(poolName, url, user, password, maxConn, initConn, maxWait);

                  pools.put(poolName, pool);

                  Log.out("Initialized pool " + poolName);

         }

 

         // 초기화 작업

         public void init(String poolName, String driver, String url, String user, String passwd, int maxConn, int initConn, int maxWait) {

                  loadDrivers(driver);

                  createPools(poolName, url, user, passwd, maxConn, initConn, maxWait);

         }

 

         // JDBC Driver Loading

         // @param driverClassName : 사용하고자 하는 DB의 JDBC 드라이버

         private void loadDrivers(String driverClassName) {

                  try {

                           Class.forName(driverClassName);

                           drivers.addElement(driverClassName);

                           Log.out("Registered JDBC driver " + driverClassName);

                  } catch (Exception e) {

                           Log.err(e, "Can't register JDBC driver: " + driverClassName);

                  }

         }

        

         public Hashtable getPools() {

                  return pools;

         }

        

         public int getDriverNumber() {

                  return drivers.size();

         }

}



ConnectionManager.java


package net.java_school.db.dbpool;

 

import java.sql.*;

import java.io.*;

import java.util.*;

import net.java_school.util.Log;

 

public abstract class ConnectionManager {

         protected DBConnectionPoolManager connMgr=null;

         protected String poolName, dbServer, dbName, port, userID, passwd;

         int maxConn,initConn, maxWait;

         private Properties dbProperties;

         private String configFile;

 

         public ConnectionManager(String pool) {

                  poolName = pool;

                  // mnd 컨텍스트 베이스에 config디렉토리에 Property파일이 있음을 가정

                  configFile = "F:\\apps\\tomcat\\webapps\\mnd\\config\\"+poolName+".properties";

                  try {

                           dbProperties = readProperties();

                           dbServer = getProperty( "dbServer" );

                           port = getProperty( "port" );

                           dbName = getProperty( "dbName" );

                           userID = getProperty( "userID" );

                           passwd = getProperty( "passwd" );

                           maxConn = Integer.parseInt(getProperty("maxConn"));

                           initConn = Integer.parseInt(getProperty("initConn"));

                           maxWait = Integer.parseInt(getProperty("maxWait"));

                  } catch( IOException ioe ) {

                           Log.err( "Error reading properties of " + configFile);

                  }

         }

 

         public Connection getConnection() {

                  return (connMgr.getConnection(poolName));

         }

 

         public void freeConnection(Connection conn) {

                  connMgr.freeConnection(poolName,conn);

         }

 

         private String getProperty( String prop ) throws IOException {

                  return ( dbProperties.getProperty( prop ) );

         }

 

         protected synchronized Properties readProperties() throws IOException {

                  Properties tempProperties = new Properties();

                  FileInputStream in = new FileInputStream(configFile);

                  tempProperties.load(in);

                  return tempProperties;

         }

        

         public int getDriverNumber() {

                  return connMgr.getDriverNumber();

         }

}


OracleConnectionManager.java


package net.java_school.db.dbpool;

 

public class OracleConnectionManager extends ConnectionManager {

         public OracleConnectionManager() {

                  super("oracle");

                  String JDBCDriver = "oracle.jdbc.driver.OracleDriver";

                  // 오라클용 JDBC thin driver

                  String JDBCDriverType = "jdbc:oracle:thin";

                  String url = JDBCDriverType+":@"+dbServer + ":" + port + ":" + dbName;

                  connMgr = DBConnectionPoolManager.getInstance();

                  connMgr.init(poolName, JDBCDriver, url, userID, passwd, maxConn, initConn, maxWait);

         }

}

 

oracle.properties

 

#########################################
# Database Connection Properties for Oracle 9i
#########################################

# Database Server Name OR IP address
dbServer=127.0.0.1

# The port number your DB server listents to.
port=1521

# Database Name
dbName=ORCL

# Database User
userID=scott

# Database Password
passwd=tiger

# Maximum Connection Number
maxConn=20

# Inital Connection Number
initConn=5

# Maximum Wait Time
maxWait=5

 

문제 1

getEmp.jsp 를 ConnectionPool을 이용하게 고쳐보세요

 

 

getEmpWithPool.jsp

 

<%@ page contentType="text/html;charset=euc-kr" %>

<%@ page import="java.sql.*, net.java_school.db.dbpool.*" %>

<jsp:useBean id="dbmgr" class="net.java_school.db.dbpool.OracleConnectionManager" scope="application" />

<%

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

String query="select * from emp";

try {

          //데이터베이스의 연결을 설정합니다.커넥션풀 이용

          conn=dbmgr.getConnection();

          //Statement를 가져온다.

          stmt=conn.createStatement();

          //SQL문을 실행합니다.

          rs=stmt.executeQuery(query);

          while(rs.next()) {

                   String empno=rs.getString(1);

                   String ename=rs.getString(2);

                   String job=rs.getString(3);

                   String mgr=rs.getString(4);

                   String hiredate=rs.getString(5);

                   String sal=rs.getString(6);

                   String comm=rs.getString(7);

                   String depno=rs.getString(8);

                   //결과를 출력합니다.

                   out.println(empno+" : "+ename+" : "+job+" : "+mgr+" : "+hiredate+" : "+sal+" : "+comm+" : "+depno+"<br>");

          }

} catch (SQLException e) {

         out.println("SQLException: " + e.getMessage());

} finally {

         try {

                  rs.close();

                  stmt.close();

                  dbmgr.freeConnection(conn);

         } catch(SQLException e){}

}

%>

 

문제 2 : 위 파일을 로그 클래스(Log.java)를 이용해서 SQLException이 일어날때 이를 로그 파일(error.dbg)에 기록하게 고치세요

 

: getEmpWithPooldbg.jsp

 

<%@ page contentType="text/html;charset=euc-kr" %>

<%@ page import="java.sql.*, net.java_school.db.dbpool.*, net.java_school.util.*" %>

<jsp:useBean id="dbmgr" class="net.java_school.db.dbpool.OracleConnectionManager"

scope="application" />

<%

Log log = new Log();

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

String query="select * from emp";

try {

         //데이터베이스의 연결을 설정합니다.커넥션풀 이용

         conn=dbmgr.getConnection();

         //Statement를 가져온다.

         stmt=conn.createStatement();

         //SQL문을 실행합니다.

         rs=stmt.executeQuery(query);

         while(rs.next()) {

                  String empno=rs.getString(1);

                  String ename=rs.getString(2);

                  String job=rs.getString(3);

                  String mgr=rs.getString(4);

                  String hiredate=rs.getString(5);

                  String sal=rs.getString(6);

                  String comm=rs.getString(7);

                  String depno=rs.getString(8);

                  //결과를 출력합니다.

                  out.println(empno+" : "+ename+" : "+job+" : "+mgr+" : "+hiredate+" : "+sal+" : "+comm+" : "+depno+"<br>");

         }

} catch (SQLException e) {

         log.debug("Error Source:getEmpWithPooldbg.jsp : SQLException");

         log.debug("SQLState : " + e.getSQLState());

         log.debug("Message : " + e.getMessage());

         log.debug("Oracle Error Code : " + e.getErrorCode());

         log.debug("Query : " + query);

} finally {

         try {

                  rs.close();

                  stmt.close();

                  dbmgr.freeConnection(conn);

                  log.close();

         } catch (SQLException e){}

}

%>

 

위 파일에서 select * from emp 란 스트링을 일부러 에러가 나게 select * fromemp 라고 고치고 다시 테스트해봅니다.

 

로그파일은 Log.java 파일에서 정한대로의 위치에 error.dbg 란 파일을 내용없이 만들어 위치시켜야 합니다.

 

그러면 다음과 같이 로그 파일에 기록이 쌓이는 것을 확인할 수 있습니다.

 

Sat Jun 19 14:01:58 KST 2004 : Error Source:getEmpWithPooldbg.jsp : SQLException
Sat Jun 19 14:01:58 KST 2004 : SQLState : 42000
Sat Jun 19 14:01:58 KST 2004 : message : ORA-00923: FROM 키워드가 있어야할 곳에 없습니다
Sat Jun 19 14:01:58 KST 2004 : Oracle Error Code : 923
Sat Jun 19 14:01:58 KST 2004 : Query : select * fromemp

여기까지 오셨다면 게시판을 위한 준비가 다 된 것입니다.

파일에 로그 파일 쌓이지 않은다면 ConnectionManager.java 의 코드에서 명시한 디렉토리에 error.dbg 파일이 없기 때문입니다.


 

 게시판 연습

 

먼저 테이블을 만듭니다. (오라클에 대한 내용은 오라클 카테고리의 글을 참고하세요)

 

-- 게시판 연습 테이블
create table board(
 num number primary key,
 subject varchar2(200) not null,
 sogae varchar2(4000) not null,
 content varchar2(4000),
 wdate date
);

create sequence seq_board_num
start with 1
increment by 1;

 

적당한 디렉토리에 board.sql로 위 내용으로 만들고 scott 계정으로 접속하여 (C:\에 저장했다면)

 

@C:\board.sql

 

하면 테이블과 시퀀스가 생성됩니다.

 

여기서의 게시판은 단순한 기본적인 기능만을 가진 것을 만들어 보겠습니다.

다음은 게시판을 위해 쓸 파일 흐름도 입니다.

(서블릿과 JSP 를 모두 이용하기 위해 애쓴것이지 이와 같이 프로그래밍하지는 않습니다.)

 

list.jsp ---> write_form.jsp

                  |              |--------->BoardWriter.java(서블릿) - DB insert 문 실행

                  |                                         

                  |---> view.jsp : 게시물의 내용을 보여준다.

                             |

                             |

                             |---> modify_form.jsp

                             |            |----------> BoardModifier.java(서블릿) --- DB update 문 실행

                             |

                             |---> delete_confirm.jsp

                                          |-----------> BoardDeleter.java(서블릿) -- DB delete 문 실행

 

list.jsp : 게시물을 모두 보여줍니다.(페이지분할 기능, 페이지 직접 이동 기능, 검색 기능은 후에 붙이도록 합니다.)

write_form.jsp : 새글 입력 폼 화면

BoardWriter.java : 새글 입력을 실제로 DB에 입력하는 서블릿

view.jsp : 해당 게시물의 상세 정보를 출력하는 페이지

modify_form.jsp : 수정 입력 폼 화면

BoardModifier.java : 수정을 위해 실제로 DB 테이블을 수정을 행하는 서블릿

delete_confirm.jsp : 삭제를 사용자가 선택했을 때 삭제를 정말로 할 것인가 확인하는 화면

BoardDeleter.java : 테이블에서 해당 레코드를 삭제하는 서블릿

 

각각의 소스

 

/bbs/bbs01/list.jsp


<%@ page contentType="text/html;charset=euc-kr" %>

<%@ page import="java.sql.*,net.java_school.util.*,net.java_school.db.dbpool.*" %>

<jsp:useBean id="dbmgr" scope="application" class="net.java_school.db.dbpool.OracleConnectionManager" />

<html>

<body>

<%

Log log = new Log();

Connection conn = null;

PreparedStatement prepare = null;

ResultSet rs = null;

String query = null;

try{

         conn = dbmgr.getConnection();

         query = "select num,subject,wdate from board order by num desc";

         prepare = conn.prepareStatement(query);

         rs = prepare.executeQuery();

         while(rs.next()){

         int num = rs.getInt("num");

         String subject = rs.getString("subject");

         Date wdate = rs.getDate("wdate");

%>

<a href="view.jsp?num=<%= num %>"><%= subject%></a>&nbsp;&nbsp;<%= wdate.toString() %><br>

<%

         }

} catch(SQLException e){

         log.debug("Error Source:bbs/list.jsp : SQLException");

         log.debug("SQLState : " + e.getSQLState());

         log.debug("Message : " + e.getMessage());

         log.debug("Oracle Error Code : " + e.getErrorCode());

         log.debug("Query : " + query);

} finally {

         try {

                  rs.close();

                  prepare.close();

                  dbmgr.freeConnection(conn);

                  log.close();

         } catch (SQLException e){}

}

%>

<br>

<br>

<p>

<a href="write_form.jsp">새글 쓰기</a>

</body>

</html>

 

/bbs/bbs01/write_form.jsp

 

<%@ page contentType="text/html;charset=euc-kr" %>
<html>
<body>
<center><h1>새글 쓰기</h1></center>
<hr>
<table cellspacing="0" cellpadding="0" border="0" width="600" align="center">
<form name="frm" method="POST" action="../servlet/bbs01.BoardWriter">
<tr>
 <td width="100" align="center">제목</td><td><input type="text" name="subject" size="45"></td>
</tr>
<tr>
 <td width="100" align="center">소개글</td><td><textarea name="sogae" rows="10"

cols="60"></textarea></td>
</tr>
<tr>
 <td width="100" align="center">본문</td><td><textarea name="content" rows="10"

cols="60"></textarea></td>
</tr>
<tr>
 <td colspan="2" align="center"><input type="submit" value="전송">&nbsp;&nbsp;&nbsp;<input type="reset" value="취소"></td>
</tr>
</form>
</table>
<br>
<center><a href="list.jsp">목록으로</a></center>
</body>
</html>

 

/bbs/WEB-INF/classes/BoardWriter.java

 

package bbs01;

 

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import java.sql.*;

import net.java_school.db.dbpool.*;

import net.java_school.util.*;

 

public class BoardWriter extends HttpServlet {

         PreparedStatement prepare = null;

         OracleConnectionManager dbmgr = null;

         // 입력 순서 : 시퀀스 , 제목, 소개글, 본문

         String query = "insert into board values (seq_board_num.nextval,?,?,?,sysdate)";

 

         public void init()  throws ServletException {

           ServletContext sc = getServletContext();
           dbmgr = (OracleConnectionManager)sc.getAttribute("dbmgr");
         }
 

         public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

                  req.setCharacterEncoding("euc-kr");

                  Log log = new Log();

                  String subject = req.getParameter("subject");

                  String sogae = req.getParameter("sogae");

                  String content = req.getParameter("content");

                  Connection con = dbmgr.getConnection();

                  try {

                           prepare = con.prepareStatement(query);

                           prepare.setString(1,subject); //제목 부분

                           prepare.setString(2,sogae); // 소개글 부분

                           prepare.setString(3,content); // 본분 부분

                           prepare.executeUpdate();      // 쿼리 실행

                  } catch (SQLException e){

                           log.debug("Error Source:BoardWriter.java : SQLException");

                           log.debug("SQLState : " + e.getSQLState());

                           log.debug("Message : " + e.getMessage());

                           log.debug("Oracle Error Code : " + e.getErrorCode());

                           log.debug("Query : " + query);

                  } finally {

                           try {

                                   prepare.close();

                                   dbmgr.freeConnection(con);

                                   log.close();

                           } catch(SQLException e){}

                           String path = req.getContextPath();

                           res.sendRedirect(path+"/bbs01/list.jsp");

                  }

         }

}

 

/bbs/bbs01/view.jsp

 

<%@ page contentType="text/html;charset=euc-kr" import="java.sql.*,

net.java_school.util.*,net.java_school.db.dbpool.*" %>

<jsp:useBean id="dbmgr" scope="application"

class="net.java_school.db.dbpool.OracleConnectionManager" />

<html>

<body>

<%

int num = Integer.parseInt(request.getParameter("num"));

Log log = new Log();

Connection conn = null;

PreparedStatement prepare = null;

ResultSet rs = null;

String query = null;

try{

         conn = dbmgr.getConnection();

         query = "select num,subject,sogae,content,wdate from board where num="+num;

         prepare = conn.prepareStatement(query);

         rs = prepare.executeQuery();

         while(rs.next()){

                  String subject = rs.getString("subject");

                  String sogae = rs.getString("sogae");

                  String content = rs.getString("content");

                  Date wdate = rs.getDate("wdate");

%>

<%= subject %></a><br>

<hr>

<%= sogae %><br>

<hr>

<%= content %><br>

<hr>

<%= wdate.toString() %><br>

<%

         }

} catch(SQLException e){

         log.debug("Error Source:bbs/view.jsp : SQLException");

         log.debug("SQLState : " + e.getSQLState());

         log.debug("Message : " + e.getMessage());

         log.debug("Oracle Error Code : " + e.getErrorCode());

         log.debug("Query : " + query);

} finally{

         try {

                  rs.close();

                  prepare.close();

                  dbmgr.freeConnection(conn);

                  log.close();

         } catch(SQLException e){}

}

%>

<form name="modify" method="POST" action="modify_form.jsp">

<input type="submit" value="수정">

<input type="hidden" name="num" value="<%= num %>">

</form>

<form name="delete" method="POST" action="delete_confirm.jsp">

<input type="submit" value="삭제">

<input type="hidden" name="num" value="<%= num %>">

</form>

<br>

<a href="list.jsp">목록으로</a>

</body>

</html>

 

/bbs/bbs01/modify_form.jsp

 

<%@ page contentType="text/html;charset=euc-kr"

import="java.sql.*,net.java_school.util.*,net.java_school.db.dbpool.*" %>

<jsp:useBean id="dbmgr" scope="application"

class="net.java_school.db.dbpool.OracleConnectionManager" />

<%

Log log = new Log();

int num = Integer.parseInt(request.getParameter("num"));

Connection conn = null;

PreparedStatement prepare = null;

ResultSet rs = null;

String subject = null;

String sogae = null;

String content = null;

String query = "select subject,sogae,content from board where num ="+num;

try {

         conn = dbmgr.getConnection();

         prepare = conn.prepareStatement(query);

         rs = prepare.executeQuery();

 

         while(rs.next()){

                  subject = rs.getString("subject");

                  sogae = rs.getString("sogae");

                  content = rs.getString("content");

         }

} catch(SQLException e){

         log.debug("Error Source:modify_form.jsp : SQLException");

         log.debug("SQLState : " + e.getSQLState());

         log.debug("Message : " + e.getMessage());

         log.debug("Oracle Error Code : " + e.getErrorCode());

         log.debug("Query : " + query);

} finally{

         try {

                  rs.close();

                  prepare.close();

                  dbmgr.freeConnection(conn);

                  log.close();

         } catch(SQLException e){}

}

%>

<html>

<body>

<center><h1>글 수정</h1></center>

<hr>

<table cellspacing="0" cellpadding="0" border="0" width="600" align="center">

<form name="frm" method="POST" action="../servlet/bbs01.BoardModifier">

<input type="hidden" name="num" value="<%= num %>">

<tr>

         <td width="100" align="center">제목</td><td><input type="text" name="subject" size="45" value="<%= subject %>"></td>

</tr>

<tr>

         <td width="100" align="center">소개글</td><td><textarea name="sogae" rows="10" cols="60"><%= sogae %></textarea></td>

</tr>

<tr>

         <td width="100" align="center">본분</td><td><textarea name="content" rows="10" cols="60"><%= content %></textarea></td>

</tr>

<tr>

         <td colspan="2" align="center"><input type="submit" value="전송">&nbsp;&nbsp;&nbsp;<input type="reset" value="취소"></td>

</tr>

</form>

</table>

<br>

<center><a href="list.jsp">목록으로</a></center>

</body>

</html>

 

/bbs/WEB-INF/classes/BoardModifier.java

 

package bbs01;

 

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import java.sql.*;

import net.java_school.db.dbpool.*;

import net.java_school.util.*;

 

public class BoardModifier extends HttpServlet {

         PreparedStatement prepare = null;

         OracleConnectionManager dbmgr = null;

         String query = "update board set subject=?, sogae=?, content=? where num=?";

        

         public void init()  throws ServletException {
           ServletContext sc = getServletContext();
           dbmgr = (OracleConnectionManager)sc.getAttribute("dbmgr");
         }
 

         public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

                  req.setCharacterEncoding("euc-kr");

                  Log log = new Log();

                  int num = Integer.parseInt(req.getParameter("num"));

                  String subject = req.getParameter("subject");

                  String sogae = req.getParameter("sogae");

                  String content = req.getParameter("content");

                  Connection con = dbmgr.getConnection();

                  try{

                           prepare = con.prepareStatement(query);

                           prepare.setString(1,subject);   //제목 부분

                           prepare.setString(2,sogae);    // 소개글 부분

                           prepare.setString(3,content);   // 본분 부분

                           prepare.setInt(4,num);     // 시퀀스 primary key

                           prepare.executeUpdate();    // 쿼리 실행

                  }catch(SQLException e){

                           log.debug("Error Source:BoardModifier.java : SQLException");

                           log.debug("SQLState : " + e.getSQLState());

                           log.debug("Message : " + e.getMessage());

                           log.debug("Oracle Error Code : " + e.getErrorCode());

                           log.debug("Query : " + query);

                  } finally {

                           try {

                                   prepare.close();

                                   dbmgr.freeConnection(con);

                                   log.close();

                           } catch(SQLException e){}

                           String path = req.getContextPath();

                           res.sendRedirect(path+"/bbs01/view.jsp?num="+num);

                  }

         }

}

 

/bbs/bbs01/delete_confirm.jsp

 

<%@ page contentType="text/html;charset=euc-kr"

import="java.sql.*,net.java_school.util.*,net.java_school.db.dbpool.*" %>

<jsp:useBean id="dbmgr" scope="application"

class="net.java_school.db.dbpool.OracleConnectionManager" />

<%

Log log = new Log();

int num = Integer.parseInt(request.getParameter("num"));

Connection conn = null;

PreparedStatement prepare = null;

ResultSet rs = null;

String subject = null;

String sogae = null;

String query = "select subject,sogae from board where num ="+num;

try {

         conn = dbmgr.getConnection();

         prepare = conn.prepareStatement(query);

         rs = prepare.executeQuery();

 

         while(rs.next()){

         subject = rs.getString("subject");

         sogae = rs.getString("sogae");

         }

} catch(SQLException e){

         log.debug("Error Source:delete_confirm.jsp : SQLException");

         log.debug("SQLState : " + e.getSQLState());

         log.debug("Message : " + e.getMessage());

         log.debug("Oracle Error Code : " + e.getErrorCode());

         log.debug("Query : " + query);

} finally{

         try {

                  rs.close();

                  prepare.close();

                  dbmgr.freeConnection(conn);

                  log.close();

         } catch(SQLException e){}

}

%>

<html>

<body>

<center><h1>삭 제 확 인</h1></center>

<hr>

<table cellspacing="0" cellpadding="0" border="0" width="600" align="center">

<form name="frm" method="POST" action="../servlet/bbs01.BoardDeleter">

<input type="hidden" name="num" value="<%= num %>">

<tr>

         <td width="100" align="center">제목</td><td><input type="text" name="subject" size="45" value="<%= subject %>"></td>

</tr>

<tr>

         <td width="100" align="center">소개글</td><td><textarea name="sogae" rows="10" cols="60"><%= sogae %></textarea></td>

</tr>

<tr>

         <td colspan="2" align="center">위 글을 정말로 삭제하겠습니까?</td>

</tr>

<tr>

         <td colspan="2" align="center"><input type="submit" value="삭제">&nbsp;&nbsp;&nbsp;

<input type="button" value="취소" onClick="javascript:history.go(-1)"></td>

</tr>

</form>

</table>

</body>

</html>

 

/bbs/WEB-INF/classes/BoardDeleter.java

 

package bbs01;

 

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import java.sql.*;

import net.java_school.db.dbpool.*;

import net.java_school.util.*;

 

public class BoardDeleter extends HttpServlet {

         PreparedStatement prepare = null;

         OracleConnectionManager dbmgr = null;

         String query = "delete board where num=?";

 

         public void init()  throws ServletException {
           ServletContext sc = getServletContext();
           dbmgr = (OracleConnectionManager)sc.getAttribute("dbmgr");
         }
 

         public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

                  req.setCharacterEncoding("euc-kr");

                  Log log = new Log();

                  int num = Integer.parseInt(req.getParameter("num"));

                  Connection con = dbmgr.getConnection();

                  try{

                           prepare = con.prepareStatement(query);

                           prepare.setInt(1,num);

                           prepare.executeUpdate();    // 쿼리 실행

                  }catch(SQLException e){

                           log.debug("Error Source:BoardDeleter.java : SQLException");

                           log.debug("SQLState : " + e.getSQLState());

                           log.debug("Message : " + e.getMessage());

                           log.debug("Oracle Error Code : " + e.getErrorCode());

                           log.debug("Query : " + query);

                  } finally {

                           try {

                                   prepare.close();

                                   dbmgr.freeConnection(con);

                                   log.close();

                           } catch(SQLException e){}

                           String path = req.getContextPath();

                           res.sendRedirect(path+"/bbs01/list.jsp");

                  }

         }

}

<출처: http://cafe.naver.com/webprogramguide >

반응형

JSP 예제

 

[예제를 실습하기에 앞서 첨부 파일(example.zip)을 다운로드 한 후 /bbs 디렉토리에 압축을 풉니다.]

 

include 지시자를 이용한 파일 인클루드 예제

 

/bbs/ex1/main.jsp : main.jsp에서 include 지시자를 사용하여 디자인 페이지인 main.html 포함.

main.jsp 내에서 다음과 같이 main.html 페이지를 인클루드 합니다.

 

<%@ include file=”main.html” %>


유지 보수의 편리함 때문에 디자인 페이지를 include 지시자를 이용하여 포함시키는 경우가 많이 발생합니다.

이때 주의해야 할 사항은 인클루드 되는 파일(main.html)내에서의 링크는(<img src=..) 인클루드하는 파일(main.jsp)이 기준으로 해야 합니다는 점입니다.

이 예제에서 만약에 main.jsp 가 /bbs/ex1/ 에 위치하고 main.html 파일이 /bbs/ex1/inc 에 그리고 이미지 파일의 디렉토리가 /bbs/img 라고 가정한다면,

main.html 내에서의 이미지에 대한 링크는 <src img=../img/image.gif > 이어야지 <src img=../../img/image.gif>이면 안됩니다.

물론 main.html 만를 요청하면 main.html 내에서 이미지는 깨집니다. 하지만 깨지는 것이 정상입니다.

우리가 서비스하는 파일은 main.html 파일 단독이 아니기 때문입니다. main.jsp 가 웹 사이트가 방문자가 방문할 페이지이지 main.html 역시 단독으로 방문할 수 있는 페이지라면 디렉토리 구조부터 바꾸어야 합니다.

 

Form 태그의 텍스트필드를 이용해서 파라미터 값 전송 예제

 

main.jsp -> welcome.jsp

 

/bbs/ex2/main.jsp : main.jsp 에서 파라미터 값으로 uid, passwd를 welcome.jsp 에 전달

main.jsp 에서 <input type=”text” name=”uid”> 로 welcome.jsp 로 전송했다면 welcome.jsp에서는 사용자가 웹 브라우저를 통해 보내는 문자열 정보를 받는 코드 조각으로 받을 수 있습니다.


<% request.getParameter(“uid”) %>

 

파라미터 값을 자바빈에 설정하고 값 가져오기(set/get) 예제

 

/bbs/ex3/main.jsp 파일에서 디자인 파일 main.html 과 welcome.jsp 파일을 인클루드 하는데

welcome.jsp는 uid, passwd 값이 넘어 올때만 인클루드 됩니다. (main.jsp 소스를 확인해 보세요)

 

또한 이 예제에는 자바빈을 이용합니다.

이 때 자바빈을 어떻게 셋팅하고 자바빈에 저장된 값을 어떻게 가지고 오는지 확인합니다.


main.jsp 에서 아래와 같이 표준액션을 이용해서 자바빈을 생성합니다.

 

<%@ page import="kr.go.mnd.baby.*" info="Simple JSP" contentType="text/html; charset=euc-kr" %>

<jsp:useBean id="login" scope="page" class="kr.go.mnd.baby.User" />

<jsp:setProperty name="login" property="*"/>


여기서 <jsp:useBean>은 main.jsp 가 요청되면 id가 login이고 scope가 page인 객체를 찾아서 레퍼런스를 리턴하려 합니다.

그런데 찾는 객체가 없으면 객체를 생성합니다.

객체를 생성할 때는 페이지 지시자의 임포트된 팩키지에서 클래스를 찾아서 생성하게 됩니다.

두번째 표준액션인 <jsp:setProperty>는 넘어온 파라미터값으로 자바빈의 set 메소드를 호출하여 값을 저장하는 액션인데 처음 main.jsp를 호출하게 되면 아무런 작용도 하지 않는다.

main.jsp 에서 uid, passwd를 값을 자기 자신인 main.jsp에 넘기게 되면

 

<jsp:setProperty name="login" property="*"/>

 

의해 이미 생성한 User 자바빈의 setUid() 메소드와 setPasswd() 메소드를 호출하게 되어 자바빈을 설정하게 됩니다.

 

main.jsp

main.jsp

User.java

<input type=”text” name=”uid”>

<jsp:setProperty name=”login” property=”uid”>

setUid(String userid)


여기서 자바빈의 setUid 메소드에서 Uid가 대문자인 것에 의구심을 가질 수도 있는데

이는 자바 언어의 메소드 이름 규칙에 의한 것입니다.

자바 언어는 메소드나 변수의 이름을 작성할 때 첫 글자는 소문자로 시작하고 여러 단어가 붙을 때는 단어의 첫글자는 대문자로 함을 권고합니다.

클래스의 작명 규칙은 첫 글자는 대문자인 것만 빼고 메소드 작명 규칙과 같습니다.

실습 : 아래 자바빈은 /bbs/ex3/main.jsp 를 실행하기 위해서 필요한 자바빈입니다. 작성한 후 /bbs/WEB-INF/claases 에 넣고

컴파일하면 /bbs/ex3/main.jsp 가 실행이 됩니다.

 

package kr.go.mnd.baby;

public class User {

         String uid;

         String passwd;

         public User(){

                  uid = null;

                  passwd = null;

         }

         public String getUid(){

                  return uid;

         }

         public String getPasswd(){

                  return passwd;

         }

         public void setUid(String uid){

                  this.uid = uid;

         }

         public void setPasswd(String passwd){

                  this.passwd = passwd;

         }

}

 

 

사용자 유틸리티 클래스 사용 예제

 

파일 : /bbs/ex4/main.jsp, welcome.jsp

 

위 예제에서 uid 값에 한글을 넣고 테스트하면 한글이 깨집니다.

이를 보완하기 위해서 사용자 유틸리티 클래스를 만들어 사용해 봅니다.


/bbs/ex4/main.jsp -> welcome.jsp 로 uid 값을 한글로 보낼 때 welcome.jsp 에서 한글이 제대로 보이도록 하는 것이 목적입니다.


JSP 엔진에 따라서 입력된 한글이 깨져 나올 수 있습니다.

한글 처리가 미흡한 경우에는 코드에서 처리해 주어야 합니다.

JSP 내에서 한글을 처리하기 위해서는 입력 받은 한글을 euc-kr 이나 KSC5601 문자 세트로 변환합니다.

(이는 JSP엔진이나 OS에 따라서 달라질 수 있습니다. 여기서는 KSC5601로 변환했습니다)

데이터베이스에 한글을 삽입할 때도 문제가 생기면 같은 방법을 사용합니다.

 

아래 사용자 유틸리티 클래스를 작성하여 예제 실현


*** Hanguel.java ***

 

package kr.go.mnd.util;

 

import java.io.UnsupportedEncodingException;

public class Hanguel {

    public Hanguel() {}   

         public static String toHanguel(String s) {

                  try {

                           if (s != null)

                                   return (new String(s.getBytes("8859_1"),"KSC5601"));

                           return s;

                  } catch (UnsupportedEncodingException e) {

                           return "Encoding Error";

                  }

    }

}

 

welcome.jsp 에서 기존의 코드 request.getParameter("uid"); 다음과 같이 바꿉니다.

 

Hanguel.toHanguel(request.getParameter("uid"));

 

Hanguel 클래스의 toHanguel메소드를 사용하기 위해서는 welcome.jsp 의 페이지 지시자에서 다음과 같은 해당 클래스를 임포트해주어야 합니다.

 

<%@ page import="kr.go.mnd.util.Hanguel" %>


여기서 주의해야 것은 welcome.jsp 내에서 Hanguel 클래스의 객체 생성하지도 않고 바로 toHanguel 메소드를 사용할 있었다는 것입니다.

이는 Hanguel 클래스의 toHanguel 메소드가 static으로 선언된 클래스 메소드이기 때문에 객체 생성 없이 해당 메소드의 호출이 가능하기 때문입니다. (자바에 대한 기초 내용입니다)


welcome.jsp 파일을 아래와 같이 고치고 테스트해 봅니다.


<%@ page import="kr.go.mnd.util.Hanguel" %>
<center>
<h1>Hello,
<%
out.println(Hanguel.toHanguel(request.getParameter("uid")));
%>
</h1>
</center>

한글을 위해서 위와 같이 사용자 유틸리티 클래스를 이용하는 것대신에 request 의 setCharacterEncoding 메소드를 이용해도 됩니다.

Welcome.jsp 내에서 아래와 같이 사용합니다.


<%

request.setCharacterEncoding("euc-kr");

%>


setCharacterEncodeing 메소드는 최근 자바 버전부터 생긴 메소드로 이전의 웹 애플리케이션을 보면 거의다 위와 유사한 한글 컨버터 사용자 유틸리티 클래스를 쓰고 있습니다.

또한 문서에 의하면 setCharacterEncodeing 메소드가 아직 버그가 있어서 함께 혼용하여 사용하는 것이 낫다고 합니다.


이번에 다운로드 한 그대로의 welcome.jsp 파일를 이용하여 한글을 나오도록 해 보겠습니다.


<center>
<h1>Hello,
<jsp:getProperty name="login" property="uid" />
</h1>
</center>


위 파일이 원래의 welcome.jsp 파일입니다.


자바 빈을 이용하는 것이므로 자바 빈의 코드 안에 위에서 만든 Hanguel 사용자 유틸리티 클래스를 이용하도록 코드를 고칩니다.

아래와 같이 User 빈이 Hanguel 사용자 유틸리티 클래스를 이용하도록 고치고 테스트 합니다.


User.java


package kr.go.mnd.baby;


import kr.go.mnd.util.Hanguel;


public class User {
    String uid;
    String passwd;

    public User() {
        uid = null;
        passwd = null;
    }

    public String getUid() {
        return uid;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setUid(String uid) {
        this.uid = Hanguel.toHanguel(uid);
    }

    public void setPasswd(String passwd) {
        this.passwd = Hanguel.toHanguel(passwd);
    }
}

<출처: http://cafe.naver.com/webprogramguide/>

+ Recent posts