펌: http://blog.daum.net/faqall/4300136

/*********************************************
 *  SAMPLE : SQL*LOADER SAMPLES PART I
 *********************************************/

  SQL*Loader 는 외부 화일의 데이타를 ORACLE 데이타베이스의 table에 넣기
 위한 유틸리티입니다. SQL*Loader를 사용하려면 외부 데이타 화일과
 콘트롤 화일이 필요합니다. 콘트롤 화일이라고 하는 것은 로드하는 데이타의
 정보를 저장한 화일입니다. 간단한 샘플 콘트롤 화일을 설명하겠습니다.
 
    LOAD DATA                          제어 화일의 선두에는 반드시 이 말이 필요합니다.
    INFILE sample.dat                 외부 화일을 지정(경로,파일명)합니다.
    REPLACE                             테이블에 데이타 넣는 방법 지정
    INTO TABLE table_name       데이타를 로드하는 테이블을 지정
    FIELDS TERMINATED BY ','   데이타 필드의 종결 문자 지정
    (a integer external,                테이블의 열, 외부 데이타 화일의 데이타 형을 지정
     b char)
 
   참고로 REPLACE 외에 다음의 옵션이 가능합니다.
     REPLACE                    테이블의 기존 행을 모두 삭제(DELETE)하고 INSERT
     APPEND                     새로운 행을 기존의 데이타에 추가
     INSERT                       비어 있는 테이블에 넣을 때
     TRUNCATE                 테이블의 기존 데이타를 모두 TRUNCATE 하고 INSERT
 
   SQL*Loader를 실행하면 아래의 화일이 작성됩니다.
    * 로드 작업 중 동작에 관한 통계 등을 포함한 로그 화일(확장자는 log)
    * 데이타 에러 때문에 로드가 안된 레코드를 저장한 화일(확장자는 bad)
    * 사용자의 선택 기준에 적합하지 않은 레코드를 저장한 화일(discard 화일)
       이것은 discardfile 옵션으로 별도로 지정해야 생성됩니다.
 
 실행 방법은 다음과 같습니다.
 $sqlldr  scott/tiger  control = sample.ctl  data=sample.dat
 
 1.1 임의의 열에 변화없는 고정 문자열(값)을 입력한 경우
    **** 테이블 구조 ****
    CREATE TABLE cons_test
    (a number,
     b number,
     c number,
     d varchar(10))
 
    **** 콘트롤 화일 (즉 이예에서 sample.ctl)****
    LOAD DATA
    INFILE cons.dat
    REPLACE
    INTO TABLE cons_test
    FIELDS TERMINATED BY ','
    (a integer external,
     b integer external,
     c CONSTANT '100',
     d char)
 
    **** 외부 데이타 화일 (즉, 이예에서 sample.dat) ****
    1,2,DATA
    2,4,DATA2
 
    **** 검색결과 ****
    SQL>SELECT * FROM cons_test;
           A       B     C     D
          -------------------------
           1       2     100   DATA
           2       4     100   DATA2
 
   주의사항 : 이 예에서 C열은 데이타 화일에 넣어서는 안 됩니다. COSNTANT는
  그것으로 완결된 열 지정의 하나가 됩니다. integer external 데이타 형은
  수치 데이타를 문자형식(ASCII CODE)로 나타낸 것입니다.
 
 1.2 로드한 때의 날짜를 데이타로 로드하고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE sysdatetb
    (a number,
     b date,
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE sysdate2.dat
    REPLACE
    INTO TABLE sysdatetb
    FIELDS TERMAINATED BY ','
    (a integer external,
     b sysdate,
     c char(10))
 
    **** 외부 데이타 화일 ****
    111,STRINGS
    222,STRINGS2
 
    **** 검색결과 ****
    SQL>SELECT * FROM sysdatetb;
          A     B               C
      ---------------------------
         111  13-MAY-94  STRING
         222  13-MAY-94  STRING2
 
     주의사항 : 이 예에서는 B열은 데이타 화일에 넣어서는 안됩니다. SYSDATE는
    그것으로 완결된 열 지정의 하나가 됩니다. 새로운 시스템 날짜매김은 컨벤셔널
    패스에서는 실행 시에 삽입된 각각의 레코드 배열마다 또, 다이렉트 패스의
    경우는 로드된 각각의 레코드의 블럭마다 사용됩니다.
 
 1.3 임의의 수치열에 연속 번호(sequence)를 붙이고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE rectb
    (a varchar(10),
     b number,
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE rec.dat
    REPLACE
    INTO TABLE rectb
    FIELDS TERMINATED BY ','
    (a char,
     b recnum,
     c char)
    **** 외부 데이타 화일 ****
    A,a
    B,b
    C,c
    **** 검색결과 ****
    SQL>SELECT * FROM rectb;
         A       B  C
       ---------------------
         A       1  a
         B       2  b
         C       3  c
 
    주의사항 : 이 예에서는 B 열은 데이타 화일에 넣어서는 안됩니다. RECNUM는
   그것으로 완전한 열 지정의 하나가 됩니다. 연속번호는 1부터 차례대로 1씩
   더해져서 번호가 매겨집니다. 가산된 번호를 둘씩 건너뛰거나 하는 것은 불가능
   합니다.
 
 1.4 임의의 수치열에 연속 번호(sequence)를 임의의 간격으로 붙이고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE seqtb
    (a varchar(10),
     b number,
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE seq.dat
    REPLACE
    INTO TABLE seqtb
    FIELDS TERMINATED BY ','
    (a char,
     b sequence(100,5),
     c char)
 
    **** 외부 데이타 화일 ****
    1,a
    2,b
    3,c
 
    **** 검색결과 ****
    SQL>SELECT * FROM seqtb;
    A    B    C
    -------------
    1    100  a
    2    105  b
    3    110  c
 
   주의 사항 : 이 예에서는 B열은 데이타 화일에 넣어서는 안 됩니다. SEQUENCE는
  그것으로 완결된 열 지정의 하나가 됩니다. 초기 값 100과 늘인 값 5는 다른
  수치로 변경 가능합니다.
 
 1.5 로드하는 논리 레코드를 구성하는 물리 레코드가 복수열로 구성된 경우
     (물리 레코드의 1바이트 째로 판단되는 경우)
    **** 테이블 구조 ****
    CREATE TABLE conti_test
    (a varchar(10),
     b varchar(10),
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE conti.dat
    REPLACE
    COUTINUEIF THIS
    (1) = '%'
    INTO TABLE conti_test
    FIELDS TERMINATED BY ','
    (a char,
     b char,
     c char)
 
    **** 외부 데이타 화일 ****
    %1,
    %2,
    3
    %A,B
    ,C
    %a,b
    %c
    %d
    ,ef
 
    **** 검색결과 ****
    SQL>SELECT * FROM conti_test;
    A      B      C
    ---------------------
    1      2       3
    A      B      C
    a      bcd   ef
 
   주의사항 : 이 예의 경우 1바이트 째가 계속 행의 체크를 위해서 사용되기
  때문에, 실 데이타를 1 바이트 째부터 시작해서는 안됩니다. 상기 예의 경우,
  레코드의 선두 바이트가 '%'일 때 다음의 레코드가 연결됩니다.
 
 1.6 외부 데이타 화일의 물리 레코드가 복수 레코드로 구성된 경우
     (구성하는 물리 레코드 수가 모두 일정한 경우)
    **** 테이블 구조 ****
    CREATE TABLE con_test
    (a varchar(10),
     b varchar(10),
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE conti.dat
    REPLACE
    --일례로 모든 논리레코드가 그 레코드로 구성됩니다.
    CONCATENATE 2
    INTO TABLE con_test
    FIELDS TERMINATED BY ','
    (a char,
     b char,
     c char)
 
    **** 외부 데이타 화일 ****
    1,2,
    3
    a,b,
    c
    A,
    B,C
 
    **** 검색결과 ****
    SQl) SELECT * FROM con_test;
    A       B       C
    -------------------------
    1       2       3
    a       b       c
    A       B       C
 
   주의 사항 : 하나의 논리 레코드가 모두 일정한 갯수의 물리 레코드로부터
  성립되는 것 같은 단순한 경우에 한합니다.
 
 1.7 데이타의 잘린 문자를 데이타로 로드하고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE enc
    (a varchar(10),
     b varchar(10),
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE enc.dat
    REPLACE
    INTO TABLE enc
    FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY ' " ' AND ' " '
    (a char,
     b char,
     c char)
 
    **** 외부 데이타 화일 ****
    "abc,d",2,3
    "a,,d",4,5
 
    **** 검색결과 ****
    SQL>SELECT * FROM enc;
    A         B       C
    -------------------------
    abc,d   2       3
    a,,d      4       5
 
 1.8 포지션 지정 시 char 형 데이타 전후의 blank도 로드하고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE pretb
    (a varchar(10),
     b varchar(10),
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE pre.dat
    REPLACE
    PRESERVE BLANKS
    INTO TABLE pretb
    (a position(01:05) char,
     b position(06:10) char,
     c position(11:20) char)
 
    **** 외부 데이타 화일 ****
    12 4 67890 ab def hi
     2   67890 ab def hi
 
    **** 검색 결과 ****
    SQL>SELECT  * FROM pretb;
    A       B       C
    --------------------------
    12 4    67890   ab def hi
    2       67890   ab def hi
    **** 결과 확인 ****
    SQL>SELECT LENGTH(a), LENGTH(c) FROM pretb;
 
    LENGTH(A) LENGTH(C)
    -------------------
            5        10
            5        10
 
 1.9 어떤 데이타 열의 데이타 유무와 상관없이 데이타가 없는 경우 NULL 데이타를
     넣도록 하고 싶다
    **** 테이블 구조 ****
    CREATE TABLE tratb
    (a varchar(10),
     b varchar(10),
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE tra.dat
    REPLACE
    INTO TABLE tratb
    FIELDS TERMINATED BY  ','
    trailing nullcols
    (a char,
     b char,
     c char)
 
    **** 외부 데이타 화일 ****
    1,aa,
    2,bb,FF
    3,cc,
 
    **** 검색결과 ****
    SQL>SELECT * FROM tratbl
    A       B       C
    ------------------------
    1       aa
    2       bb      FF
    3       cc
 
   주의사항 : trailing nullcols를 사용하지 않으면 1 레코드째와 3 레코드째가
  데이타 에러가 됩니다. 데이타가 들어 있기도 하고 없기도 한 열의 데이타는
  데이타 화일의 최후로 가져갑니다.
 
 1.10  CHAR 형 필드가 BLANK로 채워져 있을 때 NULL을 삽입하고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE nulltb
    (a varchar(10),
     b varchar(10),
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE null.dat
    REPLACE
    INTO TABLE nulltb
    FIELDS TERMINATED BY ','
    (a char,
     b char,
     c char(10) nullif c = blanks)
 
    **** 외부 데이타 화일 ****
    aa,bb, ,
    11,22, ,
    99,88,AA
    00,00,BB
 
    **** 검색결과 ****
    SQL>SELECT * FROM nulltb;
    A       B       C
    -------------------------
    aa      bb
    11      22
    99      88      AA
    00      00      BB
   주의 사항 : 검색결과의 1 행째, 2 행째의 열 C는 블랭크가 아니라 NULL 입니다.
 
 1.11 DATE 필드가 BLANK로 채워져 있을 때 NULL을 삽입하고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE nulltb2
    (a varchar(10),
     b varchar(10),
     c date)
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE null2.dat
    REPLACE
    INTO TABLE nulltb2
    FIELDS TERMINATED BY ','
    (a char,
     b char,
     c date "YY/MM/DD" nullif c = blanks)
 
    **** 외부 데이타 화일 ****
    aa,bb, ,
    11,22, ,
    99,88,92/11/11,
    00,00,94/12/12,
 
    **** 검색결과 ****
    SQL>SELECT * FROM nulltb2;
    A       B       C
    -------------------------
    aa      bb
    11      22
    99      88      92/11/11
    00      00      94/12/12
   주의사항 : 검색결과의 1 행째, 2 행째의 열 C는 블랭크가 아니라 NULL 입니다.

 1.12 POSITION 지정 시 BLANK를 그대로 로드하고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE nulltb2
    (a varchar(10),
     b varchar(10),
     c date)
 
    **** 콘트롤 화일 ****
    --- position 지정으로 블랭크를 그대로 입력 원하는 경우
    --- preserve blanks를 지정한다.
    LOAD DATA
    INFILE null3.dat
    REPLACE
    PRESERVE blanks
    INTO TABLE nulltb2
    (a position(1:2) char,
     b position(3:4) char nullif b = blanks,
     c position(5:13) date "YY/MM/DD")
 
    **** 외부 데이타 화일 ****
    998892/11/11
        94/12/12
 
    **** 검색결과 ****
    SQL>select * from nulltb2;
    A       B       C
    -------------------------
    99      88      92/11/11
                    94/12/12
 
    SQL>select length(a), length(b) from nulltb2;
 
    LENGTH(A) LENGTH(B)
    -------------------
            2         2
            2

   주의사항 : 이 경우 2 레코드 째는 A에 블랭크가 들어가고 B에 NULL이 들어갑니다.
 
 1.13 데이타 화일의 수치 데이타 열에 BLANK가 들어가 있을 때 0을 입력하고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE def2
    (a varchar(10),
     b varchar(10),
     c number)
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE def2.dat
    REPLACE
    INTO TABLE def2
    FIELDS TERMINATED BY ','
    (a char,
     b char,
     c integer external defaultif c = blanks)
 
    **** 외부 데이타 화일 ****
    11,11,123
    22,22, ,
    33,33, ,
    44,44, ,
 
    **** 검색결과 ****
    SQL>SELECT  * FROM deft;
    A       B              C
    ------------------------
    11      11           123
    22      22             0
    33      33             0
    44      44             0
 
 1.14 데이타가 NULL일 때 NULL이라고 하는 문자열을 넣고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE ifnulltb
    (a varchar(10),
     b varchar(10),
     c varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE ifnull.dat
    REPLACE
    INTO TABLE ifnulltb
    FIELDS TERMINATED BY by ','
    (a char,
     b char "nvl(:b,'NULL')",
     c char)
 
    **** 외부 데이타 화일 ****
    1,2,3,
    A,,B
    a,b,c
 
    **** 검색결과 ****
    SQL>SELECT * FROM ifnulltb;
    A       B       C
    -------------------------
    1       2       3
    A       NULL    B
    a       b       c

  주의 사항 : NVL과 같은 SQL 함수는 DIRECT LOAD의 경우 SQL 인터페이스를
                  경유하지 않기 때문에 사용할 수 없습니다.
 
 1.15 어떤 열을 모두 대문자(소문자)로 변환하여 로드하고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE uptb
    (a varchar(10),
     b varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE upper.dat
    REPLACE
    INTO TABLE uptb
    FIELDS TERMINATED BY ','
    (a char "lower(:a)",
     b char "upper(:b)")
 
    **** 외부 데이타 화일 ****
    aBcDeFg,AbCdEf
    ccDD11,ffGG22
 
    **** 검색 결과 ****
    SQL>SELECT * FROM uptb;
    A             B
    --------------------
    abcdefg   ABCDEF
    cdd11      FFGG22
 
 1.16 ZONE 형 데이타를 로드하고 싶은 경우
    ZONE의 데이타 형식은 1문자 1바이트로 나타납니다.
    숫자 +123이라면
    bit     1234    5678    1234    5678    1234    5678
            1111    0001    1111    0010    1100    0011
    10진            1               2         +        3
    최후의 8비트의 상위 4비트(부호 비트)로 정부가 지정됩니다.
           1100(정) 1101(부)
    가 됩니다.
    원래는 EBCDIC 코드에서
    Hex F0 to F9  →  +0 to +9
    Hex C0 to C9  →  +0 to +9(부호비트)
    Hex D0 to D9  →  +0 to -9(부호비트)
    이것을 아스키로 변환했기 때문에
 
    부호비트        {ABCDEFGHI}JKLMNOPQR
    ------------------------------------
    부호            ++++++++++----------
    수              01234567890123456789
    가 됩니다.
 
    **** 테이블 구조 ****
    create table z
    (a varchar(10),
     i number,
     j number)
 
    **** 콘트롤 화일 ****
    load data
    infile filename.dat
    replace
    into table z
    (a position (01) char,
     i position(02:07) zoned(6),
     j position(08:13) zoned(6))
 
    **** 외부 데이타 화일 ****
    A12345{12345}
 
    **** 검색결과 ****
    SQL>select * from z;
    A            I        J
    -----------------------
    A       123450  -123450
 
 1.17 DECIMAL 형 데이타를 로드하고 싶은 경우
    decimal 데이타는 1바이트에 2개의 숫자가 지정됩니다.
    숫자 +123 이라면
    bit    1234 5678 1234 5678
           0001 0010 0011 1100
    10진      1    2    3    +
    끝의 4비트는 부호 비트로 불리고
         1100(정)        1101(부)
    가 됩니다.
 
    **** 테이블 구조 ****
    create table dec
    (a varchar(10),
     col1 number,
     col2 number)
 
    **** 콘트롤 화일 ****
    load data
    infile filename.dat
    replace
    into table dec
    (a position (01:01) char,
     col1 position(02:05) decimal(7,4),
     col2 position(06:09) decimal(7,4))
 
    **** 외부 데이타 화일 ****
    데이타 화일은 바이너리이기 때문에 more 등으로 볼 수 없습니다.
    4112 3456 7c12 3456 7d0a
 
    **** 검색 결과 ****
    SQL>select * from dec;
    A               COL1        COL2
    --------------------------------
    A           123.4567   -123.4567

    주의 사항 :
    콘트롤 화일에서 decimal 형을 사용하는 것에 따라 (length, scale)을 지정합니다.
 
 1.18 고정 길이 레코드 데이타를 로드하고 싶은 경우
    **** 테이블 구조 ****
    CREATE TABLE fixtb
    (a varchar(10),
     b varchar(10))
 
    **** 콘트롤 화일 ****
    LOAD DATA
    INFILE fix.dat
    "FIX 6"
    REPLACE
    INTO TABLE fixtb
    (a char(3),
     b char(3))
 
    **** 외부 데이타 화일 ****
    123456abcdef
 
    **** 외부 데이타 화일(16진) ****
    3132 3334 3536 6162 6364 6566
 
    **** 검색결과 ****
    SQL>SELECT * FROM fixtb;
    A      B
    ---------------
    123    456
    abc    def

  주의 사항 : 데이타 화일의 끝에는 OxOa(CARRIAGE RETURN)가 들어가면
                  안 됩니다. OxOa도 1문자로 카운트되기 때문입니다.
 
 
 

======================================
 NUMBER 와 DATE TYPE 데이타 LOAD
======================================
    PURPOSE
    ---------
     NUMBER 와 DATE TYPE 데이타 LOAD 예제이다.

    Explanation
    -----------
     1. DATA FILE<ul2.dat>
        7566 31237 961010 20

     2. CONTROL FILE<ul2.ctl>
        LOAD DATA
        INFILE 'ul2.dat'
        INTO TABLE EMP
        APPEND
        ( EMPNO    POSITION(01:04) INTEGER EXTERNAL,
          SAL      POSITION(06:10) DECIMAL EXTERNAL ":SAL/100",
          HIREDATE POSITION(12:17) DATE "YYMMDD",
          DEPTNO   POSITION(19:20) INTEGER EXTERNAL)

     3. LOADER 실행
        $ sqlldr scott/tiger control=ul2.ctl

     4. TABLE 구조
      Name                          Null?      Type
      ------------------------------- -------- ----
      EMPNO                    NOT NULL NUMBER(4)
      SAL                                          NUMBER(7,2)
      HIREDATE                                 DATE
      DEPTNO                                   NUMBER(2)

     5. 실행결과
          EMPNO        SAL HIREDATE               DEPTNO
     ---------- ---------- ------------------ ----------
           7566     312.37 10-OCT-96                  20
 
 

====================================================================================
 ORACLE 8i SQL*LOADER DATAFILE의 특정 FIELD DATA를 SKIP하고 LOADING하는 방법
====================================================================================

 아래의 예제와 같이 가변 길이의 filed들이 ',', '|' 와 같은 구분자로
 구분이 되고 있는 경우 oracle 8i부터 제공되는 'FILLER'라고 하는 필드
 구분자를 사용하여 상태인식자로 표시하여 insert시 skip할 수 있다.
 
    <Example>
    TABLE : skiptab
    ===========================
     col1 varchar2(20)
     col2 varchar2(20)
     col3 varchar2(20)
    CONTROLFIEL : skip.ctl
    LOAD DATA
    INFILE skip.dat
    INTO TABLE skiptab
    FIELDS TERMINATED BY ","
    (col1 char,
    col2 filler char,
    col3 char)
    DATAFILE : skip.dat
    SMITH,      DALLAS,        RESEARCH
    ALLEN,      CHICAGO,       SALES
    WARD,       CHICAGO,       SALES
    data loading :
    $sqlldr scott/tiger control=skip.ctl
    결과  :
    COL1          COL3
    -------------------------------------------------------
    SMITH         RESEARCH
    ALLEN         SALES
    WARD          SALES
 
 
 
======================================================
 또다른 예 readme.ctl
 SQL*LOADER를 이용하여 TEXT FILE을 INSERT하는 방법
======================================================
 
 SQL*LOADER를 이용하여 TEXT FILE을 그대로 DATABASE에 넣는 방법은 다음과 같다.
 여기서는 README.DAT 화일을 DATABASE의 README 테이블에 넣는 경우를 예로 들기로
 한다.
   <테이블 구조 : README>
     NAME   NULL  TYPE
     ----------------------
     NAME         VARCHAR2(10)
     TEXT         LONG
 
    <README.CTL>
     LOAD DATA
     INFILE readme.dat "fix 65535"
     APPEND
     CONCATENATE 10
     PRESERVE blanks
     INTO TABLE readme
     (name position(1:10) char,
      text char(65535))
     <README.DAT>
     원래의  README.DAT 앞에 10자리로 NAME 컬럼 내용을 추가하였음
     README     화일 내용
     <실행 명령>
   sqlldr username/password control=readme.ctl data=readme.dat bindsize=300000
  이렇게 하면 README.DAT의 내용이 하나의 RECORD로 되어 DATABASE에 입력된다.
  단, 최대로 입력 가능한 TEXT FILE의 크기는 200KB 이다.
 
 
================================================================
 SQL*LOADER에서 SEQUENCE 함수와 DECODE 함수 사용하는 방법
================================================================

   PURPOSE
   ---------
   Unique한 DATA을 load하고자 할때 쓰이는 SEQUENCE () 함수와 DECODE 함수의
   사용에 대해 알아보고자 한다.
   Explanation
   -----------
   table의 data에 unique한 값을 넣기 위해 sequence을 만들어 사용한다. 이것은
   블루틴 #10863 을 참고하면 알수 있다. 이때 loader에서 database의 sequence기능이
   아닌 sequence() 함수를 사용하는 것을 테스트해 보고자 한다.
   또한 decode 함수를 사용하는 내용을 여기서 다루고자 한다.
   함수를 사용하는 경우는 conventional path load인 경우에만 가능하며 direct path load
   인 경우는 적용되지 않음을 주의하자.
   함수를  사용할때 참조하고자 하는 컬럼앞에 콜론(:)을 붙이면 된다.
   SEQUENCE 를 사용하면 각각의 레코드를 로드할때 특정 컬럼을 증가할수 있다.
   단 무시되거나 잘못되어 들어가지 않은 레코드에 대해서는 증가을 하지 않기
   때문에 주의하여야 한다.
   SEQUENCE 함수의 옵션에 대해 알아보자.

    SEQUENCE(n,increment) - 지정한 n 값부터 시작하여 increment 값만큼 증가한다.
    SEQUENCE(COUNT,increment) - table에 이미 존재하는 로우들을 count한 수에서
                                시작하여 increment 값만큼 증가한다.
    SEQUENCE(MAX, increment - 해당 컬럼의 maximum 값에서 시작하여 increment
                              값만큼 증가한다.

    Examples
    ----------
    < Sequence 함수 사용 예 >
    1. sample table 생성
    create table t1
    (field1 number, field2 number, field3 varchar2(10));
 
    2. controlfile생성
    <sequence1.ctl>
    load data
    infile 'data1.dat'
    into table t1
    fields terminated by "," optionally enclosed by '"'
    (field1 SEQUENCE(MAX,1),
     field2,
     field3 )
 
    3. load할 datafile생성
    <data1.dat>
    1234, "ABC"
    3456, "CDF"
 
    4. 실행해 본다.
    $ sqlldr scott/tiger sequence1.ctl
    SQL> select * from t1;
        FIELD1     FIELD2 FIELD3
    ---------- ---------- --------------------
             1       1234 ABC
             2       3456 CDF
 
    < Decode 함수 사용 예 >
    test1 column이 'hello'이면 'goodbye'을 아니면 test1 column값을 로드하고자 한다.
    1. sample table 생성
    create table testldr
    (test1 varchar2(10), test2 varchar2(10));
 
    2. controlfile생성
    <decode1.ctl >
    Load data
    infile 'data2.dat'
    into table testldr
    fields terminated by ',' optionally enclosed by '"'
    (test1,
     test2 "decode(:test1, 'hello', 'goodbye', :test1)")
 
    3. load할 datafile생성
    <data2.dat>
    hello,""
    goodbye,""
    hey,""
    hello,""
 
    4. 실행해 본다.
    $ sqlldr scott/tiger decode1.ctl
    SQL> select * from testldr;
    SQL> select * from testldr;
    TEST1                TEST2
    -------------------- --------------------
    hello                goodbye
    goodbye              goodbye
    hey                  hey
    hello                goodbye
 
 
 
==================================================================
 SQL*LOADER에서 | (PIPE LINE)을 RECORD SEPARATOR로 사용하기
==================================================================
 
    PURPOSE
    -------
     Oracle8i부터는 , SQL*Loader을 사용할때 record terminator을 지정할 수 있게 되었다.
 
    Explanation
    -----------
     Oracle8i 이전에는 record seperator로 default로 linefeed(carriage return,
    newline 등)였다.  이전에는 VAR 또는 FIX 등의 적당한 file을 다루기 위한 옵션을
    주어야 하기 때문에 복잡한 감이 있었고 flexible하지 못했다.
     Oracle8i부터는 , SQL*Loader을 사용할때 record terminator을 지정할 수 있게
    되었다. newline 또는 carriage return 문자를 포함하는 data 또는 special 문자를
    포함하는 data를 load하고자 할때 record terminator를 hexadecimal로 지정하여 활용할 수 있다.
 
    Example
    --------
     다음의 예제는  '|' (pipe line)을 record separator로 사용한다.
     record separator를 사용하기 위해서 SQL*Loader의 control file에  'infile'절에 적당한 값을
     지정 하여야 한다.
     아래의 예는 '|' (pipe line)을 사용하기 위해서
     "str X'7c0a'"을 'infile'절에 지정하였다.
    --controlfile : test.ctl
    load data
    infile 'test.dat' "str X'7c0a'"
    into table test
    fields terminated by ',' optionally enclosed by '"'
    (col1, col2)

    --datafile:  test.dat
    1,this is the first line of the first record
    this is the second|
    2,this is the first line of the second record
    this is the second|

    SQL> desc test
     Name                                      Null?    Type
     ----------------------------------------- -------- -----------
     COL1                                               VARCHAR2(4)
     COL2                                               VARCHAR2(100)

    $  sqlldr scott/tiger control=test.ctl log=test.log
    load된 data을 보면 아래와 같이 carriage return이 들어가 있는 data가 한 column에
    제대로 들어간 것을 볼 수 있다.
    SQL> select * from test;
    COL1
    ----
    COL2
    --------------------------------------------------------------------------------
    1
    this is the first line of the first record
    this is the second
    2
    this is the first line of the second record
    this is the second
 
 
 
===========================================================================
특별한 값(SEQUENCE, 십진수, USERNAME)을 갖는 COLUMN을 LOADER로 넣기
===========================================================================

    PURPOSE
    ---------
    특별한 값(SEQUENCE, 십진수, USERNAME)을 갖는 COLUMN을 SQL*LOADER를
    이용하여 INSERT하는 방법에 대해 알아보도록 한다.

    Explanation
    -----------
    1.연산이 포함된 십진수의 load.
    은행의 경우 data가 transaction type, 계좌번호 거래액으로 될 경우,
    마지막 거래액은 함축적 의미를 가진 숫자값이다.
    아래의 예에서 실제 값은 100.73, 75.25, 820.00이다.
     DEPOSIT  10015 10073
     DEPOSIT  10012 7525
     WITHDRAWAL 10015 82000
    이것을 아래의 table로 load하려 한다.
    CREATE TABLE register
     (tx_type CHAR(15),
      acct    NUMBER,
      amt     NUMBER);
    cotrol file
     LOAD DATA
     INFILE 'month.dat'
     INTO TABLE register
     (tx_type POSITION(1:10),
     acct POSITION(13:17),
     amt POSITION(20:24) ":amt/100")
    이것을 load하면 sql*loader는 아래와 같은 insert 문을 만든다.
    INSERT INTO register (tx_type,acct,amt)
       VALUES (:data1, :data2, ":amt/100");
    그러므로 매 amt값마다 100으로 나눈 값을 얻을 수 있다.
    그러나 이 방법은 direct path 방식에서는 사용할 수 없다.

    2. database sequence를 load하기
    [예제1]
    먼저 좀 더 쉬운 방법인 position에 의해 구분되는 data file의 경우를 보자.
     - table
    CREATE TABLE load_db_seq_positional
     (seq_number NUMBER,
      data1  NUMBER,
      data2  CHAR(15);
     - sequence
    CREATE SEQUENCE db_seq
     START WITH 1
     INCREMENT BY ;
     - control file
     LOAD DATA
     INFILE *
     INTO TABLE load_db_seq_positional
     (seq_number "db_seq.nextval",
      data1  POSITION(1:5),
      data2   POSITION(6:15)
     )
     BEGINDATA
     11111AAAAAAAAAA
     22222BBBBBBBBBB
     - 결과
    select * from load_db_seq_positional;
    SEQ_NUMBER DATA1 DATA2
    ----------      -------  -----------
             1 11111  AAAAAAAAAA
             2 22222  BBBBBBBBBB

    [예제2]
    이번은 comma로 구분되는 data file이다. 여기에서 key는 file이 구분되어
    있다는 것이다.
    sql*loader는 data file에서 seq_number field값을 찾으려 할 것이다. 이
    경우 마지막 field인 seq_number값을 넣어 주어야 하는데 그 값이 없을 경우
    TRAILING NULLCOLS를 이용하여 loader에게 지정해 주어야 한다.
    trailing columns는 null or non_existent이다.
    위의 예제와 이름만 다른 (load_db_seq_delimited) table과 같은 sequence를
    사용하여 다음을 실행한다.
     - control file
     LOAD DATA
     INFILE *
     INTO TABLE load_db_seq_delimited
     FIELDS TERMINATED BY ","
     TRAILING NULLCOLS
     (data1,
      data2,
      seq_number "db_seq.nextval"
     )
     BEGINDATA
     11111,AAAAAAAAAA
     22222,BBBBBBBBBB
     - 결과
    select * from load_db_seq_delimited;
    SEQ_NUMBER DATA1 DATA2
    ----------    -------  ------
             3 11111  AAAAAAAAAA
             4 22222  BBBBBBBBBB
    sql*loader에서 위의 경우 아래와 같은 insert문을 만든다.
    INSERT INTO load_db_seq_delimiter (data1,data2,seq_number)
      VALUES (:data1,data2,"db_seq.nextval");
    위의 두방법 역시 direct path에서는 사용하지 못한다.
 
    3. sql*loader 실행 시 username을 load 하기
 
    [예제1]
    data file에서 field가 position으로 구분되어 지는 경우로 조금 쉽다.
    다음 방법은 delimiter로 구분된 것으로 좀 더 신경을 써야 한다.
    두 경우 모두 "USER" pseudo-variable을 사용하는데 만약 user ID를
    사용하려면 "UID"를 대신 사용한다.
     - table
    CREATE TABLE load_user_positional
     (username CHAR(30),
      data1  NUMBER,
      data2  CHAR(15);
     - control file
     LOAD DATA
     INFILE *
     INTO TABLE load_user_positional
     (username "USER",
      data1  POSITION(1:5),
      data2   POSITION(6:15)
     )
     BEGINDATA
     11111AAAAAAAAAA
     22222BBBBBBBBBB
     - 실행
     sqlldr scott/tiger load_user_d.ctl
     - 결과
    select * from load_user_positional;
      USERNAME  DATA1     DATA2
    ----------      -------   ----------
         SCOTT      11111       AAAAAAAAAA
         SCOTT      22222       BBBBBBBBBB
 
    [예제2]
    이 경우는 comma delimiter로 구분된 경우이다. 이경우 sql*loader는
    USERNAME field를 찾을 것이다.
    그러나 그 값이 없으므로 USERNAME FIELD는 CONTROL FILE 제일 뒤에 둔다.
    그리고 "TRAILING NULLCOLS"절로 뒤의 null또는 존재하지 않는 값을
    trailing columns라는 것을 나타내 준다.
     - table
    CREATE TABLE load_user_delimited
     (username CHAR(30),
      data1  NUMBER,
      data2  CHAR(15);
     - control file
     LOAD DATA
     INFILE *
     INTO TABLE load_user_delimited
     FIELD TERMINATED BY ","
     TRAILING NULLCOLS
     (data1,
      data2,
      username "USER"
     )
     BEGINDATA
     11111,AAAAAAAAAA
     22222,BBBBBBBBBB
     - 실행
     sqlldr jack/jack load_user_d.ctl
     - 결과
    select * from load_user_delimited;
      USERNAME   DATA1  DATA2
    ----------      -------  ----------
      JACK            11111    AAAAAAAAAA
      JACK            22222    BBBBBBBBBB
    이 loader의 작업을 insert문으로 만들면
    INSERT INTO load_user_delimiter(data1,data2,username)
       VALUES(:data1,:data2,USER);
    이 방법 역시 direct mode에서는 사용할 수 없다.
 
 
 
 
============================
SQL*LOADER 성능 향상 기법
============================
 
    PURPOSE
    ---------
    이 문서는 SQL*LOADER 사용시 성능 향상 기법에 대해서 설명한다.

    Explanation
    -----------
    1. 테이블에 인덱스가 걸려있을 경우, 먼저 인덱스를 drop 시키고, SQL*Loader로
      데이터를 올린 후 index를 생성한다.
      1) 데이터를 로딩하는 동안 rollback 과 redo log의 양을 줄일 수 있다.
      2) B*Tree가 잘 balance 된다.
      중복된 데이터가 있는지 여부를 확인하기 위해서 index를 drop 시키지
      못하는 경우도 있다.
    2. 데이터를 로딩하기 전의 데이터를 삭제한 후 데이터를 로딩하기 위해서는 REPLACE
      옵션 대신 TRUNCATE 옵션을 사용한다.
      REPLACE 옵션을 사용할 경우 'DELETE FROM' 이라는 SQL 문장이 실행되게
      되는데, 이 경우 많은 양의 redo log 및 rollback 데이터가 발생하게 된다.
      반면 TRUNCATE 옵션을 사용할 경우 단순히 테이블을 truncate시키게 되므로
      redo log 및 rollback 정보가 발생하지 않는다.
    3. commit을 자주 하지 않을 경우 SQL*Loader가 보다 더 빨리 실행된다. command
      line 상에서 commit을 할 row 수의 최적화된 값을 지정할 수 있다. 이것은
      시스템의 메모리에 따라 달리 지정하게 되는데 ROWS = n (n은 사용자가 지정하는
      값)으로 지정하면 된다. 기본 값은 64이다. array 처리를 하여 속도가 개선되는
      것처럼 많은 양의 row를 처리한 후 commit을 하면 그 만큼 데이터베이스에 대한
      request가 줄어들게 된다.
    4. 데이터의 양이 많을 경우 redo log 파일의 크기가 충분하게 한다. 만약 redo log
      파일의 크기가 작을 경우 redo log를 switch할 때 마다 LGWR에서 timeout이
      발생하여 SQL*Loader에서 hang이 발생할 수 있다. 이와 같은 상황이 발생할 경우
      LGWR trace 파일이나 alert! log에 "file busy" 또는
      "can not advance to log sequence"라는 메시지가 남는다.
    5. bind array 크기를 가능한 크게 (단일 row 크기의 100배까지) 잡을 경우에도
      속도 개선이 가능하다. bind array 크기는 (#rows)*(maximum row size in bytes)
      값과 같다.
      이것은 파라미터 BINDSIZE 값을 지정하여 설정할 수 있다. ROWS 값도 적절하게
      큰 값을 사용하여야 한다. 그렇지 않을 경우 SQL*Loader에서는 기본값을 사용하게
      된다.
    6. SQL*Loader를 single-task로 운영되는 'sqlloaderst'로 link시킬 경우
      two-task에서 발생하는 overhead를 줄일 수 있다. 보통 benchmark test등에
      적절하다.
    7. 큰 데이터 파일을 loading할 때에는 tablespace에 rollback segment가 있고
      크기가 충분히 큰지 여부를 확인하여야 한다. 만약 tablespace에 freespace가
      없거나 적은 갯수의 rollback segment밖에 없을 경우에는 SQL*Loader가 hang이
      걸리거나 에러가 발생하게 된다.
    8. DIRECT PATH 옵션을 사용한다. 이 옵션을 사용하면 데이터가 SQL engine을 거치지
      않고 데이터파일에 바로 write된다. SQL engine을 거치지 않으므로 rollback
      segment도 사용되지 않는다.
      만약 DIRECT PATCH 옵션을 사용중이라면 다음과 같은 옵션을 사용하여 추가적인
      속도 개선을 얻을 수 있다.
    9. UNRECOVERABLE 절을 사용하면 loading 되는 데이터가 redo log에 기록되지
      않는다. 테이블에 index가 걸려 있을 경우에는 index에 대한 데이터는 redo log에
      기록된다.
    10. 테이블에 index가 걸려있고, 데이터가 index에 저장되는 순서되로 미리 정렬되어
      있다면 SORTED INDEX절을 사용할 수 있다.
      1) 데이터를 load 하기 전에 table은 empty 상태이어야만 한다.
      2) SORTED INDEX 절을 사용하는데 데이터가 올바로 정렬되어 있지 않다면
        인덱스의 status가 DIRECT LOAD 상태로 남게 된다.
    11. PARALLEL 옵션을 사용한다. 다중 SQL*Loader 세션을 사용할 경우 한개의 테이블
      도  parallel하게 loading 될 수 있다.
 
 
=======================================
SQL*LOADER 실행 시 발생하는 ORA-1653
=======================================
 
    PURPOSE
    ---------
    다음은 SQL*LOADER 실행시 ORA-1653 ERROR가 발생시에 조치하는
    방법을 설명한다.

    Explanation
    -------------
    ORA-1653 error 는 특정 tablespace 에 space 가 부족해서 table의
    extent가 일어나지 못해서 발생하는 error 이다 .
    먼저 error message 에서 tablespace name 이 무엇인지 먼저
    check 한다.
    그리고 다음 command 를 이용해 해당 tablespace 를 늘려주면 된다.
    ALTER TABLESPACE tablespace_name ADD DATAFILE '.....' size 100m;
    그러나  이때의 tablespace 가 SYSTEM 일 경우는 user 의 default
    tablespace 가 잡혀있지 않기 때문이어서 근본적인 해결이 필요하다.
    이 경우는 무작정 tablespsace 를 늘리지 말고  user 의 default
    tablespace 를 create 후 user 에게 할당해주도록 한다.
    CREATE TABLESPACE tablespace_name datafile '...' size 100m;
    ALTER USER user_name IDENTIFIED BY passwd
    DEFAULT TABLESPACE  tablespace_name
    TEMPORARY TABLESPACE temp ;
    위와 같이 user의 default tablespace 를 변환한 후, 이 default
    tablespace 안에 create table을 다시 한 후 sql*loader 를 실행한다.
 
 
 
===========================================================
음수 부호가 뒤에 있을 경우 LOADER로 숫자 DATA를 올리는 방법
===========================================================

    PURPOSE
    ---------
    음수 부호가 뒤에 있을 경우 LOADER로 숫자 DATA를 올리는 방법에
    대해 알아보도록 한다.

    Explanation
    -----------
    간혹 SQL*LOADER를 사용하는 사람들 중에 data file 내의
    숫자 field의 부호가 뒤에 붙어 있거나 특정한 값을 읽어
    그 값이 무엇인가에 따라 숫자를 음수와 양수로 만들어야
    하는 경우가 있다. 이경우에는 SQL*LOADER의 기능으로는
    해결방법이 없으나 다음과 같이 SQL 함수를 사용해서 해결
    이 가능하다.
    만약 다음과 같은 table이 있다고 가정한다.
    create table test_table
    (a number(3), b number(3));
    그리고 data가 있는 test.dat는 다음과 같다고 가정한다.
    ============
    100-,3
    222,1
    ============
    그렇다면 control file을 다음과 같이 작성하여 loader를
    동작시키면 원하는 결과를 얻을수 있다.
    test.ctl
    ============
    load data
    infile 'test.dat'
    replace
    into table test_table
    fields terminated by ","
    trailing nullcols
    (a integer external "decode(substr(:a,-1),'-',rtrim(:a,'-')*-1,:a)",
    b integer external)
    ======================
    이때 sqlplus 에서 확인을 하면 결과는 다음과 같다.
    SQL> select * from test_table;
         A     B
     ------  -----
       -100    3
        222    1
 
 

======================================================
TAPE로 EXPORT, IMPORT!, LOADER 사용하기(PIPE 사용)
======================================================

    Purpose
    -------
     대용량의 DATA를 BACKUP 받거나 DATA를 처리할 때에는 TAPE을 이용하는
    경우가 있다. 이럴 때 EXPORT, IMPORT!, SQL*LOADER에서 TAPE 를 이용하는
    방법을 종류별로 정리하였다.

    Explanation
    -----------
    1. TAPE DEVICE로 EXPORT 받기
    %  exp userid=system/manager full= y file=/dev/rmt/0m volsize=245M
     FILE은 TAPE이 있는 DEVICE 이름이고 VOLSIZE는 TAPE에 들어갈 DATA의
    SIZE이다. 만약 첫번 째 TAPE이 245M에 이르게 되면 다음 TAPE를 넣으라는
    메시지가 나온다.
     (주의) VOLSIZE should be < tape capacity

    2.  Tape device에서 import!받기
    %  imp  userid=system/manager  full=y  file=/dev/rmt/0m  volsize=245M
      첫번째 TAPE이 245M에 이르면 다음 TAPE를 위한 메시지가 나온다.

    3. PIPE와 DD를 이용한 tape의 export
    %  mknod  /tmp/exp_pipe  p                    # Make the pipe
    %  dd  if=/tmp/exp_pipe  of=<tape device>  &  # Write from pipe to tape
    %  exp file=/tmp/exp_pipe <other options>     # Export to the pipe

    4. PIPE와 DD를 이용한 tape의 import!
    %  mknod  /tmp/imp_pipe  p                    # Make the pipe
    %  dd  if=<tape device>  of=/tmp/imp_pipe  &  # Write from tape to pipe
    %  imp file=/tmp/imp_pipe  <other options>    # Import! from the pipe

    5. PIPE 와 DD를 이용한 remote server의 tape device에 export하기
    %  mknod  /tmp/exp_pipe  p
    %  dd  if=/tmp/exp_pipe | rsh <hostname> dd  of=<file or device> &
    %  exp file=/tmp/exp_pipe <other options>

    6. PIPE 와 DD 를 이용한 remote server의 tape device에서 import!하기
    %  mknod /tmp/imp_pipe  p
    %  rsh <hostname> dd if=<file or device | dd of=/tmp/imp_pipe &
    %  imp file=/tmp/imp_pipe <other options>

    7. TAPE에 있는 DATA FILE을 SQL*LOADER로 받기.
    %  mknod  /tmp/load_pipe  p
    %  dd  if=<tape_device>  of=/tmp/load_pipe  &
    (주의)  만약 tape이 EBCDIC이면 다음 명령으로 ASCII로 바꾸어 줍니다.
    %  dd if=<tape_device  conv=ascii  of=/tmp/load_pipe  &
    %  sqlldr userid=user/pass control=contol.ctl log=loader.log
       infile='/tmp/load_pipe'
    * PIPE는 I/O operation의 보다 빠른 상호 작용을 위한 memory 안의 가상
     화일이다. PIPE buffer는 Sun Solaris에서는 5K, HP에서는 8K, SGI에서는
     10K이다. 이것은 FIFO를 따르며 command는 다음과 같다.
    % mknod filename p
    * DD는 한 device로부터 다른 곳으로 data를 raw copy하는 명령어이다.
 
 
===========================================
LOADER에서 MAXIMUM SPECIFIED LENGTH
===========================================

    PURPOSE
    ---------
    sql*loader로 작업시 "Field in data file exceeded maximum specified
    length" 메세지가 발생하는 원인과 해결 방안에 대해 알아보도록 한다.

    Explanation
    -----------
    sql*loader로 작업을 하다보면 varchar2 column이 255 character가 넘는
    경우에 "Field in data file exceeded maximum specified length"라는
    message를 .log에 남기면서 해당 field가 reject되는 경우가 있다.
    이경우의 해결 방법은 control file의  column의 길이를 "col1 char(300)"
    처럼 기술해 주면 된다.
    그 이유는 다음과 같다.
    sql*loder는 보통 default로 buffer를 64K로 잡아둔다. 작업이 시작되면
    loader는 이 buffer에 얼마나 많은 row를 넣을 것인지를 결정하기 위하여
    control file 내의 column의 최대길이에 대한 계산을 하게 된다. 이때 그
    값이 data type에 의하거나 명시적으로 기술되어 있지 않으면 loader는
    나름대로의 길이를 가정하게 되는데 이때 char값은 255로 가정되게 된다.
    그러므로 255가 넘는 값은 위에서와 같은 error를 만들게 된다.
    한가지 더 알아둘 필요가 있는 것은 작은 filed의 길이를 명시해 두는 경우가
    loader의 효율을 높일수 있다는 것이다. 명시된 값은 그 길이만큼 buffer에
    넣을때 적용이 되므로 255보다 적은 값이 결과적으로 buffer에 들어갈 filed의
    길이를 적게 만들어서 한번에 더 많은 row를 load할 수 있게 한다.
 
 
===================================================
 CONVENTIONAL PATH LOAD & DIRECT PATH LOAD
===================================================
  매우 많은 양의 데이타를 빠른 시간 내에 load하고자하는 경우 direct path load를
  사용할 수 있다. 여기에서 이러한 direct path load의 자세한 개념 및 사용방법,
  사용 시 고려해야 할 점 등을 설명한다.

  1. conventional path load
    일반적인 sql*loader를 이용한 방법은 존재하는 table에 datafile 내의 data를
    SQL의 INSERT command를 이용하여 insert시킨다. 이렇게 SQL command를
    이용하기 때문에 각각의 데이타를 위한 insert command가 생성되어 parsing되는
    과정이 필요하며, 먼저 bind array buffer (data block buffer) 내에 insert되는
    데이타를 입력시킨 후 이것을 disk에 write하게 된다.
    conventional path load를 사용하여야 하는 경우는 다음과 같다.
    --- load 중에 table을 index를 이용하여 access하여야 하는 경우
        direct load중에는 index가 'direct load state'가 되어 사용이 불가능하다.
    --- load 중에 index를 사용하지 않고 table을 update나 insert등을 수행해야 하는 경우
        direct load 중에는 table에 exclusive write(X) lock을 건다.
    --- SQL*NET을 통해 load를 수행해야 하는 경우
    --- clustered table에 load하여야 하는 경우
    --- index가 걸려 있는 큰 table에 적은 수의 데이타를 load하고자 할 때
    --- referential이나 check integrity가 정의되어 있는 큰 table에
        load하고자 할 때
    --- data field에 SQL function을 사용하여 load하고자 할 때

  2. direct path load의 수행 원리
    Direct Path Loads는 다음과 같은 특징들로 인하여 매우 많은 양의 데이타를
    빠른 시간에 load하고자 할 때 이용하는 것이 바람직하다.
    (1)  SQL INSERT 문장을 generate하여 수행하지 않는다.
    (2)  memory 내의 bind array buffer를 이용하지 않고 database block의
         format과 같은 data
         block을 memory에 만들어 데이타를 넣은 후 그대로 disk에 write한다.
         memory 내의 block buffer와 disk의 block은 그 format이 다르다.
    (3)  load 시작 시에 table에 lock을 걸고 load가 끝나면 release시킨다.
    (4)  table의 HWM (High Water Mark) 윗 부분의 block에 data를 load한다.
         HWM는 table에 data가 insert됨에 따라 계속 늘어나고 truncate 외에는
         줄어들게 하지 못한다.
         그러므로, 항상 완전히 빈 새로운 block을 할당받아 data를 입력시키게 된다.
    (5)  instance failure가 발생하여도 redo log file을 필요로 하지 않는다.
    (6)  UNDO information을 발생시키지 않는다.
         즉 rollback segment를 사용하지 않는다.
    (7)  OS에서 asynchronous I/O가 가능하다면, 복수개의 buffer에 의해서 동시에
         data를 읽어서 buffer에 write하면서 buffer에서 disk로 write할 수 있다.
    (8)  parallel option을 이용하면 더욱 성능을 향상시킬 수 있다.

  3. direct path load의 사용방법 및 options
    direct path load를 사용하기 위한 view들은 다음 script에 포함어 있으며,
    미리 sys user로 수행되어야 한다. 단 이 script는 catalog.sql에 포함되어 있어,
    db 구성 시에 이미 수행되어진다.
    @$ORACLE_HOME/rdbms/admin/catldr.sql
    direct path load를 사용하기 위해서는 일반적인 sqlload 명령문에 DIRECT=TRUE를
    포함시키기만 하면 된다. 다음과 같이 기술하면 된다.
    sqlload username/password control=loadtest.ctl direct=true
    이 direct path load를 사용 시에 고려할 만한 추가적인 option 및 control file
    내에 기술 가능한 clause들을 살펴본다.
    (1) ROWS = n
        conventional path load에서 rows는 default가 64이며, rows에 지정된 갯수
    만큼의 row가 load되면 commit이 발생한다. 이와 비슷하게 direct load
    path에서는 rows option을 이용하여 data save를 이루며, data save가 발생하면
        data는 기존 table에 포함되어 입력된 data를 잃지 않게 된다.
    단 이 때 direct path load는 모든 data가 load된 다음에야 index가
    구성되므로 data save가 발생하여도 index는 여전히 direct load state로
    사용하지 못하게 된다.
        direct path load에서 이 rows의 default값은 unlimited이며, 지정된 값이
    database block을 채우지 못하면 block을 완전히 채우는 값으로 올림하여,
    partial block이 생성되지 않도록 한다.
    (2) PIECED clause
        control file내에 column_spec datatype_spec PIECED 순으로 기술하는
        것으로서 direct path load에만 유효하다. LONG type과 같이 하나의 data가
        maximum buffer size보다 큰 경우 하나의 data를 여러번에 나누어 load하는
        것이다. 이 option은 table의 맨 마지막 field
        하나에만 적용가능하며, index column인  경우에는 사용할 수 없다.
        그리고 load도중 data에 문제가 있는 경우 현재 load되는 data의 잘린 부분만
        bad file에 기록되게 된다. 왜냐하면 이전 조각은 이미 datafile에 기록되어
        buffer에는 남아있지 않기 때문이다.
    (3) READBUFFERS = n (default is 4)
        만약 매우 큰 data가 마지막 field가 아니거나 index의 한 부분인 경우
        PIECED option을 사용할 수 없다. 이러한 경우 buffer size를 증가시켜야
        하는데 이것은 readbuffers option을 이용하면 된다. default buffer갯수는
        4개이며, 만약 data load중 ORA-2374(No more slots for read buffer
        queue) message가 나타나면, buffer갯수가 부족한 것이므로 늘려주도록 한다.
        단 일반적으로는 이 option을 이용하여 값을 늘린다하더라도 system
        overhead만 증가하고 performance의 향상은 기대하기 어렵다.

  4. direct path load에서의 index 처리
    direct path load에서 인덱스를 생성하는 절차는 다음과 같다.
    (1) data가 table에 load된다.
    (2) load된 data의 key 부분이 temporary segment에 copy되어 sort된다.
    (3) 기존에 존재하던 index와 (2)에 의해서 정렬된 key가 merge된다.
    (4) (3)에 의해서 새로운 index가 만들어진다.
        기존에 존재하던 index와 temporary segment, 그리고 새로 만들어지는 index가
        merge가 완전히 끝날 때까지 모두 존재한다.
    (5) old index와 temporary segment는 지워진다.
    이와 같은 절차에 반해 conventional path load는 data가 insert될 때마다 한
    row 씩 index에 첨가된다. 그러므로 temporary storage space는 필요하지 않지만
    direct path load에 비해 index 생성 시간도 느리고, index tree의 balancing도
    떨어지게 된다.
    index생성 시 필요한 temporary space는 다음과 같은 공식에 의해 예측되어질 수
    있다.
    1.3 * key_storage
    key_storage = (number_of_rows) * (10 + sum_of_column_sizes +
                   number_of_columns)
    여기에서 1.3은 평균적으로 sort 시에 추가적으로 필요한 space를 위한 값이며,
    전체 data가 완전히 순서가 거꾸로 된 경우에는 2, 전체가 미리 정렬된 경우라면
    1을 적용하면 된다.
    --- SINGLEROW clause
    이와 같이 direct path load에서 index 생성 시 space를 많이 차지하는 문제점
    때문에 resource가 부족한 경우에는 SINGLEROW option을 사용할 수 있다.
    이 option은 controlfile 내에 다음과 같은 형태로 기술하며, direct path
    load에만 사용 가능하다.
    into tables table_name [sorted indexes...] singlerow
    이 option을 사용하면 전체 data가 load된 뒤에 index가 구성되는 것이 아니라
    data가 load됨에 따라 data 각각이 바로 index에 추가된다.
    이 option은 기존에 미리 index가 존재하는 경우 index를 생성하는 동안
    merge를 위해 space를 추가적으로 필요로 하는 것을 막고자 하는 것이므로
    INSERT 시에는 사용하지 않고, APPEND시에만 사용하도록 하고 있다.
    실제 새로 load할 data 보다 기존 table이 20배 이상 클 때 사용하도록 권하고
    있다.
    direct path load는 rollback information을 기록하지 않지만, 이 singlerow
    option을 사용하면 insert되는 index에 대해 undo 정보를 rollback segment에
    기록하게 된다.
    그러나, 중간에 instance failure가 발생하면 data는 data save까지는 보존
    되지만 index는 여전히 direct load state로 사용할 수 없게 된다.

    --- Direct Load State
    만약 direct path load가 성공적으로 끝나지 않으면 index는 direct load
    state로 된다.
    이 index를 통해 조회하고자 하면 다음과 같은 오류가 발생한다.
    ORA-01502 : index 'SCOTT.DEPT_PK' is in direct load state.
    index가 direct load state로 되는 원인을 구체적으로 살펴보면 다음과 같다.
    (1) index가 생성되는 과정에서 space가 부족한 경우
    (2) SORTED INDEXES clause가 사용되었으나, 실제 data는 정렬되어 있지 않은
        경우
    이러한 경우 data는 모두 load가 되고, index만이 direct load state로 된다.
    (3) index 생성 도중 instance failure가 발생한 경우
    (4) unique index가 지정되어 있는 컬럼에 중복된 data가 load되는 경우
    특정 index가 direct load state인지를 확인하는 방법은 다음과 같다.
    select index_name, status
    from user_indexes
    where table_name = TABLE_NAME';
    만약 index가 direct load state로 나타나면 그 index는 drop하고 재생성
    하여야만 사용할 수 있다. 단, direct load 중에는 모든 index가 direct
    load state로 되었다가 load가 성공적으로 끝나면 자동으로 valid로 변경된다.
    --- Pre-sorting (SORTED INDEX)
    direct load 시 index구성을 위해서 정렬하는 시간을 줄이기 위해 미리 index
    column에 대해서 data를 정렬하여 load시킬 수 있다. 이 때 control file 내에
    SORTED INDEXES option을 다음과 같이 정의한다.
    이 option은 direct path load 시에만 유효하며, 복수 개의 index에 대해서
    지정가능하다.
    into table table_name SORTED INDEXES (index_names_with_blank)
    만약, 기존의 index가 이미 존재한다면, 새로운 key를 일시적으로 저장할 만큼
    의 temporary storage가 필요하며, 기존 index가 없는 경우였다면, 이러한
    temporary space도 필요하지 않다.
    이와 같이 direct path load 시에 index 구성 시에는 기존 데이타가 있는 table에
    load하는 경우 space도 추가적으로 들고, load가 완전히 성공적으로 끝나지 않으면
    index를 재생성하여야 하므로, 일반적으로 direct path load 전에 미리 table의
    index를 제거한 후 load가 모두 끝난 후 재생성하도록 한다.

  5. Recovery
    direct load는 기존 segment중간에 data를 insert하는 것이 아니라 완전히
    새로운 block을 할당받아 정확히 write가 끝난 다음 해당 segment에 포함되기
    때문에 instance failure시에는 redo log정보를 필요로 하지 않는다. 그러나
    default로 direct load는 redo log에 입력되는 data를 기록하는데 이것은 media
    recovery를 위한 것이다. 그러므로 archive log mode가 아니면 direct load에
    생성된 redo log 정보는 불필요하게 되므로 NOARCHIVELOG mode시에는 항상
    control file내에 UNRECOVERABLE이라는 option을 사용하여 redo log에 redo entry를 기록하지
    않도록 한다.
    data가 redo log 정보 없이 instance failure시에 data save까지는 보호되는데
    반해 index는 무조건 direct load state가 되어 재생성하여야 한다. 그리고 data save이후의 load
    하고자 하는 table에 할당되었던 extent는 load된 data가 user에게 보여지지는 않지만 extent가
    free space로 release되지는 않는다.

  6. Integrity Constraints & Triggers
    direct path load중 not null, unique, primary key constraint는 enable
    상태로 존재한다. not null은 insert시에 check되고 unique는 load후 index를
    구성하는 시점에 check된다.
    그러나 check constraint와 referential constraint는 load가 시작되면서
    disable상태로 된다. 전체 데이타가 load되고 난 후 이렇게 disable된
    constraints를 enable시키려면 control file내에 REENABLE이라는 option을
    지정하여야 한다. 이 reenable option은 각 constraint마다 지정할 수는 없으며
    control file에 한번 지정하면 전체 integrity/check constraint에 영향을
    미치게 된다. 만약 reenable되는 과정에서 constraint를 위배하는 data가
    발견되면 해당 constraint는 enable되지 못하고 disabled status로 남게 되며,
    이렇게 위배된 data를 확인하기 위해서는 reenable clause에 exceptions option을 다음과 같이
    추가하면 된다.
    reenable [exceptions table_name]
    이 때 table_name은 $ORACLE_HOME/rdbms/admin/utlexcpt.sql을 다른
    directory로copy하여 table이름을 exceptions가 아닌 다른 이름으로 만들어 수행시키면 된다.
    insert trigger도 integrity/check constraint와 같이 direct load가 시작하는
    시점에 disable되며, load가 끝나면 자동으로 enable된다. 단 enable되고 나서도
    load에 의해 입력된 data에 대해 trigger가 fire되지는 않는다.   
Posted by 영 김해영

1. 정규식이란?

  • String의 검색,치환,추출을 위한 패턴.
  • 언어별 사용법은 대동소이함.
  • 패턴예>전화번호 형식, 이메일 형식 등.

2. 정규식 만들기

  1. Javascript 
    • var regexp = /pattern/[flags] ;
      var test = regexp.test(to be checked)
    • var regexp = new RegExp("pattern"[, "flags"]);
      var test = regexp.test(to be checked)
    • flags for javascript 
      • g : global match, 일반적으로 패턴이 1번만 발견되면 찾기를 종료하지만, g flag가 있으면, 문자열 내에서 모든 패턴을 찾는다.
      • i : ignore case, 대소문자를 고려하지 않고 체크한다.[a-z]와 [A-Z]는 같은 표현이 된다.
      • m : match over multiple lines, 여러 줄에 걸쳐 체크를 한다.
  2. Java
    • java.util.regex package
    • Pattern p = Pattern.compile("pattern");
      Matcher m = p.matcher("string to be checked");
      boolean b = m.matches();
    • boolean b = Pattern.matches("pattern", "string to be checked");

3. 정규식 표현법

*는 valid, 는 invalid
*두꺼운 글씨체는 매칭되는 부분.
*예제는 javascript  기준이며, 언어에 따라 다소 차이가 발생할 수 있다.
문자 용도 예제
\
  • 특수문자를 의미
  • 특수문자의 사용을 제외(특수문자 앞에서)
  • b는 b라는 글자를 의미 하지만 \b는 단어 경계를 의미
  • *은 0번이상 반복이라는 의미이지만, \*는 *이라는 글자를 의미.
^ 문자열의 시작. []안에서는 not의 의미
* ^A는 "A로 시작"이라기 보다는 "시작 직후에 A가 나온다"는 의미로 해석하는 것이 좋다. 즉, 시작과 끝과 같은 빈 공간을 하나의 문자로 간주하는 것이 좋다.
/^A/g
  • A string
  • an A
/[^A]/g
  • A string
  • an A
$ 문자열의 마지막
/t$/
  • eat
  • GREAT
* 0번 이상 반복 /ab*d/g
  • ad
  • abd
  • abdcdeabbbbdedb
  • ab
  • axd
+ 1번 이상 반복 ( = {1,} ) /ab+d/g
  • ad
  • abd
  • abdcdeabbbbdedb
  • ab
  • axd
? 0번 이나 1번 /e?le?/g
  • angel
  • angle
  • element
/abc\-?d/g
  • abc-d
  • abcd
. new line 을 제외한 모든 글자 /.n/g
  • nay, an apple is on the tree
  • nay
(x) x를 체크하고 체크한 값을 변수로 저장 /(f..) (b..)/
  • foo bar
    1th :foo
    2th :bar
(?:x) x를 체크하고 체크한 값을 변수로 저장하지 않음 /(?:f..) (b..)/
  • foo bar
    1th :bar
  • bar foo
x|y x 또는 y /green|red/
  • green apple
  • red apple
  • yellow apple
x(?=y) x후에 y가 나오고, x부분만 매칭되는 부분으로 간주 /Tmax(?=soft|hard)/
  • Tmaxsoft
  • Tmaxhard
  • Tmax soft
/Tmax(?=soft).*/
  • Tmaxsoft
  • Tmaxhard
  • Tmax soft
x(?!y) x가 나오고 그 뒤에 y가 있으면 안 됨 /Tmax(?!hard)/
  • Tmaxsoft
  • Tmaxhard
  • Tmax soft
{n} 앞에 지정한 것이 n개 /.{3}/
  • ab
  • abc
  • abcd
  • 홍길동
{n,} 앞에 지정한 것이 n개 이상 /.{3,}/
  • ab
  • abc
  • abcd
{n,m} 앞에 지정한 것이 n~m개 /.{3,5}/
  • ab
  • abc
  • abcd
  • 홍길동
[xyz] x나 y나 z. []안에는 얼마든지 쓸 수 있다. /[abc]{2}/
  • ab
  • abc
  • adbd
[x-z] x에서 z까지 /[a-z]{4,}/g
  • She sells sea shells by the sea shore는 Very 어렵다!
[^xyz] x,y,z를 제외한 나머지 모든 것 /[^a-z]{2,}/g
  • I'm a good man
  • I am A good Man
[\b] 백스페이스. \b와 혼동하지 말것. /[\b]/g
  • abcd
일반적인 String에서는 \b가 백스페이스를 의미한다.
\b 단어의 경계.[\b]와 혼동하지 말것. /\bn[a-z]/g
  • I am not a boy
  • online
  • nope
\B \b 를 제외한 전부 /\Bn[a-z]/g
  • noonday
  • online
  • nope
\cX 컨트롤X와 매칭. \cM은 컨트롤M과 매칭
\d 숫자.[0-9]와 같음 /\d/g
  • 7 eight 9
  • 123
/^0[0-9]{2}/g
  • 0120
  • 12011
\D \d 를 제외한 전부 /\D/g
  • 7 eight 9
  • 12?3
\f form-feed
\n new line
\r carriage return
\s white space
ex>탭, 띄어쓰기, \n, \r
/k\s/g
  • korea
  • blank is
  • blank
\S \s 를 제외한 전부 /k\S/g
  • korea
  • blank is
\t
\v vertical tab
\w 알파벳+숫자+_. [A-Za-z0-9_]와 동일 /\w/g
  • !@#$%^&*()+_-[]{}\|"':;,.<>?/
        _가 <b>를 먹여도 별로 티가 안 난다.
\W \w 빼고 전부 /\W/g
  • !@#$%^&*()+_-[]{}\|"':;,.<>?/
\n \n이 자연수일때, ()로 지정한 n번째 정규식 /(.{2})e tru\1 is \1at/
  • the truth is that ...
    1th :th
(th)가 \1로 지정된다.
\xhh hh는 hexacode, /[\x21-\x40]/g
  • !@#$%^&*()po
Code table 보기
\uhhhh hhhh는 hexacode, /[\u3131-\u3163\uac00-\ud7a3]/g
  • Tmax .
코드 번호> 3131:ㄱ 3163:ㅣ ac00:가 d7a3:힣 (javascript , java)

4. 정규식 사용 예제

/^[0-9]/
  • 09없다
  • 100점
  • 집이 10평
/^\w+$/
  • tmaxsoft
  • tmax(co)
  • tmax soft
/^[a-zA-Z][\w\-]{4,11}$/
  • tmax2010
  • tmax-2010!
  • 2010tmax
  • ILikegoooooooooooooooooogle
/^[0-9]{2,3}-[0-9]{3,4}-[0-9]{4}/
  • 02-6288-2114
  • 031-779-7114
  • 12-1234-5678
  • 02-6288-2114545
  • 02-0288-2114
/^0\d{1,2}-[1-9]\d{2,3}-\d{4}$/
  • 02-6288-2114
  • 031-779-7114
  • 12-1234-5678
  • 02-2123-12314545
  • 02-0288-2114
/^[\.a-zA-Z0-9\-]+\.[a-zA-Z]{2,}/
  • r-d.tmax.co.kr
  • r-d.tmax.co.kr입니다.
  • tmax..co.kr
  • a.com
/^(?:[\w\-]{2,}\.)+[a-zA-Z]{2,}$/
  • r-d.tmax.co.kr
  • r-d.tmax.co.kr입니다.
  • tmax..co.kr
  • a.com
/^[_a-zA-Z0-9\-]+@[\._a-zA-Z0-9\-]+\.[a-zA-Z]{2,}/
/^[\w\-]+@(?:(?:[\w\-]{2,}\.)+[a-zA-Z]{2,})$/
/^([a-z]+):\/\/((?:[a-z\d\-]{2,}\.)+[a-z]{2,})(:\d{1,5})?(\/[^\?]*)?(\?.+)?$/i
/^[ㄱ-ㅣ가-힣]+$/
  • 티맥스소프트
  • ㅜㅜ
  • ㅎㅎ

5. Javascript  정규식 함수

함수 코드예제 코드설명
Array RegExp.exec (to be checked)
var myRe=/d(b+)(d)/ig;
var myArray = myRe.exec("cdbBdbsbz");

/d(b+)(d)/ig

  • cdbBdbsbz
myArray.index =1 ; (처음으로 매칭되는 위치, 컴터가 늘 그렇듯 위치는 0번째부터 센다.)
myArray.input = cdbBdbsbz; (체크할 대상)
myArray[0] = dbBd;(검사에 통과한 부분)
myArray[1] = bB;(1번째 괄호에서 체크된 부분)
myArray[2] = d;(2번째 괄호에서 체크된 부분)

myRe.lastIndex =5 ; (다음번 체크를 하기위한 위치.)
myRe.ignoreCase = true; (/i 플래그 체크)
myRe.global = true; (/g 플래그 체크)
myRe.multiline = false; (/m 플래그 체크)

RegExp.$_ = cdbBdbsbz;(입력한 스트링)
RegExp.$1 = bB;(1번째 괄호에서 체크된 부분 )
boolean RegExp.test(to be checked)
var myRe=/d(b+)(d)/ig;
var checked = myRe.test("cdbBdbsbz");
document.write ("checked = " + checked +";<br>");

/d(b+)(d)/ig

  • cdbBdbsbz
실행결과: checked = true;
String RegExp.toString()
var myRe=/d(b+)(d)/ig;
var str = myRe.toString();
document.write (str);

실행 결과: /d(b+)(d)/ig
String String.replace(pattern or string, to be replaced)
var str = "abcdefe";
document.write (str.replace("e" , "f"));
실행 결과: abcdffe

e가 2번 있지만, 첫번째 인자가 정규식이 아니라 문자열일 경우는 첫번째 것만 바꾼다.
var str = "aba";
document.write (str.replace(/^a/ , "c"));
실행 결과: cba
var re = /(\w+)\s(\w+)/;
var str = "John Smith";
newstr = str.replace(re, "$2, $1");
document.write (newstr)
실행 결과: Smith, John

re에 의해서 찾아진 문자열 들은 re에서 ()로 표현된 순서대로 $1, $2와 같이 변수로 저장된다.
var re = /\s(?:http|https):\/\/\S*(?:\s|$)/g;
var str = "url is http://iilii.egloos.com/ ;!!\n";
str += "tmax home: http://www.tmax.co.kr";
newstr = str.replace(re, function (str,p1,offset,s) {
     return "<a href='" + str + "'>" + str + "</a>";
  }
).replace(/\n/, "<br>");
document.write (newstr);
url is http://iilii.egloos.com/ !!
tmax home:
http://www.tmax.co.kr

str: 찾은 문자열
p1: ()에서 검색된 1번째 문자열. 마찬가지로 p2,p3 등도 가능
offset: str을 찾은 위치
s : 원본 문자열.
Array String.match(regular expression
var str = "ABCdEFgHiJKL";
var myResult = str.match(/[a-z]/g );
for(var cnt = 0 ; cnt < myResult.length; cnt++){
    document.write (cnt +":" + myResult[cnt] +"<br>");
}

document.write ("비교<br>");

var str = "ABCdEFgHiJKL";
var myResult = /[a-z]/g.exec(str);
for(var cnt = 0 ; cnt < myResult.length; cnt++){
    document.write (cnt +":" + myResult[cnt] +"<br>");
}
실행 결과:
0:d
1:g
2:i
비교
0:d

String.match(RegExp) =>g flag가 있어도 다 찾아낸다.
RegExp.exec(String) =>g flag가 있으면, 한 개만 찾고 끝낸다.
Array String.split([separator[, limit]])
var str = "ABCdEFgHiJKL";
var myResult = str.split(/[a-z]/g , 3);
for(var cnt = 0 ; cnt < myResult.length; cnt++){
    document.write (cnt +":" + myResult[cnt] +"<br>");
}
실행 결과:
0:ABC
1:EF
2:H

주어진 문자열을 separator를 기준으로 limit 만큼 자른다.

6. 정규식으로 만든 유용한 Javascript  함수

String removeTags(input)

HTML tag부분을 없애준다
function removeTags(input) {
    return input.replace(/<[^>]+>/g, ""); 
};
example>
var str = "<b>Tmax</b> <i>soft</i>";
document.write (str +"<br>");
document.write (removeTags(str));
result>
Tmax soft
Tmax soft

String String.trim()

문자열의 앞뒤 공백을 없애준다.
String.prototype.trim = function() {
    return this.replace(/^\s+|\s+$/g, ''); 
};
example>
var str = "         untrimed string            ";
document.write ("========" + str+ "==============<br>");
document.write ("========" + str.trim() + "==============");
result>
======== untrimed string ==============
========untrimed string==============

String String.capitalize()

단어의 첫 글자를 대문자로 바꿔준다.
String.prototype.capitalize = function() {
    return this.replace(/\b([a-z])/g, function($1){
        return $1.toUpperCase();
    }) ;  
};
example>
var str = "korea first world best";
document.write (str.capitalize());
result>
Korea First World Best

String number_format(input)

입력된 숫자를 ,를 찍은 형태로 돌려준다
function number_format(input){
    var input = String(input);
    var reg = /(\-?\d+)(\d{3})($|\.\d+)/;
    if(reg.test(input)){
        return input.replace(reg, function(str, p1,p2,p3){
                return number_format(p1) + "," + p2 + "" + p3;
            }    
        );
    }else{
        return input;
    }
}
example>
document.write (number_format(1234562.12) + "<br>");
document.write (number_format("-9876543.21987")+ "<br>");
document.write (number_format("-123456789.12")+ "<br>");
result>
1,234,562.12
-9,876,543.21987
-123,456,789.12

7. Java 정규식 함수

<PRE>Pattern p = Pattern.compile("(a*)(b)");Matcher m = p.matcher("aaaaab");if (m.matches()) { for (int i = 0; i < m.groupCount() + 1; i++) { System.out.println(i + ":" + m.group(i)); }} else { System.out.println("not match!");}result>0:aaaaab1:aaaaa2:b0번째는 매칭된 부분.</PRE>
<PRE>String a = "I love her";System.out.println(a.replaceAll("([A-Z])", "\"$1\""));result>"I" love her자바도 $1을 쓸 수 있다.</PRE>
<PRE>Pattern p = Pattern.compile("cat");Matcher m = p.matcher("one  cat two cats in the yard");StringBuffer sb = new StringBuffer();while (m.find()) { m.appendReplacement(sb, "dog"); System.out.println(sb.toString());}m.appendTail(sb);System.out.println(sb.toString());result>one dogone dog two dogone dog
Posted by 영 김해영
=======================================================================
출처 : http://www.ricky.co.kr/jsboard/read.php?table=system&no=17&page=1
=======================================================================
[제목] 아파치 로그 파일 - 특이한 녀석들은 따로 담거나 없애기

* 제목이 좀 이상하네요........^.^

작성자 : 김칠봉 <san2(at)linuxchannel.net>
작성일 : 2001. 04. 30
대상자 : 초보

- 힌트 URL : 임은재님이 쓴 글을 보고 나서
   http://kltp.kldp.org/stories.php?story=00/10/22/9724184

- 관련 문서 : 아파치 제공 문서
   http://httpd.apache.org/docs/mod/mod_log_config.html
   http://httpd.apache.org/docs/mod/mod_setenvif.html


목차

1. 배경

2. 기초지식
  2-1. 로그포맷과 CustomLog 지시자
  2-2. 아파치 환경변수 설정

3. 예제
  3-1. 특정 IP 주소만 환경변수로 설정하기
  3-2. 특정 타입의 파일만 환경변수로 설정하기
  3-3. 특정 User-Agent 만 환경변수로 설정하기
  3-4.  종합예제 : 사오정(?) 로그 분석 피하기

------------------------------------------

1. 배경

몇 달 전부터 Webalizer 라는 로그 분석기로 제가(이하 '필자') 운영하는 싸이트의
로그를 대충 분석(?)해 봤는데 사오정(?) 분석이 되어 버렸더군요.
결정적으로 필자가 운영하는 싸이트의 대부분은 php로 구성되어 있는데, 이는 실제로

 - 방문자 외에 localhost에서 php가 실행하는 로그 기록
 - 로봇들의 접근 기록
 - 운영자(필자)가 접근한 로그 기록

 등등이 함께 기록되어 있어 순수 방문자 통계에 약간 덜(?) 정확한 통계가 나오더군요.
 따라서 이런 유형들의 로그는 없애거나 따로 로그기록하는 것이 낫을 것 같더군요.

 위의 내용이 이하 다루는 내용입니다.


2. 기초지식

2-1. 로그포맷과 CustomLog 지시자

Module mod_log_config 은 아파치 기본 모듈입니다.

    로그 포맷 스트링

    %a : 원격의 IP 주소
    %b : 헤더를 포함한 전송량(bytes)
    %{var}e : 환경 변수 "var"
    %f : 파일이름
    %h : 원격의 호스트
    %{hdr}i : 서버에 들어오는(요청) 헤더 값 "hdr"
    %l : 원격의 로그인 ID(지원한다면)
    %{label}n : 다른 모듈에서 "label" 구성
    %{hdr}o : 응답 헤더 값 "hdr"
    %p : 서버의 Canonical 포트 번호
    %P : 자식 프로세스 ID(PID)
    %r : 첫번째 요청 라인
    %s : 상태코드
    %t : 시간 포맷(CLF 포맷)
    %{format}t : "format"으로 구성된 시간 포맷
    %T : 서버에 요청하는 시간(초)
    %u : 원격의 유저이름(인증시)
    %U : 요청한 URL
    %v : 클라이언트 요청에 따른 Canonical 서버네임
    %V : UseCanonicalName 설정에 따른 서버네임

일반적으로 아파치를 설치하고 나면, 다음과 같이 기본설정되어 있을 겁니다.
(굳지 수정할 필요없음)

    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b" common
    LogFormat "%{Referer}i -> %U" referer
    LogFormat "%{User-agent}i" agent

    CustomLog /usr/local/apache/logs/access_log common

물론 이 정도는 다 알고 계시리라 믿습니다.

    로그 기록 지시자

    CookieLog
    CustomLog
    LogFormat
    TransferLog

CookieLogs는 제외하고 나머지 지시자에 대해서 알아봅시다.

LogFormat 지시자

Syntax: LogFormat format|nickname [nickname]
Default: LogFormat "%h %l %u %t \"%r\" %>s %b"
Context: server config, virtual host
Status: Base
Compatibility: Nickname only available in Apache 1.3 or later
Module: mod_log_config


CustomLog 지시자

Syntax: CustomLog file|pipe format|nickname [env=[!]environment-variable]
Context: server config, virtual host
Status: Base
Compatibility: Nickname only available in Apache 1.3 or later.
Conditional logging available in 1.3.5 or later.
Module: mod_log_config


TransferLog 지시자

Syntax: TransferLog file|pipe
Default: none
Context: server config, virtual host
Status: Base
Module: mod_log_config

LogFormat 지시자는 앞에서 예제가 있으므로 생략하고 비슷한 기능을 가진
CustomLog, TransferLog 지지자에 대해서 잠깐 알아봅시다.

둘다 file 에 로그를 기록한다는 면에서는 동일한 기능입니다.(pipe 기능도 동일)

    같은점

    1. 둘다 file에 로그를 기록한다.
    2. |pipe 에 설정한 외부 프로그램을 인자로 사용할 수 있다.
    3. 둘다 다중 로그를 사용할 수 있다.

    다른점

    1. CustomLog 지시자는 LogFormat 지시자에서 설정한 nickname이나 Log format을
        인자로 사용할 수 있다.
    2. CustomLog 지시자는 환경변수를 인자로 가질 수 있다.


따라서,
이하 다룰 내용은 환경변수를 설정하고 이 환경변수(env)와 동일(=)하거나 그렇지 않은(!=)
특정 유형에 대해서 로그에 기록해야하 하므로 당연히 CustomLog 지시자를 사용해야
합니다.

*주의)
환경변수를 인자로 사용할 경우 하나만 가능.

이해가 되셨는지 모르겠네요....


2-2. 아파치 환경변수 설정

아파치에서 환경변수를 설정하는 방법은 몇가지 있습니다.
예를 들어,

    - mod_env 모듈에 의한
       SetEnv
       지시자 이용

    - mod_setenvif 모듈에에 의한
       BrowserMatch
       BrowserMatchNoCase
       SetEnvIf
       SetEnvIfNoCase
       지시자 이용

등이 있습니다.
여기에서 주로 사용할 지시자는 BrowserMatch 지시자와 SetEnvIf 지시자입니다.

*참고)
xxxxNoCase 지시자는 대소문자를 구분하지 않겠다는 의미입니다.

이 두개의 지시자에 대한 사용법만 간단하게 알아봅시다.

BrowserMatch 지시자

Syntax: BrowserMatch regex envar[=value] [envar[=value]] ...
Default: none
Context: server config, virtual host, directory, .htaccess
Override: FileInfo
Status: Base
Module: mod_setenvif
Compatibility: Apache 1.2 and above (in Apache 1.2 this directive was found in the
now-obsolete mod_browser module); use in .htaccess files only supported with
1.3.13 and later


SetEnvIf 지시자

Syntax: SetEnvIf attribute regex envar[=value] [envar[=value]] ...
Default: none
Context: server config, virtual host, directory, .htaccess
Override: FileInfo
Status: Base
Module: mod_setenvif
Compatibility: Apache 1.3 and above; the Request_Protocol keyword and
environment-variable
matching are only available with 1.3.7 and later; use in .htaccess files only supported
with 1.3.13 and later

   환경변수 지정 및 값 지정 방법

   1.varname, or
   2.!varname, or
   3.varname=value

   예 :

   BrowserMatch ^Mozilla forms jpeg=yes browser=netscape
   BrowserMatch "^Mozilla/[2-3]" tables agif frames javascript
   BrowserMatch MSIE !javascript

   BrowserMatchNoCase Robot is_a_robot
   SetEnvIfNoCase User-Agent Robot is_a_robot

   BrowserMatchNoCase mac platform=macintosh
   BrowserMatchNoCase win platform=windows

   SetEnvIf Request_URI "\.gif$" object_is_image=gif
   SetEnvIf Request_URI "\.jpg$" object_is_image=jpg
   SetEnvIf Request_URI "\.xbm$" object_is_image=xbm
        :
   SetEnvIf Referer www\.mydomain\.com intra_site_referral
        :
   SetEnvIf object_is_image xbm XBIT_PROCESSING=1

   SetEnvIfNoCase Host Apache\.Org site=apache

   SetEnvIf 지자자에서 attribute 에 올 수 있는 것들 :

   Remote_Host - the hostname (if available) of the client making the request
   Remote_Addr - the IP address of the client making the request
   Remote_User - the authenticated username (if available)
   Request_Method - the name of the method being used (GET, POST, et cetera)
   Request_Protocol - the name and version of the protocol with which the request
                                    was made (e.g., "HTTP/0.9", "HTTP/1.1", etc.)
   Request_URI - the portion of the URL following the scheme and host portion
   그외
   Host, User-Agent, and Referer 가능

   see http://www.rfc-editor.org/rfc/rfc2616.txt

따라서,
BrowserMatch와 SetEnvIf User-Agent 는 서로 동일하다는 것을 알 수 있을 겁니다.

*중요)
CustomLog 지자자와 다르게 환경변수 인자에 여러 개를 설정할 수 있음.

여기까지 이해가 되셨다면 다음은 보지 않아도 될듯 하군요.......^.^


3. 예제

3-1. 특정 IP 주소만 환경변수로 설정하기

- 환경변수 이름 : do_not_log
- 목적 : 이 환경변수에 해당되는 특정 IP 주소는 access_log에 기록하지 않는다.

    SetEnvIf Remote_Addr "^127.0.0.1$" do_not_log
    SetEnvIf Remote_Addr "^211.35.159.12[89]$" do_not_log
    SetEnvIf Remote_Addr "^211.35.159.1[345][0-9]$" do_not_log

    위에서 설정한 특정 IP 주소는
    127.0.0.1 자기자신을 말하는 루프백 주소
    211.35.159.128, 211.35.159.129 두개의 IP 주소
    211.35.159.130 ~ 211.35.159.139 10개의 IP 주소
    211.35.159.140 ~ 211.35.159.149 10 개의 IP 주소
    211.35.159.150 ~ 211.35.159.159 10 개의 IP 주소

즉 원격의 IP 주소(Remote_Addr)가 위와 일치하면 do_not_log 환경변수에 지정하는 예임.

*참고)
^ 은 시작을 의미
$ 은 마지막을 의미
[0-9] 0~9까지의 숫자중 어느 하나


3-2. 특정 타입의 파일만 환경변수로 설정하기

    SetEnvIfNoCase Request_URI "\.(gif|jpg|png|css|js|java)$" do_not_log

    요청 URI(Request_URI) 파일이
    *.gif
    *.jpg
    *.png
    *.css
    *.js
    *.java

    로 끝난 파일인 경우(대소문자를 구별하지 않음) do_not_log 환경변수에 지정함


3-3. 특정 User-Agent 만 환경변수로 설정하기

- 환경변수 이름 : do_not_log 과 is_a_robot
- 목적 : 이 환경변수에 해당되는 특정 User-Agent는 access_log에 기록하지 않고,
             따로 로그(robot-log)에 기록하거나 기록하지 않기 위함.

    BrowserMatchNoCase "ru-robot" do_not_log is_a_robot
    BrowserMatchNoCase "Slurp/si" do_not_log is_a_robot
    BrowserMatchNoCase "Mercator" do_not_log is_a_robot
    BrowserMatchNoCase "Gulliver" do_not_log is_a_robot
    BrowserMatchNoCase "SyncIT/" do_not_log is_a_robot
    BrowserMatchNoCase "FAST-WebCrawler" do_not_log is_a_robot
    BrowserMatchNoCase "Lycos_Spider" do_not_log is_a_robot
    BrowserMatchNoCase "^ia_archive" do_not_log is_a_robot
    BrowserMatchNoCase "^tv" do_not_log is_a_robot
    BrowserMatchNoCase "Scooter" do_not_log is_a_robot
    BrowserMatchNoCase "ZyBorg/" do_not_log is_a_robot
    BrowserMatchNoCase "KIT-Fireball" do_not_log is_a_robot
    BrowserMatchNoCase "Googlebot/" do_not_log is_a_robot
    BrowserMatchNoCase "DIIbot/" do_not_log is_a_robot
    BrowserMatchNoCase "teoma_agent3" do_not_log is_a_robot
    BrowserMatchNoCase "empas_robot" do_not_log is_a_robot

모두 로봇으로 맞게 설정되었는지 모르겠네요...............T.T

각각의 정규표현식 조건에 맞는 User-Agent 인 경우, do_not_log 환경변수와
is_a_robot 이라는 두개의 환경변수에 설정한 예입니다.
is_a_robot 이라고 또 하나의 환경변수를 지정한 이유는 앞서 얘기했듯이
이 조건게 맞는 User-Agent가 접근할 경우 따로 로그에 기록하기 위함입니다.

이 BrowserMatchNoCase 지시자 대신에 SetEnvIfNoCase User-Agent 로 대신할 수 있는데
첫번째 부분을 다음과 같이 설정할 수 있습니다.(둘다 똑 같은 결과임)

    SetEnvIfNoCase User-Agent "ru-robot" do_not_log is_a_robot


3-4.  종합예제 : 사오정(?) 로그 분석 피하기

앞의 3개의 예제를 모두 적용하면 다음과 같습니다.

목적은 배경에서 설명했듯이 가능한 access_log 파일에

 - 방문자 외에 localhost에서 php가 실행하는 로그 기록은 기록하지 않는다.
 - 로봇들의 접근 기록은 따로 robot-log 파일에 기록한다.
 - 운영자가 주고 접근할 IP 주소는 로그에 기록하지 않는다.
 - *.gif, *.jpg, *.png, *.css, *.js, *.java 등과 같은 파일은 로그에 기록하지 않는다.

 이 정도의 조건이라면 가능한 어느 정도 수준으로 순수 방문자의 접근 기록만
 access_log 파일에 기록할 수 있습니다.

 -- httpd.conf(실제로 필자가 운영하는 아파치 설정 내용임) --------------------
##
## 중간 생략
##
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{User-Agent}i\"" use_robot
##
## 중간 생략
##

## 환경변수 do_not_log에 일치하지 않은 접근만 access_log 파일에 기록함.
##
CustomLog /usr/local/apache/logs/access_log combined env=!do_not_log

## 중간 생략

## 로봇들은 따로 robot_log 파일에 user_robot 포맷에 맞게 기록함
##
CustomLog /usr/local/apache/logs/robot_log use_robot env=is_a_robot

## 중간 생략

## 특정 IP 주소는 로그에 기록하지 않는다.
## 주로 서버 IP 주소와 운영자가 자주 접속하는 IP 주소들
##
SetEnvIf Remote_Addr "^127.0.0.1$" do_not_log
SetEnvIf Remote_Addr "^211.35.159.12[89]$" do_not_log
SetEnvIf Remote_Addr "^211.35.159.1[345][0-9]$" do_not_log

## 중간 생략

## 주로 이미지 파일을 요청했을 경우 로그에 기록하지 않는다.
##
SetEnvIfNoCase Request_URI "\.(gif|jpg|png|css|js|java)$" do_not_log

## 중간 생략

## 로봇들의 환경변수 지정
##
BrowserMatchNoCase "ru-robot" do_not_log is_a_robot
BrowserMatchNoCase "Slurp/si" do_not_log is_a_robot
BrowserMatchNoCase "Mercator" do_not_log is_a_robot
BrowserMatchNoCase "Gulliver" do_not_log is_a_robot
BrowserMatchNoCase "SyncIT/" do_not_log is_a_robot
BrowserMatchNoCase "FAST-WebCrawler" do_not_log is_a_robot
BrowserMatchNoCase "Lycos_Spider" do_not_log is_a_robot
BrowserMatchNoCase "^ia_archive" do_not_log is_a_robot
BrowserMatchNoCase "^tv" do_not_log is_a_robot
BrowserMatchNoCase "Scooter" do_not_log is_a_robot
BrowserMatchNoCase "ZyBorg/" do_not_log is_a_robot
BrowserMatchNoCase "KIT-Fireball" do_not_log is_a_robot
BrowserMatchNoCase "Googlebot/" do_not_log is_a_robot
BrowserMatchNoCase "DIIbot/" do_not_log is_a_robot
BrowserMatchNoCase "teoma_agent3" do_not_log is_a_robot
BrowserMatchNoCase "empas_robot" do_not_log is_a_robot

## 이하 생략
##
------------------------------------------------------

END
Posted by 영 김해영
document.oncontextmenu = new Function ("return false");
document.ondragstart = new Function ("return false");
document.onselectstart = new Function ("return false");
if ( document.body.style.MozUserSelect != undefined )
   document.body.style.MozUserSelect = "none";
Posted by 영 김해영

rotatelogs

2008/04/29 14:47
작성자 : 좋은진호(truefeel)
작성일 : 2000 아님 2001(언제인지 기억 안남 ^^)
수정일 : 2003.8.6(수)

접속자가 많은 사이트에서는 아파치 로그를 관리하는 것도 만만치 않다.
Linux, solaris 등에서 로그 파일 크기가 2GB가 넘어서 골치아픈 경우도 발생한다.
또한 로그 파일이 크면 클수록 서버에 필요없는 무리를 주는 것도 사실이다.


1) Apache 자체에서 제공하는 rotatelogs를 이용하여 로그를 시간과 용량에 따라
  분리 저장하는 방법
2) 보다 개선된 형태의 cronolog로 로그를 rotation하는 방법
3) images 로그와 윈도 OS 관련된 warm 로그를 별도로 저장하는 방법을 소개한다.

------------------------------------------------------------------------
1) weblog를 자동 rotation하기

로그를 관리하기 위해 logrotate 를 사용하는 경우가 많지만, 이는 서비스를 일시 중단해야
한다. 그러나 apache에서 제공하는 rotatelogs는 웹서버 동작중에 (1) 특정시간간격이나
(2) 특정 크기 단위로 로그를 저장한다.

예) CustomLog "|/usr/local/apache/bin/rotatelogs /usr/local/apache/logs/access.log 86400" common

위는 웹서버를 실행한지 86400초(24시간)간격으로 로그파일을 나눠서 저장한다.
로그 파일명은 'access.log.????' 형식으로 ???? 는 로그파일 생성시의 timestamp 이다.

즉, access.log.1060189068, access.log.1060120280 형태로 파일명이 만들어진다.
이런형태의 파일명이라면 이해하기 힘들 것이다. 좀더 쉽게 파일명을 만들어보자.

CustomLog "|/usr/local/apache/bin/rotatelogs /usr/local/apache/logs/access_log.%m%d-%H%M%S 86400" common
또는
CustomLog "|/usr/local/apache/bin/rotatelogs /usr/local/apache/logs/access_log.%m%d 86400" common

이제는 'access_log.월일-시분초' 또는 'access_log.월일' 형태로 생성될 것이다.

CustomLog "|/usr/local/apache/bin/rotatelogs /usr/local/apache/logs/access_log.%m%d-%H%M%S 100M" common

100M 단위로 로그를 저장할 수도 있다.

2) cronolog 툴로 개선된 형태로 log rotation하기

cronolog는 년월일에 따라 다른 디렉토리에 로그를 남길 수도 있다.
또한 webalizer 같은 웹로그 분석툴은 access_log 와 같이 지정한 파일을 분석하는데,
access_log.년월일' 처럼 로그 파일이 매번 바뀐다면 로그파일 지정하는게 쉽지않다.
cronolog 는 심볼릭 링크를 통해 이를 쉽게 해결해주고 있다.

http://www.cronolog.org/ 에서 cronolog-x.y.z.tar.gz 을 받아

./configure
make
make install

을 한다.

사용은 rotatelogs와 비슷하다.

 (1) 년월 단위로 로테이션한다.

 CustomLog "|/usr/local/sbin/cronolog /usr/local/apache/logs/access_log.%Y%m" common

 년월로 나눠서 'access_log.년월'형식으로 저장을 한다.

 /usr/local/apache/logs/access_log.200307
 /usr/local/apache/logs/access_log.200308

 (2) 다음과 같이 년월에 따라 다른 디렉토리에 로그를 나눌 수도 있다.

 CustomLog "|/usr/local/sbin/cronolog /usr/local/apache/logs/%Y/%m/access_log.%Y%m%d" common

 /usr/local/apache/logs/2003/07/access_log.20030701
 /usr/local/apache/logs/2003/07/access_log.20030702
 /usr/local/apache/logs/2003/07/access_log.20030703
 ...
 /usr/local/apache/logs/2003/08/access_log.20030801

 (3) 로그파일을 access_log로 심볼릭 링크해보자

 CustomLog "|/usr/local/sbin/cronolog --symlink=/usr/local/apache/logs/access_log
 /usr/local/apache/logs/access_log.%Y%m" common

 /usr/local/apache/logs/access_log -> /usr/local/apache/logs/access_log.200308
 /usr/local/apache/logs/access_log.200307
 /usr/local/apache/logs/access_log.200308

3) images 로그와 warm 로그는 별도로 저장

웹서버 튜닝 중에 이미지 파일만 별도의 웹서버로 분리하는 방법이 있다.
이 방법은 Request와 I/O 출력, 트래픽을 분산하는 효과가 있을 것이다.

이런 튜닝 방법과는 다르지만 images 로그만 별개로 파일로 저장하는 방법을 소개한다.
다음은 이미지 파일은 images_log에, Codered나 Nimda 등의 warm은 warm_log 에 저장하는 설정이다.

---------------------------------
SetEnvIf Request_URI "\.gif$"      except=images
SetEnvIf Request_URI "\.jpe?g$"     except=images
SetEnvIf Request_URI "\.png$"      except=images
SetEnvIf Request_URI "^/default\.ida"  except=warm   # Codered
SetEnvIf Request_URI "/root\.exe?"   except=warm   # Nimda
SetEnvIf Request_URI "/cmd\.exe?"    except=warm
SetEnvIf Request_URI "^/NULL\.printer" except=warm   #
SetEnvIf Request_URI "^/NULL\.IDA?"   except=warm
SetEnvIf Request_URI "^/NULL\.ida?"   except=warm
SetEnvIf Request_URI "^/NULL\.idq?"   except=warm

SetEnvIf except   images  images
SetEnvIf except   warm   warm

CustomLog logs/access_log common env=!except
CustomLog logs/images_log common env=images
CustomLog logs/warm_log  common env=warm
---------------------------------
 
URI 중에 .gif, .jpg, .jpeg, .png 로 끝나는 것은 except 변수에 images값으로 정의하고,
/default.ida, /root.exe?, /cmd.exe?, /NULL.printer, /NULL.IDA?, /NULL.ida? /NULL.idq?
등은 윈도의 IIS의 취약점을 이용한 웜 공격으로 except 변수에 warm 값으로 정의한다.
 
그리고 또한번 변수 except의 값이 images 인 것은 변수 images로 정의
변수 except의 값이 images 인 것은 변수 warm으로 정의
 
CustomLog 설정을 보면
env=!except 에 의해 image와 warm로그(except변수로 정의)를 제외한(=!) 것만 access_log
에 저장하게 된다.
마찬가지로 env=images, env=warm에 따라 각각 images_log, warm_log에 저장하게 된다.
 
 
cronolog 와 함께 쓸 때는 다음과 같이
 
CustomLog "|/usr/local/sbin/cronolog --symlink=/usr/local/apache/logs/access_log
/usr/local/apache/logs/access_log.%Y%m" common env=!except
CustomLog "|/usr/local/sbin/cronolog /usr/local/apache/logs/images_log.%Y%m" common env=images
CustomLog "|/usr/local/sbin/cronolog /usr/local/apache/logs/warm_log.%Y%m"  common env=warm 

[출처] rotatelogs|작성자 비색

Posted by 영 김해영
출처 : http://www.javapattern.info
이번글은 XML을 이용한 property설정을 이용하여 jakarta-log4j의 환경설정 및
테스트 구동파일에 대한 사용방법을 설명한다.

기본적으로 log4j를 다운로드 받아야 하며, 이 패키지는 퍼포먼스최적화, 
수행중의 다른 프로그램과의 충돌등에서 여러 가지 사항을 고려한 패키지이며,
가장 간단하게 로그를 기록하고 볼수 있는 좋은 패키지이다..

우선 log4j를 다운받아 클래스패스에 추가하여 애플리케이션 형태로 도는 프로그램을
테스트해보도록 한다.

<@NHN@LINEBREAKER@NHN@>
1. Log4J환경설정방법
- jakarta.apache.org에서 log4j의 최신판(binary)을 다운로드 받는다.
- 압축을 푼후 log4j library파일을 클래스 패스에 추가한다.

2. Log4J의 두가지 프로퍼티 형태
   Log4J는 일반적으로 XML형태의 설정을 가진 property와 properties형태로 읽어들이는
   두가지의 종류가 있다. 보통 개발을 할 경우 property파일을 이용하는 경우는 많으나
   xml에 대한 설정방법 및 환경에 대한 한글참조가 없어서 여기서는 xml을 이용한
   DOMConfigurator를 사용하여 샘플테스트를 해보도록 하겠다.

3. 샘플프로그램
   우선 내가 원하는 logger클래스를 사용할 수 있는 클래스를 팩토리패턴을 구사하여
   만들어보도록 하겠다.

   이파일은 LogUtil이란 클래스를 사용하였으며 본사이트에도 적용되어 있다.

/*
* @(#)LogUtil.java 1.00 2003.02.01
*
* Copyright Choi Ji Woong,  All Rights Reserved.
*
*
*/
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;

import java.io.IOException;
import java.net.URL;


/**
* XML Property logger test이네요..히히
*


*
* @author  놀새(ienvyou@jlook.com)
* @version 1.00, 2003.02.01
* @since   log4j-1.2.7
*/

public class LogUtil {
    //private static Logger logger = LogUtil.getLogger(OtherClass.class);
public static void init(String xmlFile) {
String resource = xmlFile;
        URL configFileResource = LogUtil.class.getResource(resource);
// System.out.println("URL =======> " + configFileResource +
" ::::: " + configFileResource.getFile());
        //DOMConfigurator.configure(configFileResource.getFile());
DOMConfigurator.configure(xmlFile);
}

public static Logger getLogger(Class c) {
return Logger.getLogger(c);
}
}

