이름 기반의 유연한 객체구조를 이용하여 순차적인 데이터를 개발자가 생성하지 않고 setValue만을 호출하여 데이터처리가 가능하도록 만든 구조로 놀새가 많이 사용할 것 같아 올립니다. ( 2004/01/29 ) 144
Written by ienvyou - 최지웅
1 of 1
 


지금 하는 곳뿐만이 아니라 다른곳에서 상당히 많이 사용할 것 같아서 미리 올려둡니다.
쓰실수 있으면 쓰시구요.
Dependency를 가진 Value Object(VO)의 형태가 아니라 여러가지 형태의 데이터를 이름값에
의하여 처리가 되도록 jolt package내용을 참조하여 만들었습니다.

▶ AsiDataSet

그냥 이름하여 AsiDataSet이라고 붙였습니다  ASI(Application System Integration)이라고 
뭐 어쩌구 저쩌구 하는게 있는데요. 거기서 사용할려고 만들었다가 분명 나중에 또 쓰이게 될것같아서
잃어버릴까봐 올립니다.

어디다가 쓰려고 했냐면 메시지 포맷팅(자리수 맞추고, 순서맞추고 등등) 과 work flow처리, 
transaction처리, rule처리등을 위하여 하나의 객체를 사용하도록 하려한겁니다.

이걸 사용하는 곳은 5개의 서로다른 업무계 시스템(WAS, Tuxedo)이며 AsiDataSet에 
다른 시스템(Tuxedo, ELink, TCP/IP Socket Adapter, FTP, MQ Series, Soap)에 대한 타켓정보를
물고 들어오면 실제 DataSet에 있는 데이터를 각각의 대외시스템에 맞게끔 조합하여 처리될 수 있도록
만든 자동화 클래스의 핵심 데이터를 물고 있습니다.




/*
 * @(#)AsiDataSet.java	1.0 04/01/12
 *
 * Copyright (C) The Javapattern.info All rights reserved.
 *
 */

package com.javapattern.pool;

import java.util.Map;
import java.util.Hashtable;
import java.util.Vector;
import java.util.ArrayList;

/**
* WLI에 요청을 하게 되는 request parameter data set을 정의한다.
* Member Variable은 _를 이용하여 해당 전역변수임을 알아볼수 있도록 하였다
* 각 파라미터를 이용하여 데이터를 세팅할 수 있도록 처리하며 하나의 String index에 
* 다수개의 value값이 순차적으로 들어갈수 있는
* 형태로 선택하여 만들었다. 즉 name-value의 pair가 반드시 이루어지는 것이 아니라 1-n의 유연한 구조로서
* 데이터를 핸들링하도록 하며, 파라미터에 값이 유동적으로 처리되어질 수 있는 특징을 가지게 한다.
* 궁금한 사항이 있으면 아래 메일로 메일보내기.
*
* @version 0.0a
* @author Choi Ji Woong
*/
public class AsiDataSet extends Hashtable implements java.io.Serializable {
    protected boolean unrestricted;

    private String _svcName;

    // to do : Constructor here
    /**
    *	AsiDataSet 생성자로서 생성인수가 없으면 초기의 10의 크기로 데이터셋을 만들어제공하도록 한다.
    */
    public AsiDataSet(){
        super(10);
        unrestricted = true;
    }

    /**
    *	기본적으로 application요청에 의하여 생성하도록 한다.
    *	@param i 초기 DataSet size
    */
    public AsiDataSet(int i)	{
        super(i);
        unrestricted = true;
    }

    protected AsiDataSet(int i, boolean flag)	{
        super(i);
        unrestricted = flag;
    }

    /**
    * Service 명 설정
    * @param	svc_name	Tuxedo Service Name
    */
    public void setSvcName(String svcName){
        this._svcName = svcName;
    }

    /**
    *	Service명의 반환
    *	@return _svcName 서비스의 실제 이름
    */
    public String getSvcName() {
        return this._svcName;
    }


    /**
    *	Result를 통하여 결과값을 얻어가도록 처리한다.
    *	@param s value값에 대한 유일한 식별자
    *	@param i 객체의 위치
    *	@param obj default객체
    *	@return 결과반환값
    */
    public Object getValue(String s, int i, Object obj)	{
        Vector vector = (Vector)super.get(s);
        if(vector == null)	 return obj;
        try {
            return vector.elementAt(i);
        } catch(ArrayIndexOutOfBoundsException ex) {
            return obj;
        }
    }

    /**
    *	Result를 통하여 결과값을 얻어가도록 처리한다.
    *	@param s value값에 대한 유일한 식별자
    *	@param i 객체의 위치
    *	@param obj default객체
    */
    public Object getValue(String s, Object obj) {
        return getValue(s, 0, obj);
    }

    /**
    *	Hashtable의 s값의 데이터를 put하도록 하며
    *	실제 그안의 데이터를 순차값을 가진 Vector를 사용한다
    */
    public void setValue(String s, int i, Object obj) {
        Vector vector = (Vector)get(s);
        if(vector == null) {
            vector = new Vector(1, 5);
            put(s, vector);
        }
        if(i >= vector.size()) vector.setSize(i + 1);
        vector.setElementAt(obj, i);
    }

    /**
    *	Hashtable의 s값의 데이터를 put하도록 하며
    *	실제 그안의 데이터를 순차값을 가진 Vector를 사용한다
    *	setValue(s, 0, obj를 호출한다.
    */
    public void setValue(String s, Object obj) {
        setValue(s, 0, obj);
    }

    /**
    *	s값에 의하여 저장된 데이터의 사이즈를 구하도록 한다.
    */
    public int getCount(String s) {
        Vector vector = (Vector)super.get(s);
        if(vector == null)
            return 0;
        else
            return vector.size();
    }

    /**
    *	확장여부를 확인한다.
    */
    protected boolean expanded() {
        return true;
    }

}
이제 위의 클래스를 이용하여 legacy system과 연동후 결과값에 대한 클래스가 필요하겠네요 그 결과는 그냥 DataSet을 그대로 상속하여 클라이언트측에서도 같은 방법으로 데이터에 접근하도록 합니다.

/*
 * @(#)AsiResult.java	1.0 04/01/12
 *
 * Copyright (C) The Javapattern.info. All rights reserved.
 */

/**
* 이 클래스는 AsiDataSet의 기본적인 데이터와 값을 그래도 취합하여 사용할수 있도록
* 상속받고 있는 형태를 취한다.
* 결과값에 대한 application코드와 error 및 기타정보에 대한 내용을 담고 있도록 한다.
*
* @version 0.0a
* @author Choi Ji Woong
*/
public class AsiResult extends AsiDataSet implements  java.io.Serializable{
    // 다른 패키지 접근제한
    protected int applicationCode;
    protected static final int NO_ERROR = 0;
    protected static final int APPLICATION_ERROR = 1;
    protected static final int SYSTEM_ERROR = 2;

    // to do : Field mapping required
    protected AsiResult(int i) {
        super(i);
    }

    public int getApplicationCode() {
        return applicationCode;
    }
}
클라이언트는 Hashtable에 순차적인 List형태의 값을 저장하기 위하여 굳이 java.util.List계열의 클래스를 사용하지 않아도 된다. 그저 setValue(), getValue()메소드를 통하여 원하는 target시스템으로 data를 전송하고 받을뿐이다 위의 클래스를 테스트하는 클라이언트 코드를 보도록 하자.

public class TestClient extends Thread 
{
    public static void main(String [] args) throws Exception{
        TestClient x = new TestClient();
        x.start();
    }

    public void run() {
        try{
            AsiDataSet dataSet = new AsiDataSet();
            dataSet.setSvcName("MSS001");
            dataSet.setValue("NAME", "최지웅");
            dataSet.setValue("ADDRESS", "경기도 용인시 수지읍 동천동");
            dataSet.setValue("MOVIE", 0, "태극기휘날리며");
            dataSet.setValue("MOVIE", 1, "그녀를 모르면 간첩");
            dataSet.setValue("HOBBY", "술마시기");
            dataSet.setValue("E", "E");
            System.out.println(dataSet);
        }catch(Exception e) {
            e.printStackTrace();	
        }
    }
};
위에꺼 실행하면 아래처럼 나온다. {E=[E], NAME=[최지웅], MOVIE=[태극기휘날리며, 그녀를 모르면 간첩], HOBBY=[술마시기], ADDRESS=[경기도 용인시 수지읍 동천동]}

public class TestClient extends Thread 
{
    public static void main(String [] args) throws Exception{
        TestClient x = new TestClient();
        x.start();
    }

    public void run() {
        try{
            AsiDataSet dataSet = new AsiDataSet();
            dataSet.setSvcName("MSS001");
            dataSet.setValue("NAME", "최지웅");
            dataSet.setValue("ADDRESS", "경기도 용인시 수지읍 동천동");
            dataSet.setValue("MOVIE", 0, "태극기휘날리며");
            dataSet.setValue("MOVIE", 3, "그녀를 모르면 간첩");
            dataSet.setValue("HOBBY", "술마시기");
            dataSet.setValue("E", "E");
            System.out.println(dataSet);
        }catch(Exception e) {
            e.printStackTrace();	
        }
    }
};
위처럼 1을 3으로 바꾸고 실행하면 아래처럼 나온다. {E=[E], NAME=[최지웅], MOVIE=[태극기휘날리며, null, null, 그녀를 모르면 간첩], HOBBY=[술마시기], ADDRESS=[경기도 용인시 수지읍 동천동]} 이제부터는 호스트와 클라이언트사이의 인터페이스만 보고서도 우리는 핸들링 할수 있게 되었다.

크리에이티브 커먼즈 라이센스
Creative Commons License
2012/01/20 16:02 2012/01/20 16:02

2001년 6월에 작성된 글입니다.

※ Java Native Interface
JNI는 native method를 이용해서 C/C++의 code를 Java에서 이용할수 있는 방법이다.
native 메소드를 정의해서, 사용하는 방법에 대해 소개를 하겠습니다.

1. Example Java Source

C/C++의 code를 자바에서 불러 사용하려면, native method를 정의해야 하는데, 아래 코드와 같이 method body를 갖지않는 method를 native keyword를 통해 정의한다.
이것은 method의 body가 C/C++ code의 dll(Unix에서는 so) 파일로 되어 있다는 의미이다.
그래서 runtime에는 이 dll 파일을 메모리에 loading을 해야만 method를 실행할수가 있는데,
dll 파일을 loading하는 library는 System class의 loadLibrary() 메소드이다.
classpath경로에서 파라미터의 파일을 메모리에 loading을 하죠. 

public class Hello {

    public static native void print();

    static {
        System.loadLibrary("hello");
    }

    public static void main(String args[]) {
      Hello.print();
    }
}

compile
prompt>javac Hello.java

compile된 class를 실행하면 당연히 에러가 발생되겠죠. 다음과 같이.
prompt>java Hello
Exception in thread "main" java.lang.UnsatisfiedLinkError: no hello in java.libr
ary.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1312)
at java.lang.Runtime.loadLibrary0(Runtime.java:749)
at java.lang.System.loadLibrary(System.java:820)
at Hello.(Hello.java:6)

2. C/C++의 Header file 생성

native method를 C/C++로 구현 하기 위해서는 Java method 선언을 C/C++의 method선언으로 mapping시켜주어야 겠죠.  이것은 javah 라는  명령어를 이용합니다.
그래서, 다음과 같은 명령어로 C/C++의 Header 파일을 생성합니다.

prompt>javah -jni -classpath . -o Hello.h Hello

//생성된 header file : Hello.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class Hello */

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:     Hello
* Method:    print
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Hello_print
(JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

3. C code 생성을 위한 C/C++ compiler install

생성된 header file에 정의된 JNIEXPORT void JNICALL Java_Hello_print(JNIEnv *, jclass); 메소드를 구현하기 위해 Borland C++ Compiler5.5 를 다운 받아 설치합니다.
http://www.borland.com/downloads/
설치방법은 인스톨 파일을 실행하면, 설치 디렉토리에 readme file이 있는데, bcc32.cfg, ilink32.cfg 파일을  readme 파일의 내용과 같이 만들어 bin dir에 저장하면 끝납니다.

4. C source code

Hello.h 파일에 선언된 메소드를 다음과 같이 구현한다.

//hello.cpp file
#include "Hello.h"

JNIEXPORT void JNICALL Java_Hello_print
(JNIEnv *, jclass)
{
    printf("Hello World");
}

5. compile and dll file 생성

다음과 같은 명령어로 compile해서 hello.dll 파일을 생성한다.

prompt>bcc32 -c -Id:\java\jdk1.3\include -Id:\java\jdk1.3\include\win32 hello.cpp
prompt>bcc32 -tWD hello.obj

6. 실행
hello.dll 파일이 현재 디렉토리에 만들어 졌기 때문에, System.loadLibrary()로 메모리에 loading할수 있고, print() 라는 native method가 실행될수 있겠죠.

prompt>java Hello
Hello World


 

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/07/23 10:16 2008/07/23 10:16

사용자 삽입 이미지
자~ 지금까지는 푸념이었고 오늘 쓰게 되는 아티클은 보통 사이트에서 패스워드를 저장하려고 할때 암호화해서 넣는 방법중의 MD5(Message Digest)를 자바로 구현하여 집어넣는 것을 만들려고 합니다. 보통의 금융권의 사이트들이나 일반사이트들은 금융감독원에서 ISMS라고 해서 보안정책을 하달하여 "이것대로 안만들면 죽는줄 알어~"라는 지침이 하달되는데 그 기준을 맞추기 위하여 이미 아래의 아티클에서 설명이 되었었던 암호화등의 방식을 적용한 H/W or S/W를 설치하여 그 기준에 부합하게 적용을 하게 됩니다.

위에서 이야기한 MD5는 또 어디서 봤을까요? 어? 못봤어요? 리눅스에 조금만 관심이 있다던지 시스템을 조금 안다고 한다면 보통의 unix운영체제에서 MD어쩌구 저쩌구 라고 보셨을텐데..
아구~ 만약 못보았다면 공부좀 열심히 하셔야 겠습니다.

그럼 아파치에서 사용자 인증을 위한 htpasswd는 보셨나요? 디렉토리에 user리스트등을 걸어서 해당 사용자에 대한 접근을 시도하도록 하는 거 말이죠~ 어라? 이것도 구경못해봤다구요?

홍.... 그럼 진짜 쌍코피터지게 공부하셔야 겠군요. 하기사 이런것 몰라도 먹구 사는데 지장은 없습니다만 그래도 안다면 어디가서 아는 척은 할수도 있겠지요.

자 그럼 대체 MD5가 무엇인지를 알고서 넘어가야 왜 쓰는지를 알수 있겠지요? 알아봅시다.

▶ MD2, MD4, MD5란 무엇인가?

MD2, MD4, MD5는 Rivest가 개발한 메시지 요약 알고리즘이며, 128비트의 메시지 요약을 생성해냅니다. MD2는 8비트 컴퓨터용으로 최적화되었고, Md4와 Md5는 32비트 컴퓨터용으로 되어있다는 특징을 가지고 있습니다. 얘네들 같은 경우는 대표적인 해쉬함수측에 포함되어져 있는데, 해쉬함수란놈은 자바에서처럼  스트링을 일정한 길이의 해쉬코드로 출력을 내보내게 되는데 얘들은 두가지 성질을 만족해야 하는 특징을 가지고 있습니다.
첫째로 만들어진 해쉬코드를 보고 해당 해쉬코드를 만들었던 스트링을 찾아내지 못해야 하며, 두번째는 주어진 스트링에 대하여 같은 해쉬코드를 생성하는 또 다른 스트링을 찾아내지 못해야
한다는 것입니다.

보통의 해쉬함수들은 SNEFRU, MD2, MD4, MD5, SHA등등이 있는데 MD4가 1992년에 아주 똑똑한 사람에 의하여 해독이 되어 버려서 MD5로 비트길이를 발전시키기에 이르렀다네요..SHA같은 경우도 MD4에서 파생된 160비트 해쉬코드를 만드는 놈인데 보면 대략 MD5와 비슷한 형태를 가지고 있습니다.

MD5같은 경우 사실 SHA보다 32비트가 작은 128비트를 가지고 있지만 빠르고, 쉬우며, 간결하다는데 특징을  둘수 있겠네요. 알려진 결과로는 SHA보다 MD5의 수행성능이 약 25%정도 빠르다는 특징을 가지고 있습니다.

SHA같은 경우는 SMTP서버나 기타등등의 데몬등에서 사용자에 대한 내용등을 옵션으로 선택하여 사용할수도 있게하고 있죠.

▶ 자바에서의 MD5의 사용

어떤 방식의 해쉬결과가 나오는지 알고 싶다면 옆에 있는 컴퓨터중에 linux같은게 있으면 열어보세요. 콘솔로 /etc/passwd파일을 열어서 해당 사용자들에 대한 패스워드가 어떻게 입력이 되어져 있는지 보세요. cbWWy0LuJU90FgQ9GE/JcA== 등등의 문자로 장식을 하고 있는 게 보일겁니다.

자 이제 그러한 해쉬함수를 이용한 결과값을 자바측에서 얻어낼 것인데, 기본적인 MD5알고리즘을 이용하여 자바코드를 짜게 된다면 약 400라인정도의 byte연산을 통하여 결과값을 얻어낼 수
있습니다. 하지만 알고리즘 이해하고 그걸 짜실랍니까?
보통의 인터넷을 뒤진다고 했을때 지금 여러분은 빨리 찾고 빨리 적용하고 싶어서이기 때문에 API를 이용하여 만들어내는 방법을 더 선호하겠죠~ 연구자 기질이 있다면 그 원리를 알고 싶어할지는 몰라도 말이죠~

시작해보죠~
자바에서 java.security패키지에 보면 여러가지 보안에 관련된 클래스를 사용할 수 있도록 구성되어 있습니다. 거기에 MessageDigest클래스가 해당 알고리즘을 이용하여 digest를 할 수 있도록 구성해줍니다.

보통의 Util을 만든다하면 개발되는 프레임웍의 util package에 위치할 것이기 때문에 공통으로
사용할 수 있도록 클래스를 구성합니다

▶ SecurityUtil.java

import java.security.*;

public class  SecurityUtil {

    /**
     * byte[] ret = HashUtil.digest("MD5", "abcd".getBytes());
     *  처럼 호출
     */
    public static byte[] digest(String alg, byte[] input) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance(alg);
        return md.digest(input);
    }

    public static String getCryptoMD5String(String inputValue) throws Exception {
        if( inputValue == null ) throw new Exception("Can't conver to Message Digest 5 String value!!");
        byte[] ret = digest("MD5", inputValue.getBytes());
        String result = Base64Util.encode(ret);   
        return result;
    }
}

위의 클래스에 해당 스트링값을 던지게 되면 실제 digest된 결과 문자열을 되돌려 줄수 있도록 처리하였는데 이것이 끝이냐~ 아닙니다. 실제 던져진 해쉬함수에 의한 결과를 System.out으로 찍게 되면 찌그러진 코드형태로 나오게 되는데 이것을 우리 눈으로 비교하여 String문자비교를 통하여 추후 사용할 수 있도록 하려면 Base64 인코딩을 시도해야 합니다.