다음은 해당 클래스를 테스트해볼수 있는 세개의 클래스를 나열해보도록 하겠다.

/*
* @(#)OtherClass.java 1.00 2003.02.01
*
* Copyright Choi Ji Woong,  All Rights Reserved.
*
*
*/
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;

import java.io.IOException;
import java.net.URL;


/**
* XML Property logger test이네요..히히
*


*
* @author  놀새(ienvyou@jlook.com)
* @version 1.00, 2003.02.01
* @since   log4j-1.2.7
*/

public class OtherClass {
    public OtherClass() {
Logger logger = LogUtil.getLogger(OtherClass.class);
logger.debug("Other Class debug");
logger.info("Other Class info");
logger.warn("Other Class warn");
logger.fatal("Other Class fatal");
logger.error("Other Class error");
}
}

/*
* @(#)XMLPropertiesFileLoggerTest.java 1.00 2003.02.01
*
* Copyright Choi Ji Woong,  All Rights Reserved.
*
*
*/
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;

import java.io.IOException;
import java.net.URL;


/**
* XML Property logger test이네요..히히
*


*
* @author  놀새(ienvyou@jlook.com)
* @version 1.00, 2003.02.01
* @since   log4j-1.2.7
*/

public class XMLPropertiesFileLoggerTest {
   

    public static void main(String argv[]) {
LogUtil.init(argv[0]);
        Logger logger = LogUtil.getLogger(XMLPropertiesFileLoggerTest.class);
        // Add a bunch of logging statements ...
        logger.debug("Hello,놀새1~");
        logger.debug("Hello,놀새2~");
        logger.debug("Hello,놀새3~");
        logger.debug("Hello,놀새4~");
        logger.debug("Hello,놀새5~");

        logger.info("놀새정보1");
        logger.info("놀새정보2");
        logger.info("놀새정보3");
        logger.info("놀새정보4");
        logger.info("놀새정보5");

        logger.warn("아뛰~ 워닝이야~1");
        logger.warn("아뛰~ 워닝이야~2");
        logger.warn("아뛰~ 워닝이야~3");
        logger.warn("아뛰~ 워닝이야~4");
        logger.warn("아뛰~ 워닝이야~5");

        logger.error("헉뚜~~ 에러야1");
        logger.error("헉뚜~~ 에러야2" , new IOException("아~ 뛰바~"));
        logger.error("헉뚜~~ 에러야3");
        logger.error("헉뚜~~ 에러야4" , new IllegalStateException("Error !!"));


        logger.fatal("엇..치명타다1");
        logger.fatal("엇..치명타다2" , new SecurityException("Fatal Exception"));
        logger.fatal("엇..치명타다3");
        logger.fatal("엇..치명타다4" , new SecurityException("Fatal Exception"));

new OtherClass();
    }
}