그 유틸리티 클래스는 아래와 같습니다.

▶ Base64Util.java

import sun.misc.*;
import java.io.*;

/**
*

* Filename  : Base64Util.java
* Class : Base64Util
* Function : Base64 Encoding/Decoding을 수행하는 클래스
* Comment :
* History : 2000-08-16 2:48오후
*


* @version  1.0
* @author carouser
*/
public class Base64Util {

    public Base64Util() {}

    /**
     * Base64Encoding을 수행한다. binany in ascii out
     *
     * @param encodeBytes encoding할 byte array
     * @return encoding 된 String
     */
    public static String encode(byte[] encodeBytes) {
       
        BASE64Encoder base64Encoder = new BASE64Encoder();
        ByteArrayInputStream bin = new ByteArrayInputStream(encodeBytes);
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        byte[] buf = null;

        try{
            base64Encoder.encodeBuffer(bin, bout);
        } catch(Exception e) {
            System.out.println("Exception");
            e.printStackTrace();
        }
        buf = bout.toByteArray();
        return new String(buf).trim();
    }

    /**
     * Base64Decoding 수행한다. binany out ascii in
     *
     * @param strDecode decoding할 String
     * @return decoding 된 byte array
     */
    public static byte[] decode(String strDecode) {
       
        BASE64Decoder base64Decoder = new BASE64Decoder();
        ByteArrayInputStream bin = new ByteArrayInputStream(strDecode.getBytes());
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        byte[] buf = null;

        try {
            base64Decoder.decodeBuffer(bin, bout);
        } catch(Exception e) {
            System.out.println("Exception");
            e.printStackTrace();
        }

        buf = bout.toByteArray();

        return buf;

    }
}


String passwd = SecurityUtil.getCryptoMD5String("Carouser");

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/03/26 14:55 2008/03/26 14:55

사용자 삽입 이미지
▶ UDP(User Datagram Protocol)

UDP(User Datagram Protocol)는 인터넷에 있어 datagram을 전송하는 전송층 프로토콜인 것은
사실이지만 TCP 처럼 그 사양이 엄격하게 정의된 것은 아닙니다.
UDP 는 비연결기반형인데, 이러한 비연결성(connectionless) 은 IP 프로토콜의 특징이기도 합니다. 또한 UDP 의 데이터 패킷인 데이터그램 패킷은 그 헤더에 소스 포트(로컬 포트)와 목적지 포트(원거리 포트) 그리고 데이터그램 패킷 길이, 체크섬 정도만 갖습니다. 그런데 자바의 데이크그램 패킷은 IP 어드레스정보도 포함하고 있어 데이터그램 패킷에 대한 IP 패킷에 더 가깝습니다.

<표 1>   UDP Datagram 형식
-------------------------------
Source Port(로컬포트)       
Destination Port(원거리포트)
Length Checksum
Data


▶ TCP 프로그래밍과 데이터 프로그래밍의 차이

<표 2>
                                  | TCP 프로그래밍                      | Datagram 프로그래밍
소켓의 종류 접속 요청용 | ServerSocket 통신용 : Socket   | 구분없이 DatagramSocket만
사용 전달 데이터 유형    | 미가공 데이터                         | Datagram Packet

▶ 소켓의 종류

TCP 프로그래밍에서는 클라이언트로부터의 접속요청을 듣는 서버 소켓(ServerSocket 개체)과
클라이언트와 상호 통신을 담당하는 클라이언트 소켓(Socket 개체)이 구분되어 있었습니다. 
그러나 데이터그램 프로그래밍에서는 서버소켓이 따로 없고 클라이언트 소켓에 해당하는DatagramSocket 개체만이 있습니다. 이는 당연한 것이 UDP 프로그래밍은 비연결형이므로 접속을 당당할 서버소켓이 필요없는 것입니다. 그러니까 데이터그램 프로그래밍에서는 클라이언트의 접속을 허용하고 어쩌고 할 것 없이 데이터그램 소켓 하나로 무조건 데이터그램 패킷을 읽고 씁니다. 물론 보안관리자가 없을 때 그렇습니다.
보안관리자가 설치되어 있으면 보안 검사 정도는 합니다.

▶ 전달 데이터 유형

TCP 프로그래밍(혹은 소켓 프로그래밍)에서는 전달하는 데이터가 우리가 프로그래밍에서 일상적으로 다루는 유형과 다를 바 없었습니다. 즉, 입출력스트림이 다룰 수 있는 데이터라면 TCP 프로그래밍에서도 전송할 수 있었습니다. 이 말이 TCP에서 전달 데이터 유형이 그렇다는 것이 아니라 TCP 애플리케이션에서 그렇다는 것입니다. 우리가 TCP 애플리케이션에서 다른 것은 신경쓰지 않고 출력스트림으로 일반 데이터를 보내면, 그 아래층인 TCP 및 IP 층을 통과하면서 패킷화됩니다. 그러니까 TCP 프로그래밍은 철저하게 애플리케이션층만 담당합니다.

그러나 데이터그램 프로그래밍에서는 미가공 데이터를 그대로 보내지 않고, 먼저 데이터그램 패킷으로 변환한 뒤 이를 입출력스트림을 통해 전송합니다. 그런데 자바의 데이터그램 패킷은 순수한 데이터그램 패킷이 아닌 IP 패킷 특성도 포함한 그런 것입니다. 그러니까 자바 데이터그램
프로그래밍은 애플리케이션층만 담당하는 것이 아니라 그 아래층인 전송층, 심지어는
네트웍층까지도 직접 건드립니다.


▶DatagramSocket 클래스 정의
DatagramSocket 클래스- DatagramPacket 객체를 전송하고 수신하는데 사용할 수 있는 데이터그램 소켓 클래스 입니다.

 * 생성자

DatagramSocket() throws SocketException
 - 포트 번호가 임으로 정해진 상태의 DatagramSocket 객체를 생성

DatagramSocket(int port) throws SocketException
 - 매개변수로 들어온 port에서 대기하는 DatagramSocket 객체를 생성

DatagramSocket(int port, InetAddress Iaddr)  throws SocketException
- 매개변수로 들어온 port에서 대기하는 DatagramSocket 객체를 생성하여 해당 IP 어드레스에 바인딩 합니다.이 생성자는 다중 IP 어드레스를 갖는 호스트에서 사용합니다.

▶ DatagramPacket 클래스 정의

DatagramPacket은 UDP 통신에서 데이터의 패킷을 만들어 DatagramSocket을 이용해서 송,수신할 수 있는 기능을 제공합니다.

public DatagramPacket(byte buffer[ ], int length)

- 데이터를 받기 위해  DatagramPacket 객체를 만드는 생성자
- buffer[ ]: 데이터가 위치해 있는 바이트 배열
- length: 버퍼의 길이


public DatagramPacket(byte buffer[ ], int length, InetAddress ia, int port)

- 다른 호스트로 전송할 DatagramPacket 생성하는 생성자
- buffer[ ]: 전송할 데이터의 바이트 배열
- length: 버퍼의 길이
- InetAddress ia: 패킷의 목적지 호스트 주소
- Port: 목적지 포트 번호
 
▶ Datagram Server작성예


 

import java.net.*;

public class DaytimeServer {
  public static final int DEFAULT_PORT = 13;
  public static void main (String[] args) throws IOException {
    if (args.length > 1)
      throw new IllegalArgumentException ("Syntax: DaytimeServer []");
    // 사용자 입력 포트의 DatagramSocket 생성 입력값이 없을 경우 Default Port 13
    DatagramSocket socket = new DatagramSocket
      (args.length == 0 ? DEFAULT_PORT : Integer.parseInt (args[0]));
    DatagramPacket packet = new DatagramPacket (new byte[1], 1); // DatagramPacket 생성

    while (true) {
      socket.receive (packet); // 소켓으로부터 패킷을 받음
      System.out.println
        ("Received from: " + packet.getAddress () + ":" + packet.getPort ());
    
      byte[] outBuffer = new java.util.Date ().toString ()
        .getBytes ("latin1"); // 패킷 정보를 표시
      packet.setData (outBuffer);
      packet.setLength (outBuffer.length);
      socket.send (packet);
    }
  }
}


데이터임 서버는 명령행 인자로 설정된 포트에서 대기하면서, 들어오는 모든 패킷에 대해 현재 시간을 담아 클라이언트에 응답합니다. 패킷 수신과 응답에는 하나의 스레드만 사용하였습니다.

Main()메소드에서는 우선 인자로 설정된 포트 번호가 제대로 된 것인지 검증하고, 그렇지 않은경우 디폴드 포트 번호인 13을 사용합니다.그리고 DatagramSocket타입의 Socket을 생성하고 이 포트에서 대기시킨 후 UDP 패킷을 받을 때 사용할 DatagramPacket 타입의 Packet을 생성합니다. 

이 프로그램의 핵심인 루프 부분에서는 receive()를 호출하여 패킷이 오기를 기다리며, 패킷이 오면 이 패킷을 보낸 호스트 정보를 출력한 후에 현재의 날짜와 시간 정보가 담긴 바이트 배열인 outBuffer를 새로 만듭니다. packet 객체의 setData()와 setLength()를 사용하여 receive()로 받은 패킷에 시간정보를 넣습니다.

마지막으로 send()메소드를 호출하여 원래의 호스트로 packet을 돌려 보냅니다.