위와 같이 작성이 될 수 있으며 실행방법은

컴파일
javac -classpath %LOG4J_HOME%\lib\log4j-1.2.7.jar; XMLPropertiesFileLoggerTest.java


실행
java -classpath %LOG4J_HOME%\lib\log4j-1.2.7.jar; XMLPropertiesFileLoggerTest config.xml

다음페이지에서는 여러가지 환경설정을 할 수 있는 xml파일을 소개한다.


<font face='굴림'>1. console appender
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration>

<appender name="A1" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n"/>
</layout>
</appender>


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

</log4j:configuration>

2. <?xml version="1.0" encoding="euc-kr" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!--
파일에 로그를 기록하는 샘플입니다.
기본적으로 로그파일의 이름을 기록할 수 있으며
해당설정은 root logger에 설정되어 있는 내용을 참조하여 설정이 가능합니다.
아래의 설정파일은 매일로그를 남길수 있는 형태여서 형태가 다양하게 나타날수 있습니다.
날짜변경이 되면 자동으로 로그가 바뀝니다.
-->
<log4j:configuration>

<appender name="A1" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="ienvyou.log"/>
<param name="Append" value="false"/>
<param name="DatePattern" value="'.'yyyy-MM-dd"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n"/>
</layout>
</appender>


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