== 참고 ==
packet에는 원래 호스트 주소와 포트번호가 이미 들어 있기 때문에, packet정보는 그냥 놔두어도 됩니다

▶ Datagram Client 작성예


 

import java.net.*;

public class DaytimeClient {
  public static void main (String[] args) throws IOException {
    if ((args.length != 1))
      throw new IllegalArgumentException
        ("Syntax: DaytimeClient [:]");
 
    int idx = args[0].indexOf (":");
    int port = (idx > -1) ? Integer.parseInt (args[0].substring (idx + 1))
      : DaytimeServer.DEFAULT_PORT;
    String hostName = (idx > -1) ? args[0].substring (0, idx) : args[0];
    InetAddress host = InetAddress.getByName (hostName);
 
    DatagramSocket socket = new DatagramSocket ();
    socket.setSoTimeout (5000);
    DatagramPacket packet = new DatagramPacket (new byte[256], 1, host, port);
    socket.send (packet);
    packet.setLength (packet.getData ().length);
    socket.receive (packet);
    socket.close ();
 
    byte[] data = packet.getData ();
    int length = packet.getLength ();
    System.out.println (new String (data, 0, length, "latin1"));
  }
}}


DaytimeClient 는 DaytimeServer를 테스트 하기 위한 것입니다. 매개변수로 설정된 컴퓨터 포트에 패킷을 보내고, 응답을 기다리고, 받은 응답 정보의 내용을 출력하는 것이 전부입니다.

Main()메소드 에서는 호스트 주소인 host와 포트 번호를 검사하여, 역시 포트번호가 주어지지 않으면  디폴트 포트 번호를 사용합니다.

패킷을 보낼 준비를 하기 위해 5초의 수신 타임아웃이 설정된 DatagramSocket타입의 소켓을 생성하고 테이터 길이가 1이고 리모트 서버의 주소가 담긴 256바이트의 버퍼를 넣어 DatagramPacket 타입의 패킷을 생성합니다.

Send()를 통해 보내지는 패킷은 단 한 바이트를 보내지만 응답을 받기에는 충분합니다.
패킷을 보내고 나면 즉시 이 패킷의 페이로드를 256으로 재설정하여 응답을 기다리기 때문에 받아들일 수 있는 응답 정보의 길이는 최대 256 이 됩니다.

만약 수신시간 5초를 넘기면 InterruptedIOException예외와 함께 프로그램 실행이 바로 멈춥니다. 

간단하게 Datagram에 대하여 알아보았으며, 다음 아티클은 멀티캐스트에 대하여 알아보도롭 합니다.

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/03/26 14:23 2008/03/26 14:23

사용자 삽입 이미지
---------------------------------------
4. MySql은 어떻게 실행시키나요?
---------------------------------------
시작은 mysql설치 디렉토리 아래bin에(C:\mysql\bin) 가서  mysqld.exe 를 실행.
dos창이 잠시 보였다가 없어짐.
( 놀라지마세요. CTRL-ALT-DEL을눌러보면
mysqld-opt.exe(win95/98)나 mysqld-nt.exe(winnt/2000)가 실행중 )

확인법:
prompt> cd c:\mysql\bin
prompt> mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2 to server version: 3.23.21-beta

Type 'help' for help.

mysql>

c:\mysql\bin\mysql.exe는 mysql용 client program
이러한 메세지가 뜨면 정상적으로 MySql서버가 동작중임.
그리고 간단한 명령어 몇개를 입력해보죠.

mysql> show databases;      --> 기본적으로 만들어져있는 database 이름 출력
+----------+
| Database |
+----------+
| mysql    |
| test     |
+----------+
2 rows in set (0.00 sec)

mysql> select user();       --> 로긴한 사용자 정보 출력
+----------------+
| user()         |
+----------------+
| ODBC@localhost |
+----------------+
1 row in set (0.08 sec)

mysql>


위와같이 나오면 정상적인 설치임.
끝내려면 exit이라하면 됨.

mysql> exit;

---------------------------------------
5. Winnt/Win2000에서 서비스로 동작 시킬 수 있나요?
---------------------------------------

winnt/win2000에서는 mysql을 서비스로 등록가능.
방법 1 (command line ):  mysql설치 디렉토리 아래bin에 가면 mysqld.exe있음.

prompt> mysqld     -install  ( service로 등록 )
prompt> net start mysql   ( mysql 서비스 시작 )


prompt> net stop mysql ( mysql 서비스 중지 )
prompt> mysqld     -remove ( service에서 등록 해제 )

방법 2 (GUI) : c:\mysql\bin\win,ysqladmin.exe를 실행시킴.
그러면 userid와 암호를 묻는 화면이 뜰때 userid는 root, 암호는 sunedu21을 입력.
입력한 root는 기본적으로 제공되는 관리자용 id이고 암호는 없음. sunedu21을 입력하면
그걸로 자동 설정됨.

---------------------------------------
6. mysql.exe의 간단한 사용법을...
---------------------------------------

mysql.exe를 이용해서 다른 시스템의 database로 접속가능하고 다른 사용자로도 접속이
가능합니다.

mysql.exe옵션들

prompt> mysql [-h hostname] [-u userID] [-p암호] [database]

예:
prompt> mysql -h 203.239.XXX.XXX -u root -p sl300

-h 203.239.XXX.XXX  : 203.239.XXX.XXX으로 접속, 생략하면 기본값 127.0.0.1 이 사용됨.
-u root          : root사용자로 접속 -u옵션을 안주면 anonymous로 접속
-p               : 암호를 입력한후 접속. 암호를 묻는 프롬프트가 뜸.
                   암호가 있는 사용자로 접속시에는 반드시 사용.
                   만약 암호를 같이 입력하려면 암호를 -p옵션과 붙여 입력.(-ptiger)
database         : 사용할 database이름. 만약 생략하면 접속후 use명령으로
                   database를 지정해야됨. 기본제공 database name은 mysql,test 2개.ㄴ

---------------------------------------
7. MySql관련 GUI tool은 없나요?
---------------------------------------
MySql용 GUI tool은 대부분이 UNIX/LINUX용입니다.
다행히 3.23.21버전부터는 winmysqladmin.exe 라는 GUI tool이 포함되어 있읍니다.
( C:\mysql\bin\winmysqladmin.exe )
이 툴은 MySql서버를 start,stop시킬 수 있고, NT/2000에서는 service에 등록,해제도 가능합니다.
그리고 현재 접속하고있는 process의 갯수와 MySql관련 환경변수 정보와 설정정보까지
파악이 가능합니다.

이 툴은 win95/98/nt/2000에서 모두 동작합니다.
이명령어를 처음 실행하면 접속시 사용할 사용자id와 암호를 넣으라하는데 주로 root를 입력하고
암호는 아무거나 입력하면됩니다.
단 이때 입력한 암호는 자동으로 root의 암호로 인식이 됩니다. 반드시 기억해야겠죠?

그러면 시스템트레이에 신호등 아이콘이 생깁니다. 이걸 누르면 showme 와
winnt,win9X메뉴가 보이죠. winnt,win9X 메뉴를 선택하면 server start,stop tool의 shutdown이 보입니다.

현재 mysql server가 실행중이면 파란색불이 실행하지 않으면 빨간색 불이 들어옵니다.

만약 빨간색불(mysql서버가 중지중)일때 실행 시키려면 트레이에 있는 신호등 아이콘을 누르고
WinNT나Win9X를 선택, start the service 를 선택하면 신호등에 파라생 불이 들어옴.
즉 mysql서버가 시작했다는 말.

그외 관리용 명령어인 mysqladmin이 있음.
수동으로 실행시에는 mysqld.exe를 실행하면 되지만 수동으로 stop시키려면 mysqladmin을 실행.
물론 옵션과함께...
아래는 mysqladmin용 명령어임. 기능은 설명을 참조.

  create databasename   Create a new database
  drop databasename     Delete a database and all its tables
  extended-status       Gives an extended status message from the server
  flush-hosts           Flush all cached hosts
  flush-logs            Flush all logs
  flush-status          Clear status variables
  flush-tables          Flush all tables
  flush-threads         Flush the thread cache
  flush-privileges      Reload grant tables (same as reload)
  kill id,id,...        Kill mysql threads
  password new-password Change old password to new-password
  ping                  Check if mysqld is alive
  processlist           Show list of active threads in server
  reload                Reload grant tables
  refresh               Flush all tables and close and open logfiles
  shutdown              Take server down
  status                Gives a short status message from the server
  variables             Prints variables available
  version               Get version info from server

위 명령어중 특히 신경쓸것은 password관련.

설치후 반드시 root(MySQL DB)의 암호를설정할것. 
안하면 불행한일(DB삭제)이 발생할 지도...  

# mysqladmin -u root password 새암호

필요한 TABLE을 만드는 순서:
1. Database만들기
2. MySQL용 사용자 만들기
3. Table만들기

- javabrain용 DB 만들기(2가지 방법)
1.UNIX shell상에서 만들기
# mysqladmin -u root -p create javabrain

2.DB에 login후 만들기
mySQL에서제공하는 명령어중 mysqladmin은 관리용
mysql은 client용 명령어.

# mysql -u root -p mysql
mysql> create database javabrain;

** mysql 명령어 요약
mysql [-h DB서버IP] [-u DB용사용자ID] [-p[암호]] [Database] [< sql-script파일 ]
-p 만 부여하면  암호는 물어봄. 암호까지 주려면 -p암호(-p와 암호를 붙여씀)


- 새사용자등록
먼저 MySQL에 root로 login
# mysql -u root -p mysql