</log4j:configuration>

3.  File에 로그를 계속적으로 쌓이게 하는 방법

<?xml version="1.0" encoding="euc-kr" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!--
파일에 로그를 기록하는 샘플입니다.
기본적으로 로그파일의 이름을 기록할 수 있으며
해당설정은 root logger에 설정되어 있는 내용을 참조하여 설정이 가능합니다.
-->
<log4j:configuration>

<appender name="A1" class="org.apache.log4j.FileAppender">
<param name="File" value="ienvyou.log"/>
<param name="Append" value="false"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n"/>
</layout>
</appender>


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

</log4j:configuration>


4. 출력을 XML포맷의 파일로 하는 방법
<?xml version="1.0" encoding="euc-kr" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!--
파일에 로그를 기록하는 샘플입니다.
기본적으로 로그파일의 이름을 기록할 수 있으며
해당설정은 root logger에 설정되어 있는 내용을 참조하여 설정이 가능합니다.
출력은 XML형태의 파일로 저장됩니다.
-->
<log4j:configuration>

<appender name="A1" class="org.apache.log4j.FileAppender">
<param name="File" value="ienvyou.log"/>
<param name="Append" value="false"/>
<layout class="org.apache.log4j.xml.XMLLayout"/>
</appender>


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

</log4j:configuration>