mysql> grant SELECT,insert,update,delete,create,drop
> on javabrain.*
> to scott@'localhost'
> identified by 'tiger';
mysql> grant SELECT,insert,update,delete,create,drop
> on javabrain.*
> to scott@'%'
> identified by 'tiger';
mysql> flush privileges;

또는

mysql> grant all privileges
> on javabrain.* to scott@'%'
> identified by 'tiger';

mysql> grant all privileges
> on javabrain.* to scott@localhost
> identified by 'tiger';
mysql> flush privileges;

scott 사용자를 등록할때 여러가지(3가지) 방법이 있는데 그중 하나를 씀
등록시 반드시 to scott@'localhost'와 to scott@'%'를 둘다 해줘야함
localhost와 %는 접속을 허용하는 hostname인데 localhost를 빼면 다른 시스템에서
network으로만 접근할 수 밖에 없음.
그리고 %란 모든호스트를 의미하기때문에 %를 지정해야지만 다른 시스템에서
network으로 MySQL로 접근가능( mysql -h hostIP또는hostname -u 사용자ID -p )

user생성후 반드시 DB를 reload 해야만 만든 사용자가 접속가능(아래명령어)

# mysqladmin -u root -p reload
또는
mysql> flush privileges;


접속해서 확인하기
# mysql -u scott -p javabrain  --> ㅤDB서버에서login

다른시스템에서
# mysql -h DB서버의IP주소 -u scott -p javabrain


필요한 테이블 만들기(customer,shares,stock)
-주의사항: mysql은 table name에서의 대소문자를 구분함
(이것은 MySQL이 설치된 OS를 따름. 즉 M$계열에서는 구분안함)

필요한 sql문을 파일(maketable.sql-확장자는 무관)로 만든후 shell상에서 아래
명령어 실행

# mysql -u scott -p javabrain < maketable.sql

확인
# mysql -u scott -p javabrain
mysql> show tables;
mysql> select * from customer;
mysql> select * from shares;
mysql> select * from stock;

- JDBC Driver 사용하기
MySQL용 JDBC Driver는 2가지가 있지만 여기서는 mm driver를 사용.

http://www.worldserver.com/mm.mysql/ 에 가서 최신 버전을 download할것
(mm.mysql-2.0.4-bin.jar)

가져온것을 적당한 곳에서 해제.

사용법:

Class.forName("org.gjt.mm.mysql.Driver");
String url="jdbc:mysql://127.0.0.1:3306/javabrain";
Connection conn = DriverManager.getConnection(url,"scott","tiger");



관련 명령어들.

# mysql -u root -p mysql
password:
mysql> select user();               --> 현재 어떤 user권한으로 접속하고 있는지.
mysql> show database;               -->데이터베이스 종류보기
mysql> show tables from mysql;      --> mysql상의 테이블 정보보기
mysql> show columns from customer;  --> customer 테이블 구조보기
mysql> show index from customer;    --> customer테이블의 인덱스보기
mysql> use mysql;                   --> 사용할 database를 변경
(기본적으로 MySQL에는 두개의 database-mysql,test-가 있음.
  mysql은 DBMS관리용 정보, test는 연습용
  그래서 mysql이란 database는 사용시 주의할것)


#########################################
#########################################
##
##   추 가 사 항
##
#########################################
#########################################

javabrain.sql 이라는 파일을 수행 시키면 SL-300을위한 DB를 위한 모든 내용이 자동 설정됨.

사용예)

mysql 서버를 가동시킨후 ( mysqld.exe )
prompt> mysql -u root -p <  javabrain.sql

이러면 아래 사항이 자동 생성됨
database이름  : javabrain
root암호   : javabrain
사용자 id  : scott
암호    : tiger
생성된table들  : customer, shares, stock

가끔씩 user부분이 적용이 안되는 경우에는 DB를 restart시키면 된다.

prompt> mysqladmin -u root -p shutdown   --> DB shutdown
prompt> mysqld

만약 암호를 바꾸고 싶으면  sql script내부에  .... identified by 다음부분이 암호이니
알아서 바꾸면 됨.  단 반드시 암호는 single quotation으로 묶어줄것.

예) 암호를 apple 로 하자면  .... identified by 'apple'


import java.sql.*;

public class MySQLJdbcTest {
   public static void main(String[] args) throws Exception{
      try{
        Class.forName("org.gjt.mm.mysql.Driver").newInstance();
        System.out.println("DriverLoading succeed..");

        String url="jdbc:mysql://127.0.0.1:3306/javabrain";
     //  만약 위의 url로 접속시 한글에 문제가 발생하면 아래 url을 써서 사용하도록 한다.

   //  String url="jdbc:mysql://127.0.0.1:3306/javabrain?useUnicode=true&characterEncoding=euc-kr";
        Connection conn =  DriverManager.getConnection(url,"scott","tiger");
        System.out.println("Connection succeed");
        Statement stmt = conn.createStatement();
        System.out.println("정보추출");
 
        ResultSet rs = stmt.executeQuery("show databases");
       while( rs.next() ) {
         System.out.println(rs.getString(1));
       }
       System.out.println("MySQL 환경 점검 종료");
       rs.close();
       stmt.close();
       conn.close();
      }catch(Exception e){
         System.out.println(e);
      }
    }
}

2003년 3월 작성.
크리에이티브 커먼즈 라이센스
Creative Commons License
2008/03/26 14:23 2008/03/26 14:23

사용자 삽입 이미지
시스템의 비정상종료시에(Ctrl+C) 애플리케이션이 무언가 처리후 종료되어야 할 경우 그 방안에 대하여 작성할 수 있는 방법과 샘플코드를 보여주도록 한다.

이번 아티클에서는 shutdown hook에 대한 글을 써보고자 한다. 우선 개요부터 말하자면 당신이 열나게 애플리케이션을 만들었다고 가정을 해보자. 사용자가 도스창에서 애플리케이션을 실행시키고 동작시키고 있는데, 이 사용자가 갑자기 이게 아니다 싶어서 애플리케이션을 죽이려고 한다. 근데 분명 내가 메뉴에 종료라는 항목을 하나 추가해주었거늘.. Ctrl+C를 눌러버리고야 말았다.

문제는 무었인고? 나는 해당사용자의 내용에 대한 backward기능을 주고 싶어서 그 사람이 하고 있는 행동에 대한 rollback가능한 로그를 쌓고 사용자가 애플리케이션을 종료시키면 그때 필요없는 데이터들을 모두 없애버리리라 마음을 먹고 있던 차였다.

그런데...

위의 같은 상황이 발생을 한다면 당신은 어떻게 할것인가?
서블릿 컨테이너나 Applet의 경우도 실제 라이프사이클에 의하여 동작하여 해당 클래스가 메모리에서 unload되어지는 시점에 destroy메소드들을 호출하도록 하고 있는데, 위의 ctrl+c라는 행위는 아예 daemon thread까지 "너 죽어랏~!" 하면서 보내버리는 케이스니 말이다.

만약 저러한 애플리케이션을 스윙으로 만든다면 actionPerformed 이벤트를 걸어서 어떻게든
처리를 하겠는데 일반 text based application의 경우는 더더욱이 문제가 아닐듯 싶다.

▶ 자바에서의 shutdown event의 두가지

자바에서 virtual machine이 shutdown을 인식하는 방법으로는 두가지가 있는데

1. 첫번째로는 애플리케이션이 System.exit()메소드를 통하여 정상적으로 종료하는 경우가 있고,
2. 두번째로는 사용자가 직접 Ctrl+C를 키보드에 입력함으로서 현재 실행중인 애플리케이션을
closing하지 않고 virtual machine을 비정상적으로 강제종료시키는 경우이다.

간단하게 shutdown hook을 작성할 수 있는 방법은 다음과 같다.

1. java.lang.Thread를 상속받는 클래스를 하나 작성한다.
2. Thread의 run메소드를 구현할 때 당신이 shutdown시에 처리해야 할 일들에 대하여 기술한다.
3. 해당 shutdown hook클래스를 생성한다.
4. Runtime클래스의 addShutdownHook()메소드를 이용하여 등록한다.
    Runtime.getRuntime().addShutdownHook(shutdownHook);

▶ ShutdownHook Sample정의

여기서 우선 애플리케이션을 만들 경우를 가정하자.
보통의 서블릿 컨테이너는 사용자에 대한 세션을 자신의 로컬파일로 저장하여 관리하고 있다.
레진같은 엔진을 보더라도 session이라는 디렉토리에 file serialize를 통하여 저장하고 엔진이 종료되거나 세션의 시간이 만료되게 되면 그 저장된 temporary파일들을 삭제하는 루틴을 가지고 있다. 그런데 만약에 그러한 엔진들이 비정상적인 종료에 해당 파일들을 삭제하지 못하고 쌓아두게 된다면, 또한 접속이 많은 그러한 서버였다면 파일시스템의 사이즈가 비대하게 불어나게 될 것은 불을 보듯 뻔한 일 아닌가?

놀새~의 샘플코드 가정을 비슷하다.
애플리케이션이 시작되면 사용자의 고유식별파일을 하나 만들어서 기록하고 있다가 어떠한 방법으로라도 프로그램이 종료되게 되면 그 파일을 삭제하도록 할것이다.

자 그럼 위의 순서에 의하여 하나하나 작성을 해보도록 하자.

▶ ShutdownHookDemo.java