5. 파일이 일정용량(예제:100KB) 이 되면 xxx.log.1, xxx.log.2의 형태로 변경시키는 방법
<?xml version="1.0" encoding="euc-kr" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!--
파일에 로그를 기록하는 샘플입니다.
기본적으로 로그파일의 이름을 기록할 수 있으며
해당설정은 root logger에 설정되어 있는 내용을 참조하여 설정이 가능합니다.
아래의 설정파일은 일정파일사이즈가 되면 파일변경
-->
<log4j:configuration>

<appender name="A1" class="org.apache.log4j.RollingFileAppender">
<param name="File" value="ienvyou.log"/>
<param name="Append" value="false"/>
<param name="MaxFileSize" value="100KB"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n"/>
</layout>
</appender>


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

</log4j:configuration>

위와 같은 형태의 xml파일을 이용하면 다양한 로그를 남길수 있으며
유용하게 사용할 수 있으리라 본다.

Posted by 영 김해영

redirect

웹컨테이너는 sendRedirect() 메소드가 호출되면 브라우저에 응답을 보내는데

이 응답에는 브라우저가 웹 컨테이너의 응답을 받은 후 다시 요청을 보낼 새로운 URL이 포함되있다.

브라우저에서 완전히 새로운 요청을 하기때문에 요청속성으로 저장되있는 객체도 리다이렉트가 발생하기전에 소멸된다.

예를들어 a.jsp에서 request에 속성값을 담아 b.jsp 로 리다이렉트 시키면... 속성값이 b페이지에

전달되지않는다.(새로운요청을 하므로...)


forward

의 경우는 포워드를 사용하여 다른자원(jsp나 servlet)으로 요청을 보내어 처리하여도

클라이언트에는 이 사실을 알리지않는다(웹 컨테이너 내에서 처리된다)

리다이렉트와 달리 새로운 요청을 위한 클라이언트와의 통신이 없어서 좀더 나은 성능을 보여준다.

그리고 포워딩시에는 포워딩후에도 요청 속성에 있는 객체들을 사용할수 있다.

그러나 포워드를 클라이언트에 알리지 않으므로(웹 컨테이너에서만 사용하니까)

URL의 변화가 없다..


____________________________________________________________________________________________________________________________


하나의 Request 를 여러 컴포넌트가 공유해야 할 경우,

(예를 들면, 하나의 Servlet 에서 Client 의 인증과 권한 부여를 처리하는 경우 등)

Request 의 공유를 구현하는 방법


1. Web Container 가 Forward 로 처리하는 방법

2. Web Container 가 Redirect 로 처리하는 방법


Redirect


브라우저가 응답을 받은 후 다시 보낼 새로운 URL 포함.

완전히 새로운 요청을 하기 때문에 Request Attribute 가 가지고 있는 객체는 Redirect 발생 시 소멸된다.

추가적으로 발생한 왕복 처리 때문에 Forward 보다 느리다.

URL 에 파라미터가 보이므로, 중요한 정보는 포함하지 않도록 유의해야 한다.


                                               (1) HTTP 요청

           Browser (Client)            ---------------->                Web Container (Server)

        ====================                                               =========================

                                                                                         (2) 요청 처리

                                                (3) 응답 Return

                                              <----------------

                                                (4) 새로운 요청

                                               ----------------->

                                                 (5) HTTP 응답

                                               <----------------


사용 예 : response.sendRedirect (request.encodeRedirectURL ("http://xxxxxx/result.jsp?...")



Forward


Forward 되는 해당 요청이 다른 Servlet 에 전달된다.

이 때, Client 는 다른 Servlet 에서 이 요청을 처리할 것을 모른다.

Client 와 통신 없이 서버에서만 처리되기 때문에, Redirect 보다 더 나은 성능 보인다.

Forward 한 후에도 객체를 사용할 수 있으므로, 객체가 가지고 있던 Request Attribute 를 사용할 수 있다.

URL 에는 변화가 없으므로 URL 과 Client Application 이 일치하지 않을 수 있다.

이는 Forward 가 발생한 사실을 Client 가 알지 못하기 때문이다. Refresh 를 할 경우, 정확하지 않은 요청이 발생할 수 있다.

Struts Framework 는 기본으로 Forward 를 사용한다.


                                               (1) HTTP 요청

           Browser (Client)            ---------------->                Web Container (Server)

        ====================                                               =========================

                                                                                         (2) 요청 처리

                                                                                         (3) Forward 수행

                                                (4) HTTP 응답

                                               <----------------


사용 예 : RequestDispatcher dispatcher =

                                         request.getRequestDispatcher("/result.jsp?....");

             dispatcher.forward(request, response);


(RequestDispatcher.forward() 는 Client 에 어떠한 결과도 전달하지 않았을 경우 사용 가능하다.)

Posted by 영 김해영

오늘은 forward와 include의 비교 입니다
forward와 include 모두 서블릿에서 흐름을 제어할때 쓰이는 메소드 입니다.
forward는 A에서 forward(B)를 한다면 forward를 호출하기전까지 전부 해석하다가 호출하는 시점에 B로 흐름이 넘어갑니다.
즉 아직까지 request는 한번 입니다
include는 A에서 include(B)를 한다면 A에서 include 호출하기전까지 모두 해석
-> B로 흐름 넘어가서 B 전부 해석하고 끝나면 -> 다시 A의 호출문장으로 넘어옵니다.
여기서도 request는 한번입니다.
근데 sendRedirect 라는 놈이 있는데요 이놈은 request가 두번이 일어납니다.
왜냐면  sendRedirect는 특정주소를 호출할 것을 요청하는 응답신호를 브라우저에게 보내기 때문입니다. 즉 A에서 sendeRedirect(B)를 한다면 A -> B -> A -> B
그래서 request 가 두번 발생하는것입니다.

Strat.html

<html>
   <head>
  <title> forward 및 include , sendRedirect 때려잡기! </title>
  </head>
  <body>
  <form action="Check.jsp" method="post">
    당신의 나이는?
   <input type="text" name="test"><br><br>

    <input type="radio" name="check" value="forward"> forward 방식
    <input type="radio" name="check" value="include">  include 방식
    <input type="submit" value="Confirm">
 </form>
 </body>
</html>


간단히 forward와 include중 어느 형식으로 보낼것인치 체크하고 나이를 넣게 했습니다.


Check.jsp

<%
String check = request.getParameter("check");
java.util.Date today = java.util.Calendar.getInstance().getTime();
%>

 이건 include 할때만 보여요~ ^^ 
<hr>
<% if (check.equals("forward"))
{%>
  <jsp:forward page="./Result.jsp" >
  <jsp:param name="check" value="<%=check%>"/>
  <jsp:param name="today" value="<%=today%>"/>
  </jsp:forward>

   <hr>
<%  } else  if  (check.equals("include")) { %>
<jsp:include page="./Result.jsp">
  <jsp:param name="check" value="<%=check%>"/>
  <jsp:param name="today" value="<%=today%>"/>
</jsp:include>

<%  }  %>


이것은 결과 페이지(result.jsp)에 가기전에 forward와 include의 차이점을 보여주는 페이지

일단 포워드는 호출하면 그 위까지만 해석하고 바로 흐름이 바뀌기 때문에 " 이건 include 할때만 보여요~ ^^"라는 문장은 해석은 하지만 뿌리지는 못합니다.

그래서 그럼 이넘이 과연 해석은 정말 하고 가는가 의문이 들어서 시간을 넘는 today를 만들어서 넘겼습니다.


Result.jsp

<br>
 이것은 <%= request.getParameter("check")  %>  방식입니다.
<hr>
나이 : <%=request.getParameter("test")%>
<hr>
현재 시간 : <%= request.getParameter("today")%>

 이건 결과 페이지입니다.



                                                    forward의 실행화면

사용자 삽입 이미지
forward의 결과화면
사용자 삽입 이미지

 
include의 실행화면
사용자 삽입 이미지

 
include의 결과화면

사용자 삽입 이미지
 
 
Posted by 영 김해영

특정 쓰레드의 스코프에서 사용할 로컬변수가 필요했던 적이 있는가? 이때에 각각의 쓰레드는 고유의 스토리지를 갖고 하나의 쓰레드는 다른 쓰레드의 상태 정보를 액세스하는 것이 불가능할 것이다. 표준 라이브러리는 이러한 요구를 가능케 하는 ThreadLocalInheritableThreadLocal, 2개의 클래스들을 제공하고 있다.

클래스들이 사용되고 있는 예를 보자.

  import java.util.Random;

  public class ThreadLocal1 {

    // Define/create thread local variable
    static ThreadLocal threadLocal = 
                                   new ThreadLocal();
    // Create class variable
    static volatile int counter = 0;
    // For random number generation
    static Random random = new Random();

    // Displays thread local variable, counter, 
    // and thread name
    private static void displayValues() {
      System.out.println (
        threadLocal.get() + "\t" + counter +
        "\t" + Thread.currentThread().getName());
    }

    public static void main (String args[]) {

      // Each thread increments counter
      // Displays variable info
      // And sleeps for the random amount of time
      // Before displaying info again
      Runnable runner = new Runnable() {
        public void run() {
          synchronized(ThreadLocal1.class) {
            counter++;
          }
          threadLocal.set(
             new Integer(random.nextInt(1000)));
          displayValues();
          try {
            Thread.currentThread().sleep (
              ((Integer)threadLocal.get()).intValue());
            displayValues();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      };

      // Increment counter, access thread local from 
      // a different thread, and display the values
      synchronized(ThreadLocal1.class) {
          counter++;
      }
      threadLocal.set(
         new Integer(random.nextInt(1000)));
      displayValues();

      // Here's where the other threads
      //  are actually created
      for (int i=0; i<5; i++) {
        Thread t = new Thread(runner);
        t.start();
      }
    }
  }

ThreadLocal1클래스는 2개의 정적 변수를 갖는다. common integer primitive 타입인 counterThreadLocal 타입의 변수인 threadLocal이 바로 그것이다. 일반적으로 클래스 변수를 사용하면, 해당 클래스의 모든 인스턴스들은 동일한 값을 공유하게 된다. 하지만 ThreadLocal의 경우는 다르다. ThreadLocal클래스를 사용하면 동일한 쓰레드내에서만 같은 정적 정보를 공유하게 되는 반면, 서로 다른 쓰레드에서 ThreadLocal 클래스를 사용하면 서로 다른 정적 정보를 갖게 된다.

ThreadLocal1에서, ThreadLocal 인스턴스는 0에서 999사이의 랜덤한 숫자로 초기화된다.

   threadLocal.set(
     new Integer(random.nextInt(1000)));

다수의 서로 다른 쓰레드가 생성되었다. 각각의 쓰레드 별로, 2개의 클래스 변수에 대한 상태 정보가 디스플레이된다. 그러면 이 쓰레드는 ThreadLocal이 랜덤한 숫자로 지정한 만큼의 밀리초동안 휴면한다. 이후, 2개의 클래스 변수의 상태 정보가 다시 디스플레이되고, ThreadLocal1를 실행하면, 아래와 같은 출력값을 보게 된다.

   828     1       main
   371     2       Thread-0
   744     3       Thread-1
   734     4       Thread-2
   189     5       Thread-3
   790     6       Thread-4
   189     6       Thread-3
   371     6       Thread-0
   734     6       Thread-2
   744     6       Thread-1
   790     6       Thread-4

여기에서 주목할 것은 세번째 컬럼에서 쓰레드의 이름이 같을 때를 살펴보면, 첫번째 컬럼의 ThreadLocal 변수값이 프로그램이 실행되는 동안 같은 상태를 유지한다는 것이다. 반면, 두번째 컬럼에서 보이는 정적 변수는 프로그램이 실행하는 동안 변화하고, 기존 쓰레드를 재검토할 때 그 바뀐 값을 유지한다. 이것이 바로 적절하게 동기화된 정적 변수가 해야 하는 일이다.

프로그램을 실행할 때마다 모든 컬럼에는 각기 다른 값이 생성되기 때문에 결과에서 보이는 첫번째 컬럼의 시간들이 위에서 보는 바와 다르다거나, 마지막 5개의 행의 이름의 순서가 다르다고 해도 놀랄 필요는 없다. 그 순서는 첫번째 컬럼에서 보여진 시간(times)에 기반한 것이다.

쓰레드 로컬 변수는 단순히 인스턴스 변수들의 또 다른 형태만이 아니다. 만약 특정 클래스의 10개의 인스턴스를 모두 일반적인 쓰래드내에서 접근한다면, 그것들의 ThreadLocal 정보는 모두 같을 것이다. 그러나, 만약 이러한 인스턴스들 중의 하나를 다른 쓰레드에서 접근하게 되면, ThreadLocal 정보는 바뀌게 될 것이다.

ThreadLocal 클래스는 오직 3개의 메소드만을 포함하고 있는데 get, setinitialValue가 바로 그것이다. ThreadLocal1 예제를 보면, set메소드는 인스턴스가 생성된 이후 호출되는 것을 알 수 있다. ThreadLocal의 상태를 이후에 다시 초기화하는 방법 대신에, ThreadLocal을 상속한 서브클래스에서 initialValue 메소드를 오버라이드할 수 있다. 다음의 ThreadLocal2 클래스가 바로 그 일을 하는데, 생성된 출력값은 ThreadLocal1의 경우와 비슷하다.

   import java.util.Random;

   public class ThreadLocal2 {

     // Create thread local class
     // Initial value is a random number from 0-999
     private static class MyThreadLocal
                                  extends ThreadLocal {
       Random random = new Random();
       protected Object initialValue() {
         return new Integer(random.nextInt(1000));
       }
     }

     // Define/create thread local variable
     static ThreadLocal threadLocal =
                           new MyThreadLocal();

     // Create class variable
     static volatile int counter = 0;

     // For random number generation
     static Random random = new Random();

     // Displays thread local variable, counter,
     // and thread name
     private static void displayValues() {
       System.out.println (
         threadLocal.get() + "\t" + counter +
         "\t" + Thread.currentThread().getName());
     }
    
     public static void main (String args[]) {

       // Each thread increments counter
       // Displays variable info
       // And sleeps for the random amount of time
       // Before displaying info again
       Runnable runner = new Runnable() {
         public void run() {
           synchronized(ThreadLocal2.class) {
             counter++;
           }
           displayValues();
           try {
             Thread.currentThread().sleep (
               ((Integer)threadLocal.get()).intValue());
             displayValues();
           } catch (InterruptedException e) {
             e.printStackTrace();
           }
         }
       };

       // Another instance of class created
       // and values displayed
       displayValues();

       // Here's where the other threads
       // are actually created
       for (int i=0; i<5; i++) {
         Thread t = new Thread(runner);
         t.start();
       }
     }
   } 

ThreadLocalinitialValue 메소드는 각각의 쓰레드가 상태를 알아내기 위해 하는 첫번째 호출에서 사용된다. Set이 이미 호출된 상태라면, initialValue 메소드는 절대로 호출되지 않는다. initialValue 메소드를 사용할 때, public이 아니라 protected로 만들어야 한다는 것을 기억하자. 일반적으로 initialValue 메소드가 임의로 호출되는 것은 바람직하지 않기 때문이다. initialValue 가 오버라이드되지 않았을 경우, 쓰레드 로컬 변수를 나타내는 객체의 초기 값은 null이다.

ThreadLocal변수에 담을 컨텐츠는 어떤 Object 타입이든지 가능하다. ThreadLocal2에서 컨텐츠는 Integer이다. get을 이용해서 상태를 받아낼 때 Object를 적절한 타입으로 캐스팅해야 한다는 것을 기억하자.

InheritableThreadLocal라고 불리는 ThreadLocal의 두번째 타입을 보자. ThreadLocal과 같이, InheritableThreadLocal을 사용하는 각각의 쓰레드는 고유의 상태객체를 갖는다. ThreadLocalInheritableThreadLocal의 차이점은 InheritableThreadLocal의 초기값은 생성하는 쓰레드로부터 변수의 상태를 얻어낸다는 데에 있다. 상태의 변경은 다른 쓰레드를 제외한 로컬 쓰레드에서만 가능하다. InheritableThreadLocal의 새로운 childValue 메소드는 그것의 상태를 초기화하기위해 사용되는데 이는 상태를 얻어내려고 할 때가 아니라 자녀객체의 생성자에 의해 호출된다.

실제로 ThreadLocal의 사용을 검토하기 위해서는, java.nio.charsetCharset 클래스를 보거나 java.util.LoggingLogRecord를 참고한다. Charset의 경우에는, ThreadLocal가 캐릭터 세트 인코더와 디코더를 검색하기 위해 사용된다. 이것은 초기화하는 코드들이 절절한 시기에 사용되도록 조절하는 역할을 한다. LogRecord에서 ThreadLocal의 사용은 각각의 쓰레드에 Login을 위한 고유한 id가 할당되는 것을 보장해 준다.

Posted by 영 김해영

Custom Tag Library (9) - 중첩 연관 태그 구현
중첩 연관 태그 구현

한글로 쓰다 보니 정말 말이 어렵게 보인다. 중첩 연관 태그… 그냥 이 문장만 봐서 "아하~ 그걸 말하는 구나"라고 생각하는 분이 계신다면 천재임에 틀림없다. 앞에서 조금 언급했기에 망정이지 안그랬으면 아마도 필자는 여러분들의 질타와 야유를 감수해야 했을 것이다.
이 부분에서 우리가 먼저 알아두어야 할 것은 두가지이다. 그 첫째는 현재 버전의 API에서는 자신의 부모 태그를 찾을 수 있는 메커니즘은 있지만, 자식 태그를 찾을 수 있는 방법은 없다.
부모 태그를 찾는 메쏘드가 바로 findAncestorWithClass라는 메쏘드이다. 이 메쏘드는 자기 자신과 찾으려는 부모 태그를 아규먼트로 넘겨 받아서 찾게 된다. API를 보면 다음과 같이 나와 있다.

public static final Tag findAncestorWithClass(Tag tag, java.lang.Class cl)

final 메쏘드이기 때문에 오버라이드 할 수 없으며, 아규먼트는 위에서 언급한 대로이다.
두번째 아규먼트는 Class 클래스이므로 부모 태그의 클래스의 Class 클래스를 넣어야 한다.

말이 어째 좀 이상한데 부모 태그.class와 같은 형식이다라고 생각하면 된다. 만약 부모 태그를 찾지 못하면 JspTagException 예외를 발생시킨다. 두번째로 알아야 할 것은 자식 태그는 부모 태그와 연관을 맺고 있기 때문에 부모 태그가 가지는 특정 값을 받아올 수가 있어야 한다. 따라서, 부모 태그는 자식 태그가 어떤 값에 접근할 수 있는 메쏘드를 제공해야 한다. 우리의 목표는 조 건처리를 위한 중첩 태그를 구현하는 것이다. 이러한 태그의 구조는 아래와 같을 것이다.


<prefix:if> <prefix:condition>true | false</prefix:condition> <prefix:then> if condition is true then process something </prefix:then> <prefix:else> if condition is false then process something </prefix:else> </prefix:if>

이 태그는 프로그래밍의 전형적인 조건문인 아래와 같은 형태와 일치하는 것이다.

if(condition){
… …
}else{
… …
}

차례대로 태그 핸들러를 구현해 보도록 하자.

소스 5.23 IfTag.java


package com.boolpae.jsp;

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;

public class IfTag extends TagSupport{
private boolean condition;
private boolean hasCondition = false;

public void setCondition(boolean condition){
this.condition = condition;
hasCondition = true;
}

public boolean getCondition(){
return condition;
}

public void setHasCondition(boolean hasCondition){
this.hasCondition = hasCondition;
}

public boolean getHasCondition(){
return hasCondition;
}

public int doStartTag(){
return EVAL_BODY_INCLUDE;
}
}

변수는 condition 과 hasCondition, 두가지이며 이 두가지 변수를 설정하고 접근하기 위한 메쏘드들이 나열되어 있다. condition은 진리 값의 true|false를 설정하는 것이며 hasCondition은 문법적인 검사를 위한 것이다. 만약 condition이 if 태그 내부에 없다면 항상 hasCondition은 false가 될 것이다.(초기값이 false이니까) 계속해서 나올 condition 태그에서 setHasCondition 메쏘드를 사용해 hasCondition 값을 true로 설정하게 된다.

소스 5.24 IfConditionTag.java

package com.boolpae.jsp;

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;

public class IfConditionTag extends BodyTagSupport{
public int doStarTag() throws JspException{
IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class);

if(parent == null){
throw new JspTagException("ConditionTag Error");
}
return EVAL_BODY_TAG;
}

public int doAfterBody(){
IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class);
BodyContent body = getBodyContent();
String bodyString = body.getString();
if("true".equals(bodyString.trim())){
parent.setCondition(true);
}else{
parent.setCondition(false);
}
return SKIP_BODY;
}
}

IfConditionTag 클래스는 먼저 doStartTag에서 findAncestorWithClass 메쏘드를 사용해서 부모 태그(IfTag)를 찾는다. 부모 태그가 없다면 예외를 던지고 실행을 중단한다. 부모 태그를 찾게되면 계속 몸체 수행을 하게 된다. 몸체의 값이 true이면 IfTag 클래스의 condtion값을 true로 설정하게 되고 아니면 false로 설정하게 된다. 여기서는 그냥 equals 메쏘드를 사용했기 때문에 대소문자를 구별한다. 대소문자 구별할 필요가 없다면 equalsIgnoreCase 메쏘드를 사용하라. 그러고 나면 이 태그에 대한 처리가 모두 끝나고 SKIP_BODY를 리턴하게 된다. 다음으로 IfThenTag 태그 핸들러 클래스를 보자.

소스 5.25 IfThenTag.java

package com.boolpae.jsp;

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;

public class IfThenTag extends BodyTagSupport{
public int doStartTag() throws JspTagException{
IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class);
if(parent == null){
throw new JspTagException("Error in IfThen Tag : parent Tag is null");
}else if(!parent.getHasCondition()){
throw new JspTagException("Error in IfThen Tag: IfTag's hasCondition value is false");
}
return EVAL_BODY_TAG;
}

public int doAfterBody(){
IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class);
if(parent.getCondition()){
try{
BodyContent body = getBodyContent();
JspWriter out = getPreviousOut();
out.print(body.getString());
}catch(IOException e){
System.out.println("Error in IfThen Tag : "+e);
}
}
return SKIP_BODY;
}
}