import java.io.*;

    public class ShutdownHookDemo {
    private String dir = ".";
    private String filename = "session_tmp.txt";

    /**
    *  애플리케이션이 시작되면 shutdown을 생성한후 등록한다.
    */
    public void start() {
        System.out.println("Demo");
        ShutdownHook shutdownHook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }

    /**
    *  1. 실행의 순서는 먼저 tempoary파일의 생성
    *  2. Runtime클래스에 shutdown hook을 등록
    *  3. 사용자의 입력을 기다림(무한루프)
    */
    public static void main(String[] args) {
        ShutdownHookDemo demo = new ShutdownHookDemo();
        demo.setup();
        demo.start();

        try {
            while(true){
                System.in.read();
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }
    /**
    * ShutdownHook클래스를 inner클래스로 사용함으로서 ShutdownHookDemo클래스의
    * 멤버변수에 대한 참조를 가능하도록 만든다.
    */
    private class ShutdownHook extends Thread {
        public void run() {
            shutdown();
        }
    }

    /**
    *  Shutdown의 되는 시점에 생성되었던 temporary파일을 삭제한다.
    */
    private void shutdown() {
        System.out.println("start shutdown progress.. please wait");
        // delete the temp file
        File file = new File(dir, filename);

        if (file.exists()) {
            System.out.println("Deleting temporary file.");
            file.delete();
        }

    }
    /**
    *  프로그램최초 수행시에 임시파일을 생성해낸다.
    */
    private void setup(){
    // create a temp file
        File file = new File(dir, filename);

        try {
            System.out.println("Creating temporary file");
            file.createNewFile();
        }
        catch (IOException e) {
            System.out.println("Failed creating temporary file.");
        }
    }


    }


위에서 별반 다르게 볼건 없다. 프로그램이 시작되면

            while(true){
                System.in.read();
            }

처럼 무한루프를 돌며 사용자의 키보드 입력을 기다리게 되는데, ctrl+c가 입력되기 전에는 계속 입력만을 받게 된다.

실제 shutdown hook클래스는 아래와 같이 간단하게 만들수 있으며

    private class ShutdownHook extends Thread {
        public void run() {
            shutdown();
        }
    }

단순히 클래스가

    Runtime.getRuntime().addShutdownHook(shutdownHook);

에 의하여 등록만 되어 있으면 자동으로 처리되게 된다.


실제로 테스트를 해볼경우 프로그램을 수행시키는 시점에 session_tmp.txt파일이 디렉토리에 생기게 된다. 계속 테스트하다가 실제 ctrl+C를 키보드에 입력했을 경우 shutdown progress라는 메시지와 함께 해당 임시파일은 없어지게 될것이다.

간단하게 테스트 해볼 수 있으며 위의 프로그램을 조금만 응용해도 당신은 아주 훌륭한 애플리케이션을 만들수 있을 것이다.

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/03/25 09:08 2008/03/25 09:08

사용자 삽입 이미지
▶ JDK1.0 Security Model - Sandbox Model
JVM에 ClassLoader 객체는 사용되어지는 class file을 Memory로 Loading하는 작업을 한다.
Applet을 실행 시키는 JVM은 classpath(Browser내에 내장된 class Library)에서 Loading한 class와 외부로 부터 downloading한 class의 Naming Space를 다르게 관리를 한다.
외부로 부터 downloading된 class, 즉 Untrusted Code는 SecurityManager객체에 의해 활동이 제한이 되어 진다.
JDK1.0 Security Model은 Sandbox Model이라고 하는데, Untrusted Code에 대해서는 Naming Space를 다르게 관리 함으로써, SecurityManager객체에 의해 활동이 제한되는 Sandbox라는 Boundary 영역내에서만 활동하는 구조를 말한다.


▶ JDK1.1 Security Model - Sandbox Model + Signed Applet
JDK1.1 Security Model은 1.0의 Sandbox Model에 Signed 개념이 추가가 되었다.
Untrusted Code가 Sign만 되면, Sandbox boundary를 벗어나 local의 classpath에서 loading된 class처럼 활동할 수 있다. SecurityManager 객체의 제한은 않받는다는 것이지요.
지금 현재의 Browser는 JDK1.1의 Security Model을 사용하고 있다.


▶ JDK1.2 Security Model - Sandbox Model + Protection Domain
JDK1.2 Security Model은 JDK1.1 Security Model(Sandbox + Signed)에 Protection Domain개념을 추가시킨 구조이다.
1.1 version에서 Signed code는 1.2 version에서 Domain
으로 관리가 되고, 각 Domain별로 Sandbox boundary를 다르게 설정할 수가 있어, Sandbox boundary가 Domain별로 가변적이라는 특성을 갖게 된다.
Domain은 Signer와 Codebase에 의해 결정이 되고, client의 policy file에 각 Domain이 활동 가능한 Sandbox boundary를 설정하게끔 되어있다.
JDK1.2 version에서는 jdk1.2\jre\lib\security\ directory에 java.security file은 master security properties file을 제공하는데, 이것은 Security에 관련된 Configuration file이다. 이 file내에 Domain의 활동 영역을 설정하는 policy file을 지정할 수 있는 부분이 있다.

 # The default is to have a single system-wide policy file,
 # and a policy file in the user's home directory.
 policy.url.1=file:${java.home}/lib/security/java.policy
 policy.url.2=file:${user.home}/.java.policy
 # User Policy File
 policy.url.3=file:/D:/javaLab/signedApplet1.2/MyPrint.jp
 
 java.home : jre directory
 java.policy file : System policy file
 .java.policy file : user default policy file(c:\windows\ 생성)
 MyPrint.js file : user define policy file
java.policy file은 JDK1.2 version, JRE1.2 version을 Installation하면 ~jre/lib/security/ 자동 생성되고, .java.policy file은 c:\windows directory에 필요하다면 생성 할 수가 있다.
policy file은 editor tool로 작성가능하지만, JDK에서 제공하는 policytool로 edit하는 것이 편리하다. 일단 System policy file인 java.policy file의 내용은 아래와 같다.

// Standard extensions get all permissions by default

grant codeBase "file:${java.home}/lib/ext/-" {
 permission java.security.AllPermission;
};

// default permissions granted to all domains
// 모든 domain에 대해 System properties를 read 할 수 있는 permission을 설정하고 있다.
grant {
 // Allows any thread to stop itself using the java.lang.Thread.stop()
 // method that takes no argument.
 // Note that this permission is granted by default only to remain
 // backwards compatible.
 // It is strongly recommended that you either remove this permission
 // from this policy file or further restrict it to code sources
 // that you specify, because Thread.stop() is potentially unsafe.
 // See "
http://java.sun.com/notes" for more information.
 permission java.lang.RuntimePermission "stopThread";

 // allows anyone to listen on un-privileged ports
 permission java.net.SocketPermission "localhost:1024-", "listen";

 // "standard" properies that can be read by anyone

 permission java.util.PropertyPermission "java.version", "read";
 permission java.util.PropertyPermission "java.vendor", "read";
 permission java.util.PropertyPermission "java.vendor.url", "read";
 permission java.util.PropertyPermission "java.class.version", "read";
 permission java.util.PropertyPermission "os.name", "read";
 permission java.util.PropertyPermission "os.version", "read";
 permission java.util.PropertyPermission "os.arch", "read";
 permission java.util.PropertyPermission "file.separator", "read";
 permission java.util.PropertyPermission "path.separator", "read";
 permission java.util.PropertyPermission "line.separator", "read";

 permission java.util.PropertyPermission "java.specification.version", "read";
 permission java.util.PropertyPermission "java.specification.vendor", "read";
 permission java.util.PropertyPermission "java.specification.name", "read";

 permission java.util.PropertyPermission "java.vm.specification.version", "read";
 permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
 permission java.util.PropertyPermission "java.vm.specification.name", "read";
 permission java.util.PropertyPermission "java.vm.version", "read";
 permission java.util.PropertyPermission "java.vm.vendor", "read";
 permission java.util.PropertyPermission "java.vm.name", "read";
};
 
위의 내용은 보면, jre\lib\ext\ directory에서 loading된 class는 AllPermission을 갖는다. 즉 SecurityManager의 제한이 없다는 것을 의미한다. 그리고, 두번째 항목은 모든 Domain에 대해 System.getProperty("java.version") method를 통해 System Property를 read할 수 있는 Permission을 설정한 사항이다.
policytool을 이용해 위와 같은 내용으로 Domain별 Sandbox boundary를 설정한 file을 생성할 수가 있고, 그 file을 JVM이 loading될때 policy file로 인식하게 하기 위해 java.security file에 설정할 수가 있다.

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/03/25 09:04 2008/03/25 09:04

※ Transaction Processing
 
Distributed programming에서 분산객체(Distributed object)는 business component로 개발되어 집니다. 특히, Enterprise JavaBeans는 분산 서비스 API에 독립적인 Business component를 개발할수 있다는 장점을 갖고 있습니다. Business component의 business method는 여러 component의 method로써 구성되는 work flow를 갖고 있을수가 있고, 이것은 한나의 단위로 진행되어 져야 할경우가 대부분입니다. work flow를 처리하는 중 하나라도 문제가 생긴다면, 진행된 작업을 무효화 할수 있어야 겠죠. 이러한 상황에서 Transaction이 필요한 것입니다.
  결국 Transation이라는 것은 all-or-nothing 성질을 갖는 것이죠. work flow를 구성하는 하나하나의 job들이 모두 진행되거나, 아니면 모두 무효화 하거나 해야 한다는 것이죠.

1. ACID Properties 
    1. Atomic : Transaction을 구성하는 모든 작업이 모두 성공하거나, 무효화 해야 한다는 성질. 즉, Tx는 더이상 나눌수 없는 하나의 단위.(atomic unit, all or nothing property)
    2. Consistency : Transaction이 성공적으로 완료되면, 리소스의 상태를 일관성있게 보장해야 한다는 성질. Intigerity보장
    3. Isolation : Transaction중에 다른 Trasaction에 영향을 받아서는 안된다는 성질.
    4. Durability : Transaction이 이상없이 완료되었을때, 작업의 결과가 영구적으로 저장되아야 한다는 성질.
 
 2. Distributed Transaction 
     Transaction은 Distributed Application에서 분산되어 있다. 즉 서로다른 Database를 동시에 업데이트 해야 할 경우도 있고, 서로다른 시스템에 있는 Distributed object의 method를 동시에 처리해야 하는 경우도 발생하게 되는 것이죠. 그래서, 다음과 같이 Distributed Transaction을 정의 할수 있습니다.

Transactions in heterogeneous distributed systems are called distributed tracsaction.
 
     Distributed Transaction을 이해하기 위해서 Transaction Participant의 각각에 대해 먼저 설명하겠습니다.
 

사용자 삽입 이미지

Reousrce Manager : Transaction resource(RDBMS)에 대한 management의 책임을 진다.

   ex) database server, messaging queuing systems.
   cf) Resource Adapter : application component가 resource manager에
     connect하기 위해 사용하는 JDBC driver와 같은 것.
Transaction Manager : transaction의 begin, commit, rollbak을 책임지고, 분산된 Resource manager에 대한 "Two phase commit" protocol에 대한 책을을 진다.

   A TM uses the XA interface to interact with the RMs.

Tx Application : Transaction Manager에서 제공하는 Distributed Tx를 사용하는 Appllication, EJB component, web component를 말한다.

 
     ◆ The interface of X/Open Distributed Transaction Processing

 - XA interface : DTP환경에서 TM과 RM사이의 API
 - TX interface : DTP환경에서 TM과 Tx application간의 API
 
 3. Two-Phase Commit 
     Distributed Tx는 "Two phase commit"을 지원해야 하는데, 이것은 Transaction Manager의 책임이지만, Resouse Manager도 Transaction Manager의 "Two phase commit" protocol message에 대응할수 있어야 겠죠.
  즉 Two phase commit은 TM과 RM에 의해 분산 Tx의 atomicity를 보장하기위해 실행되어진다. 다음 sequence를 참조하세요.
 

사용자 삽입 이미지

4. Transaction Model
사용자 삽입 이미지

 ◆ Flat Tx : 위의 그림에서 Bean-2의 method-B()는 Bean-1의 method-A()에서 생성된 Tx1에 동참하게 된다. 즉, 새로운 Tx를 생성하지 않는다.(EJB에서 CMP의 Tx Attribute중 Required 성질)

사용자 삽입 이미지

◆ Nested Tx : 위의 그림에서 Bean-2의 method-B()는 Bean-1의 methodA()에서 생성된 Tx1에 동참하지 않고, 새로운 Tx2를 생상한다. (EJB에서 CMP의 Tx Attribute중 RequiredNew 성질)
      Tx1의 commit은 Tx2의 fail에 영향을 받지 않는다. 그리고, Tx1의 rollback은 Tx2를 rollback한다.

사용자 삽입 이미지

2001년 5월 21일 작성

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/03/21 16:15 2008/03/21 16:15

사용자 삽입 이미지
이것도 2001년에 썼던 글입니다.

이전 아티클에서는 database select speedup에 대하여 알아보았다. 이번 편에서는 update를 제외한 insert, delete tranaction의 속도를 최대로 끌어올릴 수 있는 방안을 살펴보자.

▶ Insert, Delete Query speed up!

JDBC 2.0에서는 기본적으로 scrollable한 메소드등의 유연성있는 메소드들과  batch processing에 관련된 메소드가 추가되었다.

그게 바로 어떤 것이냐하면 Statement, PreparedStatement 인터페이스의 addBatch(String sql),
addBatch()메소드이다. Pro*C같은 경우 기본적으로 array processing을 이용하여 데이터베이스에 작업을 하므로 상당한 퍼포먼스를 낼 수 있는데 이를 자바측으로 변환한것이 바로 addBatch메소드인 것이다.

첨보았는가? 아니면 아래의 BMT아티클에서도 보았는가?
Other--> Development를 보게 되면 Java vs Pro*C의 비교자료가 있다. 한번읽어보기 바라며 그 내용의 상세부분을 간략한 코딩으로서 당신에게 보여주고자 한다.

기본적으로 java에서도 그러한 배치기능을 사용하고자 한다고 하고, 당신이 만약 드라이버 개발자라고 하면 어떻게 데이터베이스에 작업을 할것인가?

그나마 setAutoCommit(false)를 connection에 때려넣어놓으면 commit에 대한 그만큼의 비용이 떨어질것이라는걸 알고 있다면 다행이겠다. 자. 그러면 데이터를 소위 "밀어넣는다"라고 이야기했을 때 가장 최선책은? 건당 하는 건 무리일테고, 음~ 자바니까 Collection을 이용해보면 딱이겠다.

그리고 Collection을 이용한다면 들어오는 데이터는 중복을 허용하고 순서도 있어야 할테니까
List 계열을 사용하는 것을 좋겠고.. 놀새의 결론은 List중에서도 제일 퍼포먼스가 좋은 ArrayList를 사용하는게 딱일것이라고 생각했다. 하지만 실제 Driver는 어떠한 자바버젼에서도 맞아야 하기 때문에 1.2부터 사용되는 ArrayList는 좀 고려해 볼만도 하다.

그러면 뭐가 들어오는 sql문장에 대한 저장소로 적당할 것인가? 답은 Vector!! 왜 Vector인지는 설명하지 않겠다. 이정도 글읽는 당신정도라면 API는 어느정도 숙지하고 있을 거라는 놀새의 생각때문이다.

우선 그러면 실제 Oracle을 예로 driver내부나 한번 보도록 할까?

JDBC API Statement 인터페이스의 구현체인 OracleStatement코드의 addBatch메소드를 잠깐보자


    public synchronized void addBatch(String s)
        throws SQLException
    {
        addBatchItem(s);
    }

    private void addBatchItem(String s)
    {
        m_batchItems.addElement(s);
    }
 

어라? addElement를 사용하는 걸 보니 놀새가 생각했던 것처럼 Vector를 사용하고 있다. 즉 batch를 위한 저장소로서 Vector class를 사용중인 것이다. 그러면 Statement클래스의 실제 batch execute 명령어인 executeBatch()메소드를 보도록 하자.

   

public int[] executeBatch()
        throws SQLException
    {
        synchronized(connection)
        {
            synchronized(this)
            {
                int i = 0;
                int j = getBatchSize();
                if(j <= 0)
                {
                    int ai[] = new int[0];
                    return ai;
                }
                int ai2[] = new int[j];
                Object obj = null;
                Object obj1 = null;
                Object obj2 = null;
                boolean flag = false;
                ensureOpen();
                prepare_for_new_result(true);
                try
                {
                    connection.needLine();
                    for(i = 0; i < j; i++)
                    {
                        String s = getBatchItem(i);
                        String s1 = expandSqlEscapes(s);
                        byte abyte0[] = strToDbaccessBytes(s1);
                        byte byte0 = getSqlKind(s1);
                        if(byte0 == 0)
                            DBError.throwBatchUpdateException(80, "invalid SELECT batch command " + i, i, ai2);
                        ai2[i] = parseExecuteFetchWithTimeout(dbstmt, byte0, abyte0, null, 1, null, 1);
                        if(ai2[i] < 0)
                            DBError.throwBatchUpdateException(81, "command return value " + ai2[i], i, ai2);
                    }

                }
                catch(IOException ioexception)
                {
                    DBError.throwBatchUpdateException(81, ioexception.getMessage(), i, ai2);
                }
                catch(SQLException sqlexception)
                {
                    if(sqlexception instanceof BatchUpdateException)
                        throw sqlexception;
                    DBError.throwBatchUpdateException(81, sqlexception.getMessage(), i, ai2);
                }
                finally
                {
                    clearBatchItems();
                }
                int ai1[] = ai2;
                return ai1;
            }
        }
    }

위에서 보면 당연히 connection은 동기화되어져야 하므로 synch걸어놓고 작업할게 뻔하며,
parseExecuteFetchWithTimeout메소드가 실제 update작업을 이루게끔 하는데 저놈은 timeout이
걸려있으면 타이머 작동시키고 statement에 update때리는 작업을 하며, timeout이 0이면 바로 update이다. 복잡한가?

PreparedStatement(이하 PS)의 addBatch()메소드는 조금 더 복잡하므로 간략하게 어떻게
작동되는지만 설명하겠다. PS의 경우는 쿼리가 이미 database의 내부 procedure로 변환되어져 있기 때문에 Stream을 이용하여 데이터를 세팅시키는 일을 한다는 것이 Statement Batch와 틀린 점이다. 그렇다면 Statement와 PreparedStatement의 속도차이는 얼마나 될까? 놀새는 이미 전부 다 해봤기 때문에 이건 당신의 숙제로 남겨두겠다.

위의 내용복잡하면 몰라도 됨을 강력히 주장한다. 당신이 JDBC Driver개발자가 아니지 않은가~!! ^^

자 그럼 이제 코더의 신분으로 돌아왔다고 가정을 하고, API를 이용하여 코딩이나 한번 해보자.

단순히 API를 사용하는 것이므로 거창하게 설명하고 자시고 할 필요도 없이 바로 들어간다.
알아서 보라~


 

public class BatchTest {
    private Connection getConnection(){
        // 알아서 연결들 하라~! ^^
    }
   
    private void close() {
        // 알아서 연결을 닫아라
    }