IfThen 태그 역시 doStartTag 메쏘드에서 부모 태그를 찾는다. 부모 태그(IfTag)가 없다면 역시 예외를 던진다. 또한 hasCondition이 false일 때도 예외를 던진다. 왜냐면 IfCondition 태그를 거치지 않았다는 의미이기 때문이다. Condition없는 조건문은 생각할 수 없다. hasCondition이 true라면 문법에 맞으므로 EVAL_BODY_TAG를 리턴하고 doAfterBody 메쏘드를 수행하게 된다. doAfterBody 메쏘드는 IfTag 의 condition이 true인지를 검사하고 true가 아니라면 예외를 던져버린다. IfThen 은 condition이 true일 때만 수행되는 태그이기 때문이다. true라면 몸체를 출력하는 일을 하게 된다.

소스 5.26 IfElseTag.java

package com.boolpae.jsp;

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;

public class IfElseTag extends BodyTagSupport{

public int doStartTag() throws JspTagException{
IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class);
if(parent == null){
throw new JspTagException("Error in IfElseTag : parent Tag is null");
}else if(parent.getHasCondition()){
throw new JspTagException("Error in IfElseTag : IfTag's hasCondition value is false");
}
return EVAL_BODY_TAG;
}

public int doAfterBody(){
IfTag parent = (IfTag)findAncestorWithClass(this, IfTag.class);
if(!parent.getCondition()){
try{
BodyContent body = getBodyContent();
JspWriter out = getPreviousOut();
out.print(body.getString());
}catch(IOException e){
System.out.println("Error in IfElseTag : "+e);
}
}
return SKIP_BODY;
}
}

IfElseTag 클래스도 IfThen 클래스와 다를 바 없다. doStartTag는 IfThen 클래스와 동일하게 문법상 적합한지 검사하고 문법상 문제가 없다면 doAfterBody 메쏘드를 수행시킨다. 그러면 condition의 값을 검사하고 false일 때만 몸체를 출력하게 된다.
이것으로 태그핸들러 클래스 작성은 끝났다. 이제 우리가 만들었던 4개의 태그 핸들러를 우리가 지금까지 쭈욱~ 해왔던 대로 TLD 파일에 추가하면 된다.

소스 5.27은 taglibExample.tld 에 추가하여야 할 부분이다.

소스 5.27 taglibExample.tld 추가

<tag>
<name>if</name>
<tagclass>com.boolpae.jsp.IfTag</tagclass>
<info>If Tag</info>
<bodycontent>JSP</bodycontent>
</tag>

<tag> <name>condition</name> <tagclass>com.boolpae.jsp.IfConditionTag</tagclass> <info>IfCondition Tag</info> <bodycontent>JSP</bodycontent> </tag> <tag> <name>then</name> <tagclass>com.boolpae.jsp.IfThenTag</tagclass> <info>IfThen Tag</info> <bodycontent>JSP</bodycontent> </tag> <tag> <name>else</name> <tagclass>com.boolpae.jsp.IfElseTag</tagclass> <info>IfElse Tag</info> <bodycontent>JSP</bodycontent> </tag>

TLD 파일에 대해서는 특별히 설명할 것이 없다. 여러분도 이미 익숙해 졌으리라 생각된다. 이제 JSP 페이지를 만들어서 실행해 보자. 우리가 만들어볼 JSP 페이지에는 이 태그를 두번 사용한다.

하나는 condition은 파라미터로 받고 condition의 상태를 출력하고, 다른 하나는 우리가 예전에 만들었던 loop 태그를 사용하여 랜덤하게 Math.random() 메쏘드를 사용하여 0.5 보다 크면 크다는 문장을, 작으면 작다는 문장을 출력할 것이다.

소스 5.28 IfTagExample.jsp

<HTML>
<HEAD>
<TITLE> 중첩 연관 태그 예제 </TITLE>
<style> BODY {font-size:15pt } </style>
</HEAD>
<BODY>
<center>
<h1> 중첩 연관 태그 Example </h1>

<%@ taglib uri="WEB-INF/tlds/taglibExample.tld" prefix="jspace" %> <jspace:if> <jspace:condition><%=request.getParameter("condition")%></jspace:condition> <jspace:then>Condition is true</jspace:then> <jspace:else>Condition is false</jspace:else> </jspace:if> <hr> <jspace:loop repeats='10'> <jspace:if> <jspace:condition><%=Math.random() > 0.5%></jspace:condition> <jspace:then>The number is greater than 0.5<br></jspace:then> <jspace:else>The number is smaller than 0.5<br></jspace:else> </jspace:if> </jspace:loop> </BODY> </HTML>

실행결과 화면은 아래와 같다.

기초강좌목록

이번 chapter도 이것으로 끝이 났다. JSP만의 기술적인 부분은 대부분 정리가 끝났다. 과연 우리는 지금까지 배운 것들을 가지고 얼마나 효율적으로 사용하여 틈만 나면 떠들어댔던 프리젠테이션과 비즈니스 로직의 분리라는…, 컴포넌트의 재사용이라는… , 지금까지 다른 스크립트 언어들이 할 수 없었던 일들을 할 수 있는 것일까?

여러분의 느낌은 어떤한지 모르겠다. "솔직히 말해서 배우기가 힘들다." "뭐 때문에 이렇게 하는 것일까?" 이런 대답을 한다면 필자가 강좌를 잘못 썼기 때문이다라고 자책할 수 밖에 없겠다. 사실 설계 단계부터 조금은 힘들고 구현에도 더 많은 양의 코드가 들어갈 것이다. 처음 개발할 때 일반적인 ASP나 PHP 등으로 코딩하는 것보다 시간도 많이 걸릴 것이다. 물론 JSP로도 그렇게 코딩하면 마찬가지이다. 어떤 식으로 할지는 프로젝트의 상황에 따라 달라질 것이다. 또한 코드가 조금 엉망이더라도 하드웨어가 좋으면 그런 오버헤드는 무마될 수 있다. 코드를 최적화하는데 들어가는 인력비보다 하드웨어에 투자하는 것이 비용절감 측면에서 더 나을지도 모른다. 하지만, 그 어플리케이션을 언젠가는 수정해야 한다면 뜯어 고치는데 들어가는 비용은 참으로 많을 것이다. 조금 힘들더라도 훗날을 생각해서 JSP의 철학이 담긴 코딩을 하도록 노력하자. 하드웨어가 아무리 좋아져도 언젠가는 소프트웨어를 뜯어 고쳐야 할 날이 올 테니까… 그럴 수 없는 상황이라면 페이지 중심으로 설계한다고 해도 조금은 신경을 써서 미래를 생각하는 버릇을 기르자.

요즘 JSP로 웹어플리케이션을 설계하는데 있어서 MVC 아키텍쳐 이야기를 여기저기서 말하고 있는데, 필자는 MVC 아키텍처 모델이 무엇인지 잘 모른다. 디자인 패턴에서 나온 이야기인 것 같은데 그 깊숙한 부분에 대해서도 잘 모르기 때문에 우리의 프로그램이 MVC 아키텍처를 따라 모델링 되어야 하고 구현되어야 한다고 말하지 않겠다.(MVC 모델은 SmallTalk에서 처음 도입되었다고 한다. 주로 GUI 어플리케이션에 많이 적용되는 디자인 패턴이다.)

그저 필자가 강조했던 JSP의 철학을 구현하려고 노력하다 보면 이미 그 프로그램은 MVC 모델의 관점에서도 잘 정의된 프로그램이 되어 있을 것이라고 말하고 싶다. 디자인 패턴에 대해서 관심이 있는 독자들에게는 책으로 Thinking in Patterns with Java(Bruce Eckel 저)을 추천한다. 아쉽게도 번역판은 없다. J2EE 플랫폼에서의 MVC 모델에 관련해서는 Sun의 웹사이트
http://java.sun.com/j2ee/blueprints/design_patterns/mvc/index.html을 참고하면 되겠다. EJB 중심으로 설명되어 있지만 개념적인 부분은 비슷하다.

이랬거나 저랬거나 우리는 앞으로 2개의 chapter를 더 할애해서 이론적인 부분을 더 살펴봐야 할 것 같다. 하나는 세션과 쿠키에 관련된 이야기이며 다른 하나는 데이터베이스와의 연동을 위한 JDBC가 될 것이다.

 
Posted by 영 김해영
◀ PREV : [1] : [2] : [3] : [4] : [5] : ... [16] : NEXT ▶

BLOG main image
by 김해영

공지사항

카테고리

young`s log (154)
서비스이야기 (0)
웹이야기 (0)
개발이야기 (152)
내생활이야기 (0)
경제이야지 (2)

최근에 달린 댓글

최근에 받은 트랙백

Total : 37,703
Today : 1 Yesterday : 11