    public void insertABT231Batch(){
        System.out.println(Utility.getTime()
        + " Insert ABT231 Batch Start .. Transaction size is " + m_abt231InsertList.size());
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            StringBuffer query = new StringBuffer();
            query.append("INSERT INTO ABT231 ");
            query.append("(customer_no, item_cd, occur_amt, reason_cd, register_ymd, register_no) ");
            query.append(" VALUES (?, ?, ?, '9', ?, ?) ");

            conn = getConnection();
            conn.setAutoCommit(false);

            pstmt = conn.prepareStatement(query.toString());
            Iterator iter = m_abt231InsertList.iterator();
            int count = 0;

            while( iter.hasNext() ) {
                m_abt231 = (Abt231) iter.next();
                pstmt.setInt(1, m_abt231.getCustomerNo());
                pstmt.setString(2, m_abt231.getItemCd());
                pstmt.setLong(3, m_abt231.getOccurAmt());
                pstmt.setString(4, s_magamCurrentTime);
                pstmt.setInt(5, Integer.parseInt(s_workCd));
                pstmt.addBatch();
                count++;
                if( (count % 10000) == 0){
                    System.out.println(count + "건 처리중");
                    pstmt.executeBatch();
                }
            }

            pstmt.executeBatch();
            conn.commit();
            System.out.println(Utility.getTime() + "] " + count + "건 입력완료");
        } catch ( Exception e) {
            e.printStackTrace();
            try{
                conn.rollback();
            }catch(Exception e2) {e2.printStackTrace();}
        } finally {
            close(pstmt);
            close(conn);
        }
    }
}
 

위에서 유심히 볼거라고는 bold체로 쓰여진 부분의 내용뿐이다. 위의 내용은 데이터베이스에서
추출된 데이터를 다시 계산하여 다른 데이터베이스 테이블에 insert하는 내용이며, ArrayList에 담긴 대량의 데이터를 iteration하며 batch를 실행하는 것이다.

좀 더 자세한 메소드 설명은 API의 내용을 참조해도 무방할 듯 싶다.

addBatch()처음보는가? 그러면 다른 클래스를 예로 들어서 java.lang패키지 클래스의 메소드는
자유자재로 구사할 줄 아는가?

놀새가 아는 사람들끼리의 고수에 대한 표현은 다음과 같다.

"저 사람은 걸어다니는 API야~!"

아무렇지도 않은 것 같으면서도 얼마나 함축적인 표현인가~!! ( 나만 그런가? ㅎㅎ)
API를 이용하여 쉽게 만들수 있는데도 예전 C코딩처럼 함수 열나 만들어서 잘 만들었다고 자랑해봤자 위의 걸어다는 API한테 망신당할 수 있다는 걸 명심해야 한다.

중요하게 쓰일수 있는 것은 바로 옆에 있으며, 당신눈에 띄지 않는 것이 대부분이다.
두 눈 크게 뜨고 다녀야 한다. API가 바로 그것인 것이다.
--------------------------------------------------------------------------------
아참. 그리고 위의 코드같은거 테스트할 때 웬만하면 TestCase만들어서 작성하세요. 습관이 중요합니다. 단위테스트의 중요성은 나중에 칼럼 쓸 일 있으면 쓰도록 하겠습니다.

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/03/21 13:58 2008/03/21 13:58

사용자 삽입 이미지
2001년에 썼던 글입니다.

이번 아티클에서는 자바에서의 배치작업에 대하여 논하여 보자. 전부터 이부분에 대하여
작성을 한다고 직접 적어놓고서 이제야 만들어 내는 놀새의 게으름을 이해해주기 바란다.
왜냐~~ 요즘 너무 바쁘기 때문에.. ^^

이런거 이미 알고 있으면서도 혼자만의 기술인냥 숨기고 있는 사람들이 있을까봐 열심히 만들어서 아티클로 쓴다. 혼자 알고 있으면 뭐하리요. 인터넷은 헛빵으로 있는 것이 아니다. 정보의 바다인 것이다.

지금 시간이 없다.. 바로 시작한다. 

일전 방명록에 이런 작업을 하는 분의 문의가 있었다. 요약하면 다음과 같은데 "당신의 연구성과를 잘 보고 있다, 우리 사이트는 배치작업을 Pro*C로 하고 front-end단은 자바로 한다. 하고 싶은
말인즉은 배치도 자바로 짜고 싶다. 퍼포먼스를 나게 하는 방법을 알려달라" 라는 것이 요지였다.

작년말의 프로젝트에서도 배치를 하긴 했지만 그때는 java data를 읽어내어 Visual Basic에서 처리하는 배치프로그램이어서 자바보다는 VB에 가까운 형태였고, 고생도 많이 했다.

하지만 이번에는 순수 자바배치를 이용하도록 한다. 당연히 transtion상황하이므로 insert, update, delete에 관한 것임을 염두에 두어야 한다.

우선 워밍업으로 select를 빨리 해올 수 있는 방법을 잠깐 보도록 하자.

단~ 우선 알아두어야 할 것은 이번 아티클은 오라클을 이용한 샘플임을 알아두도록 하자.

▶ Select Query speed up!

Database record select속도를 높일 수 있다?  흠.
오라클 데이터베이스의 메모리 구조에서 사용하는 영역을 Oracle JDBC까지 연결시켜 보면 쉽게 보이는 것이다. 보통 오라클같은 경우 Software Code Areas, P.G.A (Program Global Area),
S.G.A (System Global Area) 로 나눌수 있다

간략하게 나마 설명을 해보자면

1) SoftWare Code Areas.
 오라클의 실행 모듈들이 메모리에 할당되는 공간이다.
 일반 유저 프로그램이 접근할 수 없고, 크기는  O/S 에 따라 약간씩 다르지만 Virtual Memory 에 위 치하게 된다.

2) S.G.A ( System Global Area )
 오라클 인스턴스의 한 부분으로 인스턴스가 기동 (Startup) 할때 메모리에 잡히게 되는 작업 영역이다.  이 영역은 오라클이 기동중에 사용되는 데이타나 각종 정보들, 그리고 일반 유저들의 모든 SQL 문장들을 이곳에서 수행한다.  주로, Real Memory 에 위치하게 된다.

3) P.G.A ( Program Global Areas )
 유저가 요청한  SQL 문장을 처리하기 위해서 각 세션마다 선언된 변수 값들을 임시로 저장하는 영역으로, DisConnect 하면, 자동으로 Release 된다

자, 그러면 우리가 사용하는 JDBC의 연결영역은 어떻게 될것인가?

위의 설명에서 처럼 SGA를 사용할 것 같다. SGA는 Data Buffer Cache를 가지고 있게 되는 데 이것은 SQL 문장을 실행할때 필요한 데이타를 디스크에서 SGA 영역으로 읽어 들이는 곳이다.
여기에는 변경된 데이터뿐만 아니라 변경되지 않는 데이터도 가지고 있는데, 이는 수행속도 향상을 위해 디스크 입출력 보다는 메모리 Access를 하게 하기 위함이다.
이 영역이 크면 그만큼 많은 데이타를 SGA 영역에 올려놓을 수 있으므로 메모리에서 바로 읽을 수 있는 Hit 율이 높아진다. 그러나, 이 영역이 너무 작으면 스와핑(Swapping) 이 많이 발생되므로 시스템의 성능에 영향을 준다. 조회 속도를 높이기 위해선 이 영역을 충분히 크게 해야 하는데
이곳을 걸고 넘어질 수 있는 Java API가 ResultSet 클래스의 setFetchSize(int rows) 메소드이다. 즉 JDBC Driver가 ResultSet을 통해서 가져올 데이터의 fetch크기를 결정하게 하는 것이다.

퍼포먼스에서 항상 나오는 이야기이지만 이러한 값들은 너무 커도 안되고 너무 작아도 안된다는것이다.
데이터베이스마다 최적의 성능을 나타내는 값으로 세팅되어져야 함은 물론이다.
또한 0 <= rows <= Statement.getMaxRows() 임은 당연하겠다.

적은 양의 데이터베이스의 select 쿼리시에 위의 fetch size에 대한 결정은 별 차이점을 드러내지 않지만 대량의 데이터를 조회하는 ResultSet의  경우 그 차이는 점점 극명하게 나타나는 것을 확인할 수 있다.

간단한 예제를 보겠다.
이 예제에서 사용되는 abt140이란 테이블은 현재 6,511,102건의 데이터를 가지고 있다.
fetch size를 지정하고 안하고 의 경우를 통하여 결과값을 보도록 하겠다.

import java.sql.*;
import java.io.*;

class FetchSizeTest {
    public static void main(String[] args) throws Exception {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        String url ="jdbc:oracle:thin:@192.168.0.137:1521:abn";
        Connection conn = DriverManager.getConnection(url,"abs","abs");
        String query = " select * from abt140";
        PreparedStatement stmt = conn.prepareStatement(query);
        long start = System.currentTimeMillis();
        ResultSet rs = stmt.executeQuery();
        rs.setFetchSize(10000);
        while(rs.next()) {
            new String("xx");
        }
        System.out.println("Time elapsed : " + (System.currentTimeMillis() - start));
        rs.close();
        stmt.close();
        conn.close();
    }
}
 

위 프로그램에서 setFetchSize를 적용시키기 전과 후의 결과값은 다음과 같다.

Fetch Size 적용안할 경우 : Time elapsed --> 423038
Fetch Size를 10000 rows로 적용할 경우 : Time elapsed --> 101556

위의 경우 속도차이가 거의 4배가 나는 것을 볼 수 있으며, 꼭 10000개의 record를 fetch로 하는게 최상의 퍼포먼스가 아님을 명심해두자.

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/03/21 13:52 2008/03/21 13:52