'dev'에 해당되는 글 20건

  1. 2008.09.12 locatin 속성
  2. 2008.08.20 EUC-kr, Unicode, UTF-8 1
  3. 2008.08.05 ejb
  4. 2008.08.04 jms
  5. 2008.08.01 expression pattern
  6. 2008.08.01 script class
  7. 2008.08.01 정규화
  8. 2008.08.01 jndi
  9. 2008.07.25 ant
  10. 2008.07.25 정규식

locatin 속성

dev 2008. 9. 12. 16:50
흔히 location.href 와 location.replace() 모두 페이지를 이동시킬 때 사용한다.
하지만 둘 사이에는 분명한 차이점이 있으며, 이를 알고 적절히 사용하는 것이 좋겠다.


개념의 차이

href 는 객체의 상태를 나타내는 프로퍼티이며, replace()는 객체의 동작을 명령하는 메소드이다.

location.href = url;
location.replace(url);

 
기능의 차이

쉽게 간과할 수 있는 부분이지만, 프로퍼티와 메소드라는 차이 외에도 아래와 같은 차이점이 있다.

location.href : 페이지를 이동 시킨다.
location.replace : 현재 페이지를 바꿔준다.
 

무슨말인가 싶겠지만 매우 중요한 차이점이다.
location.href는 페이지를 이동 시키면서 히스토리를 남긴다.
따라서, A페이지 -> B페이지 이동 후 뒤로가기를 통해 A페이지로 돌아갈 수가 있다.

반면, location.replace()는 히스토리를 남기지 않는다.
따라서, location.href와 달리 A페이지 -> B페이지 이동 후 뒤로가기를 통해 A페이지로 돌아갈 수 없다.

또한, location.replace()는 캐시(인터넷 임시파일)를 쓰지 않는 점이 location.href와 다르다.

'dev' 카테고리의 다른 글

EUC-kr, Unicode, UTF-8  (1) 2008.08.20
ejb  (0) 2008.08.05
jms  (0) 2008.08.04
expression pattern  (0) 2008.08.01
script class  (0) 2008.08.01
Posted by 으랏차
,

EUC-kr, Unicode, UTF-8

dev 2008. 8. 20. 17:59
euc-kr
  • ascii 코드 + 한글
  • 영문/숫자/기호는 1바이트. 한글과 한자는 2바이트
  • euc-kr 또는 ksc5601
  • 웹페이지 작성에 사용가능
  • 특수한 외국어 문자나 일본식/중국식 한자는 표현불가
unicode
  • 모든 글자를 2바이트로 표현
  • 전세계 모든 글자들을 한꺼번에 표현가능
  • 웹페이지 작성에 사용불가
utf-8
  • 영문/숫자/기호는 1바이트로, 한글과 한자 등은 3바이트로 표현
  • 전세계 모든 글자들을 한꺼번에 표현가능
  • 웹페이지 작성에 사용가능

#1. Ascii Code Table  www.ascii-code.com


#2. KSC5601(EUC-KR) 한글 완성형 코드표       http://blog.naver.com/bestdev/10014915998

#3. UTF-8 코드중 한글 영역     http://www.utf8-chartable.de/unicode-utf8-table.pl?start=44032

U+0000 ... U+007F Basic Latin
U+0080 ... U+00FF Latin-1 Supplement
U+0100 ... U+017F Latin Extended-A
U+0180 ... U+024F Latin Extended-B
U+0250 ... U+02AF IPA Extensions
U+02B0 ... U+02FF Spacing Modifier Letters
U+0300 ... U+036F Combining Diacritical Marks
U+0370 ... U+03FF Greek and Coptic
U+0400 ... U+04FF Cyrillic
U+0500 ... U+052F Cyrillic Supplement
U+0530 ... U+058F Armenian
U+0590 ... U+05FF Hebrew
U+0600 ... U+06FF Arabic
U+0700 ... U+074F Syriac
U+0750 ... U+077F Arabic Supplement
U+0780 ... U+07BF Thaana
U+07C0 ... U+07FF NKo
U+0900 ... U+097F Devanagari
U+0980 ... U+09FF Bengali
U+0A00 ... U+0A7F Gurmukhi
U+0A80 ... U+0AFF Gujarati
U+0B00 ... U+0B7F Oriya
U+0B80 ... U+0BFF Tamil
U+0C00 ... U+0C7F Telugu
U+0C80 ... U+0CFF Kannada
U+0D00 ... U+0D7F Malayalam
U+0D80 ... U+0DFF Sinhala
U+0E00 ... U+0E7F Thai
U+0E80 ... U+0EFF Lao
U+0F00 ... U+0FFF Tibetan
U+1000 ... U+109F Myanmar
U+10A0 ... U+10FF Georgian
U+1100 ... U+11FF Hangul Jamo
U+1200 ... U+137F Ethiopic
U+1380 ... U+139F Ethiopic Supplement
U+13A0 ... U+13FF Cherokee
U+1400 ... U+167F Unified Canadian Aboriginal Syllabics
U+1680 ... U+169F Ogham
U+16A0 ... U+16FF Runic
U+1700 ... U+171F Tagalog
U+1720 ... U+173F Hanunoo
U+1740 ... U+175F Buhid
U+1760 ... U+177F Tagbanwa
U+1780 ... U+17FF Khmer
U+1800 ... U+18AF Mongolian
U+1900 ... U+194F Limbu
U+1950 ... U+197F Tai Le
U+1980 ... U+19DF New Tai Lue
U+19E0 ... U+19FF Khmer Symbols
U+1A00 ... U+1A1F Buginese
U+1B00 ... U+1B7F Balinese
U+1B80 ... U+1BBF Sundanese
U+1C00 ... U+1C4F Lepcha
U+1C50 ... U+1C7F Ol Chiki
U+1D00 ... U+1D7F Phonetic Extensions
U+1D80 ... U+1DBF Phonetic Extensions Supplement
U+1DC0 ... U+1DFF Combining Diacritical Marks Supplement
U+1E00 ... U+1EFF Latin Extended Additional
U+1F00 ... U+1FFF Greek Extended
U+2000 ... U+206F General Punctuation
U+2070 ... U+209F Superscripts and Subscripts
U+20A0 ... U+20CF Currency Symbols
U+20D0 ... U+20FF Combining Diacritical Marks for Symbols
U+2100 ... U+214F Letterlike Symbols
U+2150 ... U+218F Number Forms
U+2190 ... U+21FF Arrows
U+2200 ... U+22FF Mathematical Operators
U+2300 ... U+23FF Miscellaneous Technical
U+2400 ... U+243F Control Pictures
U+2440 ... U+245F Optical Character Recognition
U+2460 ... U+24FF Enclosed Alphanumerics
U+2500 ... U+257F Box Drawing
U+2580 ... U+259F Block Elements
U+25A0 ... U+25FF Geometric Shapes
U+2600 ... U+26FF Miscellaneous Symbols
U+2700 ... U+27BF Dingbats
U+27C0 ... U+27EF Miscellaneous Mathematical Symbols-A
U+27F0 ... U+27FF Supplemental Arrows-A
U+2800 ... U+28FF Braille Patterns
U+2900 ... U+297F Supplemental Arrows-B
U+2980 ... U+29FF Miscellaneous Mathematical Symbols-B
U+2A00 ... U+2AFF Supplemental Mathematical Operators
U+2B00 ... U+2BFF Miscellaneous Symbols and Arrows
U+2C00 ... U+2C5F Glagolitic
U+2C60 ... U+2C7F Latin Extended-C
U+2C80 ... U+2CFF Coptic
U+2D00 ... U+2D2F Georgian Supplement
U+2D30 ... U+2D7F Tifinagh
U+2D80 ... U+2DDF Ethiopic Extended
U+2DE0 ... U+2DFF Cyrillic Extended-A
U+2E00 ... U+2E7F Supplemental Punctuation
U+2E80 ... U+2EFF CJK Radicals Supplement
U+2F00 ... U+2FDF Kangxi Radicals
U+2FF0 ... U+2FFF Ideographic Description Characters
U+3000 ... U+303F CJK Symbols and Punctuation
U+3040 ... U+309F Hiragana
U+30A0 ... U+30FF Katakana
U+3100 ... U+312F Bopomofo
U+3130 ... U+318F Hangul Compatibility Jamo
U+3190 ... U+319F Kanbun
U+31A0 ... U+31BF Bopomofo Extended
U+31C0 ... U+31EF CJK Strokes
U+31F0 ... U+31FF Katakana Phonetic Extensions
U+3200 ... U+32FF Enclosed CJK Letters and Months
U+3300 ... U+33FF CJK Compatibility
U+3400 ... U+4DBF CJK Unified Ideographs Extension A
U+4DC0 ... U+4DFF Yijing Hexagram Symbols
U+4E00 ... U+9FFF CJK Unified Ideographs
U+A000 ... U+A48F Yi Syllables
U+A490 ... U+A4CF Yi Radicals
U+A500 ... U+A63F Vai
U+A640 ... U+A69F Cyrillic Extended-B
U+A700 ... U+A71F Modifier Tone Letters
U+A720 ... U+A7FF Latin Extended-D
U+A800 ... U+A82F Syloti Nagri
U+A840 ... U+A87F Phags-pa
U+A880 ... U+A8DF Saurashtra
U+A900 ... U+A92F Kayah Li
U+A930 ... U+A95F Rejang
U+AA00 ... U+AA5F Cham
U+AC00 ... U+D7AF Hangul Syllables
U+D800 ... U+DB7F High Surrogates
U+DB80 ... U+DBFF High Private Use Surrogates
U+DC00 ... U+DFFF Low Surrogates
U+E000 ... U+F8FF Private Use Area
U+F900 ... U+FAFF CJK Compatibility Ideographs
U+FB00 ... U+FB4F Alphabetic Presentation Forms
U+FB50 ... U+FDFF Arabic Presentation Forms-A
U+FE00 ... U+FE0F Variation Selectors
U+FE10 ... U+FE1F Vertical Forms
U+FE20 ... U+FE2F Combining Half Marks
U+FE30 ... U+FE4F CJK Compatibility Forms
U+FE50 ... U+FE6F Small Form Variants
U+FE70 ... U+FEFF Arabic Presentation Forms-B
U+FF00 ... U+FFEF Halfwidth and Fullwidth Forms
U+FFF0 ... U+FFFF Specials
U+10000 ... U+1007F Linear B Syllabary
U+10080 ... U+100FF Linear B Ideograms
U+10100 ... U+1013F Aegean Numbers
U+10140 ... U+1018F Ancient Greek Numbers
U+10190 ... U+101CF Ancient Symbols
U+101D0 ... U+101FF Phaistos Disc
U+10280 ... U+1029F Lycian
U+102A0 ... U+102DF Carian
U+10300 ... U+1032F Old Italic
U+10330 ... U+1034F Gothic
U+10380 ... U+1039F Ugaritic
U+103A0 ... U+103DF Old Persian
U+10400 ... U+1044F Deseret
U+10450 ... U+1047F Shavian
U+10480 ... U+104AF Osmanya
U+10800 ... U+1083F Cypriot Syllabary
U+10900 ... U+1091F Phoenician
U+10920 ... U+1093F Lydian
U+10A00 ... U+10A5F Kharoshthi
U+12000 ... U+123FF Cuneiform
U+12400 ... U+1247F Cuneiform Numbers and Punctuation
U+1D000 ... U+1D0FF Byzantine Musical Symbols
U+1D100 ... U+1D1FF Musical Symbols
U+1D200 ... U+1D24F Ancient Greek Musical Notation
U+1D300 ... U+1D35F Tai Xuan Jing Symbols
U+1D360 ... U+1D37F Counting Rod Numerals
U+1D400 ... U+1D7FF Mathematical Alphanumeric Symbols
U+1F000 ... U+1F02F Mahjong Tiles
U+1F030 ... U+1F09F Domino Tiles
U+20000 ... U+2A6DF CJK Unified Ideographs Extension B
U+2F800 ... U+2FA1F CJK Compatibility Ideographs Supplement
U+E0000 ... U+E007F Tags
U+E0100 ... U+E01EF Variation Selectors Supplement
U+F0000 ... U+FFFFF Supplementary Private Use Area-A
U+100000 ... U+10FFFF Supplementary Private Use Area-B

'dev' 카테고리의 다른 글

locatin 속성  (0) 2008.09.12
ejb  (0) 2008.08.05
jms  (0) 2008.08.04
expression pattern  (0) 2008.08.01
script class  (0) 2008.08.01
Posted by 으랏차
,

ejb

dev 2008. 8. 5. 17:03

출처 : 월간 마이크로 소프트웨어


EJB 아키텍처를 기반으로 웹애플리케이션을 구축하는 프로젝트들이 2000년 이후부터 증가해왔음은 모두가 아는 사실. 더불어 EJB 관련 서적들도 다양하게 출판됐고, EJB를 사용하는 개발자들도 늘어나고 있다.

그러나 실제 EJB를 사용하면서 개발자들이 저지르기 쉬운 실수, 함정 등은 아직 서적에서는 친절히 안내되어 있지 않은 듯 하다. 이번 강좌의 목적은 흔히 개발자들이 스스로 잘 안다고 생각하는 것 때문에 빠질 수 있는 함정들을 미리 짚어봄으로써 그 함정이 초래할 혼란을 최소화하는데 있다.

강좌 중간중간 퍼포먼스를 고려한 팁이 언급될 수도 있고, 클래스 설계와 관련된 첨언도 들어갈 수 있다. 이 강좌의 목적이나 성격이 하나의 개념에 대한 순차적인 설명 형식은 아니므로 기본개념 설명을 건너뛰거나, 동시에 초보 자바 개발자도 알고 있는 사항에 대해 새삼 지적하는 경우도 있을 것이다.

자신에게 닥칠 수도 있는 문제를 미리 파악해 보고, 어떠한 방법으로 예방하고 해결해야하는지 살펴보도록 하자. 이번 강좌에서 언급될 예시에서 쓰는 EJB의 스펙은 1.1버전이고, DBMS는 오라클 8i이다.

DB와의 연결은 누가, 언제, 어떻게 할까?
EJB 애플리케이션이 DB와의 연결자원을 이용하는 방법은 2가지다.

첫째, ConnectionPool 을 이용하는 방법이 있다. EJB 서버가 형성하게 되는 ‘ConnectionPool’이라는 데이터 소스를 이용하는 방법이다. 임의의 비즈니스 프로세스를 담당하는 세션빈의 메소드 내에서 DB의 레코드를 써야할 경우, ConnectionPool에서 java.sql.Connetion 객체를 받아 쓰는 경우이다.

웹로직(weblogic), 웹스피어(websphere), JBOSS 등 다양한 업체들의 EJB 서버마다 기동시 ConnetionPool을 생성시킬 수 있는 기능을 제공한다. 기동시 읽어들이는 프로퍼티 파일에 DB의 URL, SID, user name, 패스워드, ConnectionPool의 name까지 사용자가 지정해야 한다.

아래는 웹로직 서버(버전 5.1)의 weblogic.properties 파일 내의 ConnetionPool 설정정보 부분이다.


# testPool
weblogic.JBDC.connectionPool.testPool=\
url=JBDC:oracle:thin:@123.4.5.67:portNumber:sid명,\
driver=oracle.JBDC.driver.OracleDriver,\
initialCapacity=1,\
maxCapacity=10,\
CapacityIncrement=2,\
allowShrinking=true,\
shrinkPeriodMins=15,\
props=user=user명;password=패스워드


이렇게 EJB 서버 기동시 미리 ConnectionPool을 만들게 되면 애플리케이션 운영중 발생하게 되는 DBMS 와의 빈번한 요청이 있을 때마다 JBDC 드라이버로 DB와 일일이 연결할 필요없이, 만들어진 ConnetionPool에서 Connetion을 꺼내 쓰고 다시 반환하므로 시스템 퍼포먼스 측면에서 월등히 효율적이다.

둘째, 엔터티빈을 활용하는 방법이다. 엔터티빈 중에서도 컨테이너가 퍼시스턴스를 관리해주는 CMP 엔터티빈을 EJB 서버에 배치해 쓰면 개발자 입장에서는 많은 부담을 덜 수 있기 때문에 대부분의 프로젝트에서 BMP 엔터티빈 보다는 CMP 엔터티빈을 사용한다.

엔터티빈 객체 하나는 DB의 하나의 테이블을 대표하며, 엔터티빈 객체가 갖고 있는 필드들은 해당 테이블의 컬럼과 매핑된다. 엔터티빈의 인스턴스는 DB의 임의의 레코드를 객체화한 것이다. 그러므로 엔터티빈의 인스턴스를 호출하여, get, create, set, remove 등의 메소드를 쓴다면, 직접 SQL문을 DB로 날리지 않아도 동일한 결과를 얻을 수 있다. insert 문 대신 create 메소드를 쓰면 되므로 코딩이 간결해진다. 더불어 SQL문에 익숙하지 않은 개발자라도 쉽게 쓸 수 있다는 장점이 있다.

그러나 엔터티빈의 이러한, 소위 장점이라고 하는 것들에 섣불리 현혹되지 않는 것이 좋다. EJB 프로젝트에서 발생하는 문제의 원인은 대부분 엔터티빈에 있다.

DB 핸들링 주체는 세션빈
DB에 어떻게 접근하든 비즈니스 프로세스를 담당하는 주체는 세션빈이다. 그리고 분석된 업무 흐름을 애플리케이션으로 구현할 때의 최소단위는 세션빈의 메소드이다.

예를 들어 급여계산 프로그램이 있다면 세션빈 내의 calcuratorPay(String empNo, String yyyyMm)라는 메소드가 있을 것이고, 복리후생비 신청 프로그램은 submitWelfare(String empNo, String date, ...)라는 메소드를 콜한다.

개발자는 세션빈의 메소드 안에서 적절히 저 두가지 방법을 활용해 DB의 레코드를 핸들링하게 된다. 엔터티빈 인스턴스가 DB의 레코드를 표현한다고 해도, 엄청나게 다양한 조건 및 함수 등이 담겨있는 select 문의 효과를 엔터티빈 인스턴스를 사용해서 자바 코딩으로 얻을 수 있겠는가? 없다.

따라서 그러한 다양한 조회를 할 필요가 있는 경우에는 ConnectionPool 에서 connection 객체를 하나 얻어와서 쓰게 된다. 뿐만 아니라 update, delete, insert 등에서도 pk 값으로만 조회해서 그 값을 변경, 삭제, 추가하는 경우보다 추가적인 제약이 따르는 경우가 많다.

결국 전체 시스템의 모든 세션빈이 connetion 객체를 쓰는 경우가 훨씬 많기 때문에, 모든 세션빈이 콜 할 수 있는 공통 모듈로 connectionPool 에서 connection 객체를 하나 뽑아 리턴해주는 클래스의 개발 필요사항이 발생한다. 이 클래스에는 getConnetion() 및 closeConnection() 메소드가 위치하게 될 것이다.

아래 예시에서 등장하게 되는 ConnManager라는 클래스는 이러한 역할을 하는 클래스다.

예시 소스코드를 보자. 사번을 통해 사원명을 조회하는 매우 간단한 메소드이다.


public 클래스 EmpManagerEJB implements javax.ejb.세션빈{

    public EmpManagerEJB()
    {
    }

    public String getEmpName(String empNo) throws Exception
    {
      Connection con = null;
      String eName = null;

      try {
         con = ConnManager.getConnection();
         EmpDB DB = new EmpDB();
         eName = DB.getEmpName(con,empNo);

      } catch(Exception e) {
         throw new Exception(e.toString());
      } finally {
         try {
            ConnManager.closeConnection(con);
         } catch(Exception e) {
            throw new Exception(e);
         }
      }
      return eName;
    }

    // ---------------------------------------------------------------
    // 세션빈 interface implementation
    public void ejbActivate()
    {
      // to do: code goes here.

    }
.... 이하 생략,

}

위의 예시에서 보듯이 세션빈 메소드 내에서는 Connection 객체를 가지고 와서 마지막에 닫고, 자신을 호출한 클라이언트에게 사원명을 전달하는 일만 하고 있다. 직접적으로 DB에 갖다오는 일은 EmpDB라는 클래스에게 맡긴다. 다양한 업무를 반영하는 세션빈 메소드 내에서는 이와 같은 패턴이 보다 효과적이고 간결하다.

그렇다면 EmpDB라는 클래스의 getEmpName 메소드를 살펴보자.


    public String getEmpName(Connetion con , String empNo)
    throws Exception {

      String eName = null;
      PreparedStatement pstmt = null;
      ResultSet rs = null;

      /*
         SELECT
            EMP_NAME
         FROM EMP
         WHERE EMP_NO = '000001'
      */

      String sql =
         "SELECT " +
         " EMP_NAME " +
         "FROM EMP " +
         "WHERE EMP_NO = ? ";

      try {
         pstmt = con.prepareStatement(sql);
         pstmt.setString(1, empNo);
         rs = pstmt.executeQuery();

         if(rs.next()) {

            eName = rs.getString("EMP_NAME"));

         }
      } finally {
         ConnManager.closeResultSet(rs);
         ConnManager.closeStatement(pstmt);
      }
      return eName;
    }

사번 테이블에서 사원 번호로 사원명을 리턴하는 메소드이다. 이 메소드 중에서 가장 주의깊게 봐야 할 부분은 PreparedStatement를 Statement interface 대신 사용한 부분이다. 조회성 쿼리를 DB에 던지고자 할 경우 아무 생각없이 Statement interface를 사용하는 개발자가 많은데, 이렇게 만들어서는 안된다.
① 세션빈에서의 DB 접근전략 및 엔터티빈 사용시 주의사항
② 세션빈에서의 트랜잭션 관리
③ 비즈니스 프로세스 구현 최적화하기
④ 능률 높여주는 유틸리티를 사용하자

EJB 아키텍처를 기반으로 웹애플리케이션을 구축하는 프로젝트들이 2000년 이후부터 증가해왔음은 모두가 아는 사실. 더불어 EJB 관련 서적들도 다양하게 출판됐고, EJB를 사용하는 개발자들도 늘어나고 있다.

그러나 실제 EJB를 사용하면서 개발자들이 저지르기 쉬운 실수, 함정 등은 아직 서적에서는 친절히 안내되어 있지 않은 듯 하다. 이번 강좌의 목적은 흔히 개발자들이 스스로 잘 안다고 생각하는 것 때문에 빠질 수 있는 함정들을 미리 짚어봄으로써 그 함정이 초래할 혼란을 최소화하는데 있다.

강좌 중간중간 퍼포먼스를 고려한 팁이 언급될 수도 있고, 클래스 설계와 관련된 첨언도 들어갈 수 있다. 이 강좌의 목적이나 성격이 하나의 개념에 대한 순차적인 설명 형식은 아니므로 기본개념 설명을 건너뛰거나, 동시에 초보 자바 개발자도 알고 있는 사항에 대해 새삼 지적하는 경우도 있을 것이다.

자신에게 닥칠 수도 있는 문제를 미리 파악해 보고, 어떠한 방법으로 예방하고 해결해야하는지 살펴보도록 하자. 이번 강좌에서 언급될 예시에서 쓰는 EJB의 스펙은 1.1버전이고, DBMS는 오라클 8i이다.

DB와의 연결은 누가, 언제, 어떻게 할까?
EJB 애플리케이션이 DB와의 연결자원을 이용하는 방법은 2가지다.

첫째, ConnectionPool 을 이용하는 방법이 있다. EJB 서버가 형성하게 되는 ‘ConnectionPool’이라는 데이터 소스를 이용하는 방법이다. 임의의 비즈니스 프로세스를 담당하는 세션빈의 메소드 내에서 DB의 레코드를 써야할 경우, ConnectionPool에서 java.sql.Connetion 객체를 받아 쓰는 경우이다.

웹로직(weblogic), 웹스피어(websphere), JBOSS 등 다양한 업체들의 EJB 서버마다 기동시 ConnetionPool을 생성시킬 수 있는 기능을 제공한다. 기동시 읽어들이는 프로퍼티 파일에 DB의 URL, SID, user name, 패스워드, ConnectionPool의 name까지 사용자가 지정해야 한다.

아래는 웹로직 서버(버전 5.1)의 weblogic.properties 파일 내의 ConnetionPool 설정정보 부분이다.


# testPool
weblogic.JBDC.connectionPool.testPool=\
url=JBDC:oracle:thin:@123.4.5.67:portNumber:sid명,\
driver=oracle.JBDC.driver.OracleDriver,\
initialCapacity=1,\
maxCapacity=10,\
CapacityIncrement=2,\
allowShrinking=true,\
shrinkPeriodMins=15,\
props=user=user명;password=패스워드


이렇게 EJB 서버 기동시 미리 ConnectionPool을 만들게 되면 애플리케이션 운영중 발생하게 되는 DBMS 와의 빈번한 요청이 있을 때마다 JBDC 드라이버로 DB와 일일이 연결할 필요없이, 만들어진 ConnetionPool에서 Connetion을 꺼내 쓰고 다시 반환하므로 시스템 퍼포먼스 측면에서 월등히 효율적이다.

둘째, 엔터티빈을 활용하는 방법이다. 엔터티빈 중에서도 컨테이너가 퍼시스턴스를 관리해주는 CMP 엔터티빈을 EJB 서버에 배치해 쓰면 개발자 입장에서는 많은 부담을 덜 수 있기 때문에 대부분의 프로젝트에서 BMP 엔터티빈 보다는 CMP 엔터티빈을 사용한다.

엔터티빈 객체 하나는 DB의 하나의 테이블을 대표하며, 엔터티빈 객체가 갖고 있는 필드들은 해당 테이블의 컬럼과 매핑된다. 엔터티빈의 인스턴스는 DB의 임의의 레코드를 객체화한 것이다. 그러므로 엔터티빈의 인스턴스를 호출하여, get, create, set, remove 등의 메소드를 쓴다면, 직접 SQL문을 DB로 날리지 않아도 동일한 결과를 얻을 수 있다. insert 문 대신 create 메소드를 쓰면 되므로 코딩이 간결해진다. 더불어 SQL문에 익숙하지 않은 개발자라도 쉽게 쓸 수 있다는 장점이 있다.

그러나 엔터티빈의 이러한, 소위 장점이라고 하는 것들에 섣불리 현혹되지 않는 것이 좋다. EJB 프로젝트에서 발생하는 문제의 원인은 대부분 엔터티빈에 있다.

DB 핸들링 주체는 세션빈
DB에 어떻게 접근하든 비즈니스 프로세스를 담당하는 주체는 세션빈이다. 그리고 분석된 업무 흐름을 애플리케이션으로 구현할 때의 최소단위는 세션빈의 메소드이다.

예를 들어 급여계산 프로그램이 있다면 세션빈 내의 calcuratorPay(String empNo, String yyyyMm)라는 메소드가 있을 것이고, 복리후생비 신청 프로그램은 submitWelfare(String empNo, String date, ...)라는 메소드를 콜한다.

개발자는 세션빈의 메소드 안에서 적절히 저 두가지 방법을 활용해 DB의 레코드를 핸들링하게 된다. 엔터티빈 인스턴스가 DB의 레코드를 표현한다고 해도, 엄청나게 다양한 조건 및 함수 등이 담겨있는 select 문의 효과를 엔터티빈 인스턴스를 사용해서 자바 코딩으로 얻을 수 있겠는가? 없다.

따라서 그러한 다양한 조회를 할 필요가 있는 경우에는 ConnectionPool 에서 connection 객체를 하나 얻어와서 쓰게 된다. 뿐만 아니라 update, delete, insert 등에서도 pk 값으로만 조회해서 그 값을 변경, 삭제, 추가하는 경우보다 추가적인 제약이 따르는 경우가 많다.

결국 전체 시스템의 모든 세션빈이 connetion 객체를 쓰는 경우가 훨씬 많기 때문에, 모든 세션빈이 콜 할 수 있는 공통 모듈로 connectionPool 에서 connection 객체를 하나 뽑아 리턴해주는 클래스의 개발 필요사항이 발생한다. 이 클래스에는 getConnetion() 및 closeConnection() 메소드가 위치하게 될 것이다.

아래 예시에서 등장하게 되는 ConnManager라는 클래스는 이러한 역할을 하는 클래스다.

예시 소스코드를 보자. 사번을 통해 사원명을 조회하는 매우 간단한 메소드이다.


public 클래스 EmpManagerEJB implements javax.ejb.세션빈{

    public EmpManagerEJB()
    {
    }

    public String getEmpName(String empNo) throws Exception
    {
      Connection con = null;
      String eName = null;

      try {
         con = ConnManager.getConnection();
         EmpDB DB = new EmpDB();
         eName = DB.getEmpName(con,empNo);

      } catch(Exception e) {
         throw new Exception(e.toString());
      } finally {
         try {
            ConnManager.closeConnection(con);
         } catch(Exception e) {
            throw new Exception(e);
         }
      }
      return eName;
    }

    // ---------------------------------------------------------------
    // 세션빈 interface implementation
    public void ejbActivate()
    {
      // to do: code goes here.

    }
.... 이하 생략,

}

위의 예시에서 보듯이 세션빈 메소드 내에서는 Connection 객체를 가지고 와서 마지막에 닫고, 자신을 호출한 클라이언트에게 사원명을 전달하는 일만 하고 있다. 직접적으로 DB에 갖다오는 일은 EmpDB라는 클래스에게 맡긴다. 다양한 업무를 반영하는 세션빈 메소드 내에서는 이와 같은 패턴이 보다 효과적이고 간결하다.

그렇다면 EmpDB라는 클래스의 getEmpName 메소드를 살펴보자.


    public String getEmpName(Connetion con , String empNo)
    throws Exception {

      String eName = null;
      PreparedStatement pstmt = null;
      ResultSet rs = null;

      /*
         SELECT
            EMP_NAME
         FROM EMP
         WHERE EMP_NO = '000001'
      */

      String sql =
         "SELECT " +
         " EMP_NAME " +
         "FROM EMP " +
         "WHERE EMP_NO = ? ";

      try {
         pstmt = con.prepareStatement(sql);
         pstmt.setString(1, empNo);
         rs = pstmt.executeQuery();

         if(rs.next()) {

            eName = rs.getString("EMP_NAME"));

         }
      } finally {
         ConnManager.closeResultSet(rs);
         ConnManager.closeStatement(pstmt);
      }
      return eName;
    }

사번 테이블에서 사원 번호로 사원명을 리턴하는 메소드이다. 이 메소드 중에서 가장 주의깊게 봐야 할 부분은 PreparedStatement를 Statement interface 대신 사용한 부분이다. 조회성 쿼리를 DB에 던지고자 할 경우 아무 생각없이 Statement interface를 사용하는 개발자가 많은데, 이렇게 만들어서는 안된다.


꼭 PreparedStatement를 사용하자
Statement 인터페이스 보다는 PreparedStatement가 낫다. 사실 ‘좋다’ 정도가 아니라 ‘Statement를 쓰지 말고 PreparedStatement를 써야한다’고 말해도 될 정도다.

getEmpName의 메소드에서 PreparedStatement 대신 Statement의 executeQuery(String sql) 메소드를 사용해서 개발하면 어떤 일이 생기는지 알아보자.

인사업무 담당자가 사원 한 명씩 상세정보를 조회하고자 한다고 가정하자. 조회버튼을 누르면 사원번호로 getEmpName 메소드안에서 쿼리가 스트링으로 완성돼 Statement의 executeQuery(String sql) 메소드안의 SQL 값으로 들어간다. DB의 옵티마이저가 쿼리를 파싱, 실행하고, resultSet 리턴한다.

여기서 다음 사원을 조회하면? 사원번호만 다른, 그러나 방금전 조회시의 쿼리와 모두 같은 스트링이 던져진다. DB의 옵티마이저가 쿼리를 파싱, 실행한다.

계속 다른 사원을 조회하면? DB 입장에서는 조회조건만 다른 쿼리에 대해 파싱을 반복적으로 하고 있는 셈이고, 매번 파싱을 하고 결과값을 리턴해야 하기에 조회시 결과값 출력도 그만큼 늦어진다.

만일 이 시스템 전체가 Statement 인터페이스로 이뤄져 있고, 향후 시스템 성능 테스트에서도 이 부분이 발견되지 않는다면 어느날 갑자기 원인도 모르게 CPU 사용도가 올라가고 시스템 전체가 느려지는 현상이 발생하게 된다. 서버 관리자가 서버를 내렸다 올리겠지만 원인을 알 수 없으니 속이 편하진 않을 터.

나중에 다시 스트레스 테스트를 하고, 결국 파싱과 실행 수가 비슷하다는 사실이 발견된다. 책임은 개발자들에게 돌아간다.

Statement로 이뤄진 프로그램을 PreparedStatement로 바꾸라는 지시가 떨어지겠지만 이는 간단치 않은 일이다. Statement로 되어있는 프로그램을 그동안 copy & paste 해서 만든 프로그램이 몇 개인지 파악도 안되고, 파악이 되다 해도 바꾸는 작업은 쉽지 않다.

PreparedStatement는 주어진 쿼리를 미리 파싱을 해두기 때문에, 조회조건만 달라진 다른 쿼리가 들어오면 바로 실행만 시키게 된다. 시스템 성능 면에서 훨씬 더 효율적이다.

엔터티빈으로 접근한 row는 또 다른 Connetion 객체로 접근하지 말 것
세션빈에서 한 레코드에 대해 변경을 하는 작업을 엔터티빈으로 한다고 하고, 이때 요청사항으로 추가되는 어떤 컬럼에 대해 변경하는 부분이 늘어난다고 하자. 기존 소스는 아래와 같다. 아래 소스는 세션빈 내에서 사원발령이 났을 경우 발령 시작일자와 발령 마감일자를 변경하는 비즈니스 프로세스를 갖고 있다.


  public boolean setMoveEmp(String empNo,String startDt, String endDt ) throws Exception
  {

    boolean doneYn = false;
    EmpHome empHome = null;
    Emp emp = null;

    try {
      EmpPk pk = new EmpPk();

      /* HomeFactory : JNDI 이름으로 Entitybean의 HOME 객체를 리턴한다 */
      empHome = (EmpHome)HomeFactory.getHome(EmpHome.클래스);

      pk.empNo = empNo;
      emp = empHome.finDByPrimaryKey(pk);
      emp.setStartDt( startDt );
      emp.setEndDt( endDt );

    } catch(Exception e) {
      throw new Exception(e.toString());
    } finally {
          ConnManager.closeConnection(con);
      } catch(Exception e) {
          throw new Exception(e);
      }
    }
    return doneYn;
  }


여기에서 사원발령정보를 변경하기 위해 엔터티빈 리모트 인터페이스의 setStartDt, setEndDt 메소드를 사용했다. 여기서 만일 사원발령정보를 낼 때 변경 일자도 기록하라는 요청사항이 추가될 경우, 이를 기존 소스에 추가하는 작업에서 엔터티빈을 사용하지 않고 Connection 객체를 만들어서 직접 SQL문을 던지는 형식으로 레코드의 한 컬럼 값을 변경시킨다고 가정해보자.


  public boolean setMoveEmp(String empNo,String startDt, String endDt ) throws Exception
  {

    /* Connection 선언 추가한다. */
    Connection con = null;
    boolean doneYn = false;
    EmpHome empHome = null;
    Emp emp = null;

    try {

      /* Connection instance 받아온다. */
      con = ConnManager.getConnection();
      EmpDB DB = new EmpDB(); /* DB 래퍼 선언 */



      EmpPk pk = new EmpPk();

      /* HomeFactory : JNDI 이름으로 Entitybean의 HOME 객체를 리턴한다 */
      empHome = (EmpHome)HomeFactory.getHome(EmpHome.클래스);

      pk.empNo = empNo;
      emp = empHome.finDByPrimaryKey(pk);

      emp.setStartDt( startDt );


      /* 변경 일자를 기록하는 부분이 추가된다 */
      DB.setUpdateDate(con,empNo);


      emp.setEndDt( endDt );



    } catch(Exception e) {
      throw new Exception(e.toString());
    } finally {
      try {
        ConnManager.closeConnection(con);
      } catch(Exception e) {
        throw new Exception(e);
      }
    }
    return doneYn;
  }


위의 소스를 보면 일견 아무런 문제가 없어 보인다. 컴파일도 잘 되고 실행시에 아무런 에러도 나지 않는다. 하지만 결과를 보면,

기존

emp.setStartDt( startDt );
emp.setEndDt( endDt );


를 타서, 레코드에 startDt와 endDt 컬럼 부분은 모두 원하는 데로 변경이 되었으나,


/* 변경 일자를 기록하는 부분이 추가된다 */
DB.setUpdateDate(con,empNo);


부분이 실행이 되었음에도 변경 일자 컬럼에는 아무런 변화가 없음을 알 수 있다.

엔터티빈의 인스턴스를 가지고 온 후부터 DB 레코드와 엔터티빈간의 동기화가 형성되고, 세션빈 메소드가 끝났을 때 DB에 결과를 쓰게 된다. 즉, 최종적으로 기록이 남는 것은 엔터티빈으로 작업한 결과이다. 중간에 새로 생긴 업데이트 세션은 그 작업은 성공한다 해도 DB에는 전혀 반영이 되지 않는 것이다.

이 소스를 보고 ‘그렇다면 모두 엔터티빈 메소드로 실행시키면 되지 왜 굳이 Connection 객체로 접근하는가'라는 의문이 들 수도 있다. 하지만 시스템을 오픈하고 나서 테이블에 컬럼을 추가해야 하는 경우가 업무적으로 반드시 생기게 된다. 그때 추가된 컬럼에 대해서는 엔터티빈안에 추가된 컬럼의 필드와 상응하는 변수를 추가하고 엔터티빈을 서버에 다시 배치해야 한다. 하지만 이 작업이 번거로우므로 추가된 컬럼에 한해서 SQL 쿼리로 간단히 핸들링하고자 하는 계획을 세우게 된다. 그럴때 위와 같은 경우가 생기게 되고, 한 row에 대해서 엔터티빈을 콜 할 때 엔터티빈 인스턴스와 DB가 동기화 된다는 것을 염두에 두지 못한 채 Connetion 객체를 써서 업데이트하는 하는 일도 생기는 것이다.

위의 소스는 모두 Connection 객체를 써서 해당 row의 컬럼을 변경 시키든지, 아니면 모두 엔터티빈 메소드를 쓰도록 변경해야 한다.

엔터티빈 사용을 지양하자
주위에서 진행되는 프로젝트들을 살펴보면 엔터티빈을 몰라서 쓰지않는 것이 아니라, 엔터티빈의 성질을 잘 알기 때문에 안쓰는 경우를 종종 보게된다.

엔터티빈의 이점이 몇가지 있다는 것은 분명하지만 개발자 입장에서는 쿼리로 간단히 대처를 할 수 있다는 측면에서 엔터티빈을 기피하게 된다.

한편 엔터티빈 말고 Connection 객체로 DB를 핸들링한다고 해서 트랜잭션이 언제나 완결되는가 하면 그것은 또 아니다. 분명 엔터티빈보다는 훨씬 더 강력하지만 예외도 있다. 다음 이어지는 강좌에서는 그에 해당하는 예제를 들고, 이를 피하려면 어떤 전략을 취해야 하는지 소개하도록 하겠다. @

'dev' 카테고리의 다른 글

locatin 속성  (0) 2008.09.12
EUC-kr, Unicode, UTF-8  (1) 2008.08.20
jms  (0) 2008.08.04
expression pattern  (0) 2008.08.01
script class  (0) 2008.08.01
Posted by 으랏차
,

jms

dev 2008. 8. 4. 22:20

원문 : http://cafe.naver.com/mythinkbank.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=245

#######################################
# JMS(Java Message Service) API
#######################################
- JMS API는 애플레케이션에서 메시지를 생성하고 전송하고 전송받고 읽는 기능을 구현할 때 사용하는 Java API이다.
- 메세징은 느슨한 연결(Lazy Connection)되어 있는 분산 처리 시스템의 통신방법이다.
- 메시징은 보내는 쪽이 직접 메시지를 받는 쪽에 전달하는 것이 아니라 "목적지"라는 특별한 저장소로 전달하는 것이기 때문에

  받는쪽에 대한 어떠한 정보도 알 필요가 없다. 마찬가지로 받는 쪽에서도 보내는 쪽의 정보를 알 필요 없이 목적지로부터 메시지를

  전달받으면 된다. 따라서 보내는 쪽과 받는 쪽 모두 메시지가 어떤 형태이고 목적지가 어딘지만 알면 되는 것이다.
- JMS API는 느슨하게 연결된 통신 방법뿐 아니라 다음과 같은 특징도 가지고 있다.
   - 비동기식 : JMS제공자는 자신에게 전달된 메시지를 메시지를 전달받은 클라이언트에게 비동기식으로 전달할 수 있다.
   - 신뢰성 : JMS API는 오직 한번만 전달된다. 목적지에 전달된 메시지는 전달받으려는 클라이언트에게 메시지가 전달된 후에는 다시

                  전달되지 않는다. 이는 신뢰성이 낮은 수준의 메시지를 잃어버리거나 중복된 메시지가 잔달되는 애플리케이션에서 많은

                  도움이 될 수 있다.
- RPC(Remote Procedure Call)보다 JMS가 사용되는게 바람직한 경우
   - 다른 컴포넌트의 인터페이스 정보에 의존하지 않고 컴포넌트가 쉽게 변경되는 서비스를 원할 때
   - 동시에 모든 애플리케이션이 작동하고 있는 상황에서 특정 애플리케이션이 작동하기를 원할때
   - 애플리케이션 비즈니스 모델을 사용하는 컴포넌트가 다른 곳으로 정보를 전달하고, 바로 응답을 받지 않은 상태에서도 계속

      작동하는것을 원할때
  
########################################
# JMS API 기본 개념
########################################
1. JMS API 구조(JMS API Architecture)
   - JMS 제공자(JMS Provider) : JMS 인터페이스를 구현하고 관기 기능과 제어 기능을 가지고 있는 메시징 시스템을 말한다.

                                              J2EE1.3 플랫품 이후부터는 애플리케이션 서버에 JMS 제공자가 포함되고 있다.
   - JMS 클라이언트(JMS Client) : 메시지를 생산하고 소비하는 자바 언어로 작성된 프로그램이나 컴포넌트를 말한다. 모든 J2EE

                                                애플리케이션 컴포넌트는 JMS 클라이언트로 사용될 수 있다.
   - 메시지(Message) : JMS클라이언트 간에 통신 정보를 주고받을 수 있는 객체를 말한다.
   - 관리객체(Administered Object) : 클라이언트가 사용하기 위해 미리 설정된 JMS 객체로서 애플리케이션 관리자가 생성한다.

                                                       JMS 관리 객체는 목적지와 커넥션 팩토리 두 가지가 있다.
                                          
2. 메시징 도메인(Messaging Domain)
   - 메시징 도메인인란 JMS API를 이용하여 개발한 애플리케이션으로 JMS API 이전에는 대부분 Point-to-Point 또는

     Publish/Subscribe 방식의 메시징 서비스를 지원하였다.               
   - JMS명세서에서는 각각의 분리된 도메인이나 동일한 도메인에서 메시징 서비스를 할 수 있도록 지원한다.
   - 독립적인 JMS 제공자는 하나 또는 양쪽의 도메인을 구현할 수 있으나 J2EE 제품 제공자는 반드시 양쪽 도메인을 구축해야만 한다.
   - JMS API를 사용하여 개발한 대부분의 애플리케이션은 Point-to-Point와 Publish/Subscribe도메인을 지원할 수 있다.
  
3. Point-to-Point 메시징 도메인
   - Point-to-Point 방식의 메시징 도메인은 Queue, Sender, Receiver의 개념을 기본으로 하여 구성되어 있다.
   - 각각의 메세지는 지정된 Queue의 주소로 전달되고 전달 받으려는 클라이언트는 자신에게 전달된 메시지를 가지고 있는

      Queue로부터 메시지를 받는다.
   - Point-to-Point 메시징 도메인의 특징
      - 각각의 메시지는 오직 하나의 소비자에게만 전달된다.
      - 메시지 Sender와 Receiver는 시간에 비의존적이다. 즉 Receiver는 Sender가 언제 메시지를 보냈는지에 상관없이 자신이

        원할 때 메시지를 받는다.
      - Receiver는 큐에 성공적으로 메시지를 받았음을 알린다.
   - Point-to-Point 메시징 도메일을 사용하여 메시지를 전달하면 반드시 한 소비자에게만 메시지가 전달된다.
  
4. Publish/Subscribe 메시징 도메인
   - Publish/Subscribe 방식의 메시징 도메인은 메시지를 전달하는 클라이언트가 인터넷 게시판과 같은 기능을 갖는 Topic이라는

     목적지로 메시지를 전달한다.
   - Publisher와 Subscriber는 일반적으로 익명의 상대를 대상으로 하며 동적으로 메시지를 전달하거나 전달받을 수 있다.
   - 이 시스템은 다양한 Publisher가 다양한 Subscriber에게 메시지를 전달할 수 있고 토픽은 현재 자신에게 등록한 Subscriber에게

     메시지를 전달한다.
   - Publish/Subscribe 메시징 도메인의 특징은 다음과 같다.
      - 각각의 메시지는 다양한 소비자에게 전달된다.
      - Publisher와 Subcriber는 시간에 의존적이다. 특정 토픽(Topic)에 등록한 Subscriber는 자신이 등록한 토픽에 전달된

        메시지만을 받을 수가 있고, 메시지를 소비하기 위해서 반드시 Publisher에 연결된 상태이어야 한다.
       
5. 메시지의 소비
   - 메시징 서비스 제품들은 기본적으로 비동식이다. 그러나 하나의 메시지는 동기식, 비동기식 모두에서 사용될 수 있다.
      - 동기식(Synchronously) : Subscriber또는 Receiver는 명시적으로 receive()메소드를 호출하여 목적지로부터 메시지를

                                              받는다. 이 메소드의 호출은 메시지가 도착하거나 메시지가 도착하지 않았을때 지정된 시간까지

                                              대기한다.
      - 비동기식(Asynchromously) : 클라이언트는 이벤트 리스너와 비슷한 메시지 리스너(MEssage Listener)를 통해 메시지를

                                                   전달받을 수 있다. 메시지 리스너는 목적지에 메시지가 도착하였을 때 onMessage() 메소드가

                                                   호출되어 메시지가 전달되었음을 알려준다.


##########################################
# JMS API로 구성된 애플리케이션의 구성
##########################################
- JMS애플리케이션은 다음과 같은 구성요소로 되어 있다.
   - 관리객체(Administered Object) : 커넥션 펙토리(Connection Factory)와 목적지(Destination)
   - 커넥션(Connection)
   - 세션(Session)
   - 메시지 생산자(Message Producer)
   - 메시지 소비자(Message Consumer)
   - 메시지(Message)


1. 관리객체 (Administered Object)
   - JMS 애플리케이션에서 프로그래밍적인 요소보다 더 중요하게 관리되는 구성 요소가 커넥션 펙토리와 목적지라는 관리 객체이다.
   - 이들 객체의 생성과 관리는 JMS API에서 직접 구현하는 것이 아니라 JMS제공자에 의해서 관리된다.
   - 관리자는 JNDI 네임스페이스내에 이들 관리 객체를 구성하고 JMS 클라이언트는 JNDI API를 통해 등록된 JNDI네임스페이스를

     검색할 수 있다.


1.1 커넥션 팩토리(Connection Factory)
   - 커넥션 팩토리는 클라이언트가 JMS 서비스에 연결하기 위한 커넥션을 생성할 때 사용하는 객체이다.
   - 커넥션 팩토리는 관리자가 정의한 커넥션에 관련된 설정 매개 변수르르 캡슐화하고 있으며, 각각의 커넥션 팩토리는
     QueueConnectionFactory 또는 TopicConnectionFactory 인터페이스의 인스턴스이다.
   - JSM클라이언트는 프로그램 시작 부분에 JNDI API를 사용하여 커넥션 팩토리를 네이밍서비스에서 검색한다.
   - 검색된 커넥션 팩토리는 ConnectionFactory 객체로 캐스팅하여 사용되기도 하고 QueueConnectionFactory나
     TopicConnectionFactory 객체로 캐스팅하여 사용되기도 한다.
   - EX>
        Context ctx = new InitialContext();
        ConnectionFactory connectionFactory = (ConnectionFactory)ctx.lookup("jms/ConnectionFactory");
       
1.2 목적지(Destination)
   - 목적지는 생성된 메시지가 저장되는 저장소로 그리고 메시지가 소비되는 곳으로 사용되는 객체이다.
   - 목적지는 Point-to-Point 메세징 도메인에서는 Queue, Publish/Subscribe 메시징 도메인에서는 Topic이라 부른다.
   - 애플리케이션 서버를 사용하여 목적지를 생성하려면 다음 두 가지 단계를 거쳐야 한다.
      - JNDI 목적지의 이름을 지정한 JMS 목적지 리소스를 생성한다.
      - JNDI 이름을 참조하는 물리적인 목적지를 사용한다.
   - 클라이언트 프로그램에서는 먼저 커넥션 팩토리를 네이밍 컨텍스트에서 검색하고 그런 다음에 목적지를 검색한다.
   - 목적지 객체는 Destination 타입으로 캐스팅되며, 적절한 다른 타입으로 캐스팅될 수도 있다.
   - Ex> Destination myDest = (Destination)ctx.lookup("jms/MyTopic");
           Topic myTopic = (Topic)myDest;
           or
           Queue myQueue = (Queue)ctx.lookup("jms/MyQueue");
          
2. 커넥션(Connection)
   - 커넥션은 JMS 제공자를 이용한 가상의 커넥션을 캡슐화하고 있다.
   - 커넥션은 클라이언트와 제공자 서비스 데몬 사이를 TCP/IP 소켓을 이용하여 통신하고, 개발자는 커넥션을 사용하여 한 개 또는
     여러개의 세션을 생성한다.
   - 커넥션은 ConnectionFactory 객체의 createConnection() 메소드를 호출하여 Connection인터페이스 타입으로 얻을 수 있다.
        Connection connection = connectionFactory.createConnection();
   - 애플리케이션이 완료되기 전에 생성했던 커넥션은 반드시 닫아야 한다.
        connection.close();
       
3. 세션(Session)
   - 세션은 메시지를 생성하고 소비하는 단일 스레드 단위이다.
   - 세션은 메시지 리스너가 실행될 때 함께 작동하며, 메시지 생산자, 메시지 소비자 그리고 메시지를 생성할 때 세션을
     사용할 수 있다.
   - 애플리케이션에서 메시지를 보내고 받기 위한 최소한의 단위를 트랜잭션이라 하는데 세션은 이런 트랜잭션을 기능을 제공한다.
   - 세션은 Session 인터페이스를 구현하고 Connection 객체의 createSession() 메소드를 호출하여 세션 객체를 생성할 수 있다.
         Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
   - 트랜잭션에 포함되는 세션을 생성하려면 다음과 같이 작성한다.
         Session session = connection.createSession(true, 0);
     
4. 메시지 생산자(Message Producer)
   - 메시지 생산자는 목적지에 메시지를 전달하는 역할을 하는 객체이다.
   - 메시지 생산자는 MessageProducer 인터페이스를 구현한 객체로 MessageProducer를 생성하기 위해서는 Session 객체를
     사용한다.
   - Ex> MessageProducer producer = session.createProducer(myQueue);
            MessageProducer producer = session.createProducer(myTopic);
   - 메시지 생성자를 생성한 다음에 send()메소드를 호출하여 메시지를 전달한다.
         producer.send(message);
   - createProducer()메소드에 null값을 지정하여 특정한 생성자를 할당하지 않을 수도 있다. 이렇게 메시지 목적지를 갖지 않는
     메시지 생산자는 목적지를 지정하기 위해서 send() 메소드의 첫번째 매개 변수에 목적지를 지정해야 한다.
         MessageProducer anon_prod = session.createProducer(null);
         anon_prod.send(myQueue, message);
     
5. 메시지 소비자(Message Consumer)
   - 메시지 소비자는 목적지에 전달된 메시지를 전달받는 역할을 하는 객체이다.
   - 메시지 소비자는 MessageConsumer 인터페이스를 구현한 객체로 메시지 생산자와 마찬가지로 세션에 의해서 생성된다.
   - 메시지 소비자는 JMS클라이언트가 JMS제공자를 이용하여 목적지에 클라이언트 자신을 등록할 수 있도록 해준다.
   - 목적지에 등록된 클라이언트만이 메시지를 전달받을 수 있으며, JMS제공자는 목적지에 등록된 메시지 소비자에게 목적지에 전달된
     메시지를 전달하는 것을 관리하게 된다.
   - Ex> MessageConsumer consumer = session.createConsumer(myQueue);
            MessageConsumer consumer = session.createConsumer(myTopic);
   - 메시지 소비자가 생성된 다음에 이 메시지 소비자를 이용하여 메시지를 잔달받을 수 있으나 메시지는 커넥션이 연결된 다음
     start() 메소드가 호출되기 전까지는 전달되지 않는다. 메시지 소비자가 동기적으로 메시지를 소비하기 위해서는 receive()
     메소드가 사용되는데 start()메소드가 호출된 다음에는 언제든지 receive()메소드를 호출할 수 있다.
         MessageConsumer의 close()메소드를 사용하면 메시지의 소비자의 사용을 중지할 수 있다.
         connection.start();
         Message m = consumer.receive();
         connection.start();
         Message m = consumer.receive(1000); // time out after a second
   - 만약 비동기적으로 메시지를 소비하려면 메시지를 이벤트처럼 관리하는 메시지 리스너가 필요하다.
  
5.1 메시지 리스터(Message Listener)
   - 메시지 리스너는 JMS API에서 사용되는 이벤트 핸들러로 비동기식으로 메시지를 전달받을 수 있게 해준다.
   - 메시지 리스너는 MessageListener 인터페이스를 구현해야 하며 MessageListener에는 onMessage()라는 단 하나의 이벤트
     메소드를 가지고 있다. 메시지가 목적지에 도착했을 때 작업하려는 내용을 onMessage()메소드에 작성하면 된다.
   - 메시지 리스너를 등록하기 위해서는 MessageConsumer의 setMessageListener()메소드를 이용한다.
        Listener myListener = new Listener();
        consumer.setMessageListener(myListener);
   - 메시지 리스너를 등록한 다음에는 Connection의 start()메소드를 호출하여 메시지 전송을 시작한다.
   - 메시지가 전달되기 시작하면 JMS 제공자는 메시지가 전달되었을 때 자동으로 메시지 리스너의 onMessage() 메소들 호출한다.
     onMessage() 메소드는 메시지를 매개 변수로 전달받는데 전달받은 메시지는 6가지(TextMessage, MapMessage,
     BytesMessage, StreamMessage, ObjectMessage, Message)의 메시지 타입 중 하나로 변환하여 사용할 수 있다.
    
5.2 메시지 셀렉터(Message Selector)    
   - 애플리케이션에서 전달받은 메시지를 필터링(Filtering)할 필요가 있다면 JMS API의 메시지 셀렉터를 사용한다.
   - 메시지 셀렉터의 표현식은 문자열로 나타내는데 이 표현식은 SQL92조건절을 기반으로 해서 작성한다.
   - Ex> NewsType = '스포츠' OR NewsType = '독자의견'
   - createConsumer()와 createDurableSubsriber()메소드를 이용하여 메시지 소비자를 생성할 때 매개 변수로 메시지 셀렉터를
     지정할 수 있다. 이렇게 메시지 셀렉터를 가지고 생성된 메시지 소비자는 메시지의 해더(Header)와 프로퍼티(Property)가
     셀렉터와 일치하는 메시지만을 전달받게 된다.
     
6. 메시지(Message)
   - JMS 애플리케이션의 최종 목적은 메시지를 생성한 다음에 다른 소프트웨어 애플리케이션으로 하여금 이 메시지를 소비하도록
     하는데 있다.
   - JMS메시지는 Header, Property 그리고 Body 의 세부분으로 구성되어 있다. 이중 Header는 필수사항이고 나머지는 옵션이다.
      - Message Header(필수)
      - Message Property(옵션)
      - Message Body(옵션)
     
6.1 메시지 헤더(Message Header)
   - JMS메시지 해더는 JMS클라이언트와 JMS제공자가 메시지를 확인하고 전달하기 위해 사용하는 몇가지 정의된 값을 가지고 있다.
   - 모든 메시지는 JMS 헤더 필드에 JMSMessageID라는 유일한 식별자를 가지고 있다.
   - JMSDestination이라는 헤더 필드는 메시지가 보내질 큐나 토픽을 나타낸다.
   - 각각의 헤더 필드는 모두 setter()/getter()메소드를 가지고 있으며 Message 인터페이스를 참조하여 이를 사용할 수 있다.
   - 대부분의 헤더 필드는 send() 또는 publish() 메소드에 의해서 자동으로 사용되는 데 반해서 클라이언트에 의해서 사용되기도
     한다.
   JMS 메시지 헤더 필드
   ===============================================================
   헤더필드                                  사용되는 위치
   ==================      ==========================================
   JMSDestination                       send() 또는 publish()메소드
   JMSDeliveryMode                    send() 또는 publish()메소드
   JMSExpiration                         send() 또는 publish()메소드
   JMSPriority                              send() 또는 publish()메소드
   JMSMessageID                       send() 또는 publish()메소드
   JMSTimestamp                       send() 또는 publish()메소드
   JMSCorrelationID                   Client
   JMSReplyTo                            Client
   JMSType                                 Client
   JMSRedelivered                      JMS제공자
  
6.2 메시지 프로퍼티(Message Property)
   - 메시지에 헤더 필드가 제공한 정보 외에 추가로 정보가 필요하면 메시지 프로퍼티를 생성하여 지정하면 된다.
   - 메시지 프로퍼티는 다른 메시지 시스템과 호환되어 사용될 수 있고 메시지 셀렉터에서 사용될 수 있다.
  
6.3 메시지 바디(Message Body)
   - JMS API에는 6가지의 메소드 바디타입이 있다.
   - 다양한 형태의 데이터로 메시지 바디를 채울수 있도록 JMS API는 여러 메소드를 제공한다.
      Ex> TextMessage message = session.createTextMessage();
            message.setText(msg_text);  // msg_text is a String
            producer.send(message);
   - 소비하는 측에서는 메시지를 일반적인 Message인터페이스 타입으로 받는데 반드시 적절한 메시지 타입으로 캐스팅해야만
     메시지를 사용할 수 있다.
   - 캐스팅된 메시지의 내용을 얻기 위해서는 getText()메소드와 같은 getter()메소드를 이용한다.
      Ex> Message m = consumer.receive();
            if(m instanceof TextMessage){
                TextMessage message = (TextMessage)m;
                System.out.println("Reading message : " + message.getText());
            }else{
                //Handle Error
            }
  JMS 메시지 타입           
   ==============================================================
   메시지 타입                         메시지 내용
   =================   ======================================
   TextMessage                    java.lang.String 객체 타입의 메시지이다.
   MapMessage                    name/value를 쌍으로 갖는 타입의 메시지이다.
   ByteMessage                    바이트스트림의 메시지이다. 이 메시지 타입은 기존의 메시지 포멧과 매칭하기 위해서 메시지의
                                            내용을 인코딩(Encoding)할 때 사용한다.
   StreamMessage                자바에서 사용하는 기본 스트림 타입의 메시지이다.
   ObjectMessage                 자바에서 사용하는 직렬화된 객체 타입의 메시지이다.
   Message                           메시지 바디가 없이 헤더와 프로퍼티만으로 구성되어 있다. 메시지 바디가 필요하지 않을 때 사용한다.
  
7. 예외처리
   - JMS API에서 사용되는 예외중 최상위 예외 클래스는 JMSException이다. JMSException 서브 클래스는 다음과 같다.
      - IllegalStateException
      - InvalidClientIDException
      - InvalidDestinationException
      - InvalidSelectorException
      - JMSSecurityException
      - MessageEOFException
      - MessageFormatException
      - MessageNotReadableException
      - MessageNotWriteableException
      - ResourceAllocationException
      - TransactionInProgressException
      - TransactionRolledBackException  


########################################

# Sun Application 설정

########################################      

- Sun Application Server 를 JMS제공자로 사용하기위해 다음 세가지를 설정한다.

   - 두 개의 커넥션 펙토리 (Queue/Topic)

   - 두 개의 물리적인 목적지 (Queue/Topic)

   - 두 개의 목적지 리소스 (Queue/Topic)


- 커넥션 펙토리 설정

   1. Admin Console을 실행한다

   2. Java Message Service Resource > Connection Factory 노드 선택

   3. JMS Connection Factory 페이지에서 "New" 버튼클릭 -> Create JMS Connection Factory 화면 이동

   4. JNDI Name 필드에서 jms/QueueConnectionFactory 입력, Type 콤보박스에서 javax.jms.QueueConnectionFactory를

       선택, Resource 항목은 Enabled Check한 후 "OK" 버튼으로 커넥션 펙토리를 저장한다.

   5. 위와 같은 방법으로 jms/TopicConnectionFactory, javax.jms.TopicConnectionFactory를 생성한다.


- 물리적 목적지 설정

   1. Configuration > Java Message Service > Physical Destination 항목을 선택한다.

   2. Physical Destination 항목에서 "New" 를 클릭 -> Create Physical Destination 페이지로 이동

   3. Physical Destination Name 필드에 PhysicalQueue라고 입력. Type 콤보박스에서 Queue 선택. "OK" 버튼을 클릭하여

      저장한다.

   4. 위와 같은 방법으로 PhysicalTopic/topic을 생성한다.


- 목적지 리소스 생성

   1. JMS Resource > Destination Resource 으로 이동한다.

   2. "New" 버튼을 클릭 -> Create JMS Destination Resource 페이지로 이동한다.

   3. JNDI Name필드에 jms/Queue라고 입력. Type콤보박스에서 javax.jms.Queue를 선택하고 Resource Enabled항목으로

       선택. Additional Property 항목의 Name 필드의 Value에 PhysicalQueue라고 입력한 후 "OK"버튼을 클릭하여 저장한다.

   4. 위와 같은 방법으로 jms/Topic/javax.jms.Topic/Resource Enabled Check 선택, Name 필드의 Value에 PhysicalTopic

       라고 입력한다.

  
########################################

# 예제!!

########################################    

/*
 * SimpleProducer.java
 *
 * Created on 2007년 3월 26일 (월), 오후 9:25
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package simpleproducer;

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.naming.*;
import javax.jms.*;

/**
 *
 * @author Administrator
 */
public class SimpleProducer {
   
    /** Creates a new instance of SimpleProducer */
    public SimpleProducer() {
    }
   
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        final int NUM_MSGS;
       
        if ((args.length < 1) || (args.length > 2))
        {
            System.out.println("Program take one or two arguments : <dest_name> [<number-of message>]");
            System.exit(1);
        }
       
        String destName = new String(args[0]);
        System.out.println("Destination name is " + destName);
       
        if (args.length == 2)
        {
            NUM_MSGS = (new Integer(args[1])).intValue();
        }
        else
        {
            NUM_MSGS = 1;
        }
       
        Context jndiContext = null;
       
        try
        {
            jndiContext = new InitialContext();
        }
        catch(NamingException e)
        {
            System.out.println("Could not create JNDI API context : " + e.toString());
            System.exit(1);
        }
       
        ConnectionFactory connectionFactory = null;
        Destination dest = null;
       
        try
        {
            connectionFactory = (ConnectionFactory)jndiContext.lookup("jms/QueueConnectionFactory");
            dest = (Destination) jndiContext.lookup(destName);
        }
        catch(Exception e)
        {
            System.out.println("JNDI API lookup failed : " + e.toString());
            e.printStackTrace();
            System.exit(1);
        }
       
        Connection connection = null;
        MessageProducer producer = null;
       
        try
        {
            connection = connectionFactory.createConnection();
           
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
           
            producer = session.createProducer(dest);
           
            TextMessage message = session.createTextMessage();
           
            for(int i = 0; i < NUM_MSGS; i++)
            {
                message.setText("This is message " + (i + 1));
                System.out.println("Sending message : " + message.getText());
               
                producer.send(message);
            }
        }
        catch(JMSException e)
        {
            System.out.println("Exception occurred : " + e.toString());
        }
        finally{
            if (connection != null)
            {
                try{
                    connection.close();
                }
                catch(JMSException e)
                {
                   
                }
            }
        }
       
    }
   
}




/*
 * SimpleSynchConsumer.java
 *
 * Created on 2007년 3월 26일 (월), 오후 10:21
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package simplesynchconsumer;

import javax.jms.*;
import javax.naming.*;

/**
 *
 * @author Administrator
 */
public class SimpleSynchConsumer {
   
    /** Creates a new instance of SimpleSynchConsumer */
    public SimpleSynchConsumer() {
    }
   
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
       
        String destName = null;
        Context jndiContext = null;
        ConnectionFactory connectionFactory = null;
        Connection connection = null;
        Session session = null;
        Destination dest = null;
        MessageConsumer consumer = null;
        TextMessage message = null;
       
        if(args.length != 1)
        {
            System.out.println("Program takes one argument : <dest_name>");
            System.exit(1);
        }
       
        destName = new String(args[0]);
        System.out.println("Destination name is " + destName);
       
        try
        {
            jndiContext = new InitialContext();
        }
        catch(NamingException e)
        {
            System.out.println("Could not create JNDI API context : " + e.toString());
            System.exit(1);
        }
       
        try
        {
            connectionFactory = (ConnectionFactory)jndiContext.lookup("jms/QueueConnectionFactory");
            dest = (Destination)jndiContext.lookup(destName);
        }
        catch(Exception e)
        {
            System.out.println("JNDI API lookup failed : " + e.toString());
            System.exit(1);
        }
       
        try
        {
            connection = connectionFactory.createConnection();
           
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
           
            consumer = session.createConsumer(dest);
           
            connection.start();
           
            while(true)
            {
                Message m = consumer.receive(1);
               
                if (m != null)
                {
                    if (m instanceof TextMessage)
                    {
                        message = (TextMessage)m;
                       
                        System.out.println("Reading message : " + message.getText());
                    }
                }
                else
                {
                    break;
                }
            }
        }
        catch(JMSException e)
        {
            System.out.println("Exception occured : " + e.toString());
        }
        finally
        {
            if (connection != null)
            {
                try{
                    connection.close();
                }
                catch(JMSException e)
                {
                   
                }
            }
        }
    }
   
}




/*
 * SimpleAsynchConsumer.java
 *
 * Created on 2007년 3월 26일 (월), 오후 11:02
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package simpleasynchconsumer;

import javax.jms.*;
import javax.naming.*;
import java.io.*;

/**
 *
 * @author Administrator
 */
public class SimpleAsynchConsumer {
   
    /** Creates a new instance of SimpleAsynchConsumer */
    public SimpleAsynchConsumer() {
    }
   
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
       
        String destName = null;
        Context jndiContext = null;
        ConnectionFactory connectionFactory = null;
        Connection connection = null;
        Session session = null;
        Destination dest = null;
        MessageConsumer consumer = null;
        TextListener listener = null;
        TextMessage message = null;
        InputStreamReader inputStreamReader = null;
        char answer = '\0';
       
        if (args.length != 1)
        {
            System.out.println("Program takes one argument : <dest_name>");
            System.exit(1);
        }
       
        destName = new String(args[0]);
        System.out.println("Destination name is " + destName);
       
        try
        {
            jndiContext = new InitialContext();
        }
        catch(NamingException e)
        {
            System.out.println("Could not create JNDI API context : " + e.toString());
            System.exit(1);
        }
       
        try
        {
            connectionFactory = (ConnectionFactory)jndiContext.lookup("jms/QueueConnectionFactory");
            dest = (Destination)jndiContext.lookup(destName);
        }
        catch(Exception e){
            System.out.println("JNDI API lookup failed : " + e.toString());
            System.exit(1);
        }
       
        try
        {
            connection = connectionFactory.createConnection();
           
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
           
            consumer = session.createConsumer(dest);
           
            listener = new TextListener();
           
            consumer.setMessageListener(listener);
           
            connection.start();
           
            System.out.println("To end program, type Q or q, then <return>");
           
            inputStreamReader = new InputStreamReader(System.in);
           
            while(!((answer == 'q') || (answer == 'Q')))
            {
                try
                {
                    answer = (char)inputStreamReader.read();
                }
                catch(IOException e)
                {
                    System.out.println("I/O exception : " + e.toString());
                }
            }
        }
        catch(JMSException e)
        {
            System.out.println("Exception occured : " + e.toString());
        }
        finally
        {
            if (connection != null)
            {
                try
                {
                    connection.close();
                }
                catch(JMSException e)
                {

                }
            }
        }
    }
   
}



/*
 * TextListener.java
 *
 * Created on 2007년 3월 26일 (월), 오후 11:02
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package simpleasynchconsumer;

import java.awt.font.TextMeasurer;
import javax.jms.*;

/**
 *
 * @author Administrator
 */
public class TextListener implements MessageListener {
   
    /** Creates a new instance of TextListener */
    public TextListener() {
    }
   
    public void onMessage(Message message)
    {
        TextMessage msg = null;
       
        try
        {
            if (message instanceof TextMessage)
            {
                msg = (TextMessage)message;
               
                System.out.println("Reading message : " + msg.getText());
            }
            else
            {
                System.out.println("Message is not a TextMessage");
            }
        }
        catch(JMSException e)
        {
            System.out.println("JMSException in onMessage() : " + e.toString());
        }
        catch(Throwable t)
        {
            System.out.println("Exception in onMessage() : " + t.getMessage());
        }
    }
   
}

'dev' 카테고리의 다른 글

EUC-kr, Unicode, UTF-8  (1) 2008.08.20
ejb  (0) 2008.08.05
expression pattern  (0) 2008.08.01
script class  (0) 2008.08.01
정규화  (0) 2008.08.01
Posted by 으랏차
,

expression pattern

dev 2008. 8. 1. 17:40
Javascript Reqular Expression Pattern



자바스크립트뿐만 아니라 다른 언어의 정규식 표현들은 항상 잊어 버리기 싶단 말이지...

항상 할때 마다 생각이 나지 않아서 정리해 본다.

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


http://doc.ddart.net/scripting/html/reconintroductiontoregularexpressions.htm

http://www.javascriptkit.com/javatutors/redev2.shtml

http://del.icio.us/kebie/%EC%A0%95%EA%B7%9C%EC%8B%9D

 

 

 

CSS Hack에 대한 사설

http://www.javascriptkit.com/dhtmltutors/csshacks.shtml

 



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

 

1. 만들기

1)
    var re=/pattern/flags;

2)
    var re=new RegExp("pattern","flags");

3) 차이 - new로 만들때에는 이스케이프문자는 \\는 \\\\로 해주어야 한다.
    var re=/\\w/;
    var re=new RegExp("\\\\w");


2. 플래그(flag)

  • g (Global 찾기) 패턴에 맞는 모든문자 찾기
  • i (Ignore Case) 대소문자 무시
  • m (Multiline) 여러줄

3.

  • ^ 문자열의 시작을 의미 ,m 플래그를 사용할경우 경우는 각 문자열의 시작
  • $ 문자열의 끝을 의미 ,m 플래그를 사용할경우 경우는 각 문자열의 끝
  • . 모든 한문자

4.

  •     [문자들]  - 괄호안의 문자 하나와 매치
            예) [abc] 는 a나 b나 c중 하나를 의미

  •     [^문자들] - 괄호안의 문자가 아닌문자와 매치
            예) [^abc] 는 1,2.... d,e.... 등과 매치

  •     [문자1-문자2] - 문자1과 문자2와 그 사이의 값과 매치
           예) [a-d] a,b,c,d와 매치    

5. (abc) abc와 매치

6.
|

   
좌우 패턴중 하나를 의미
        예) (abc|def) abc나 def를 의미

7. *, +, ?
        *  앞의 패턴이 0회 또는 그 이상반복됨
        +  앞의 패턴이 1회 또는 그 이상반복됨
        ?  앞의 패턴이 0또는 1회 반복

8. {n}, {n,}, {n,m} 패턴의 반복회수

    예)
        (abc){1,3} abc가 1에서 3회 반복
        (abc){1} abc가 1회반복
        (abc){,10} abc가 10회 이하 반복



9. 특수문자 (Escapes Character)

\\ 일반문자에 \\을 붙여서 특수한 용도로 사용한다.
\\f 폼피드(?)
\\r 캐리지리턴
\\n 새줄
\\t 일반 탭문자
\\v 세로 탭문자(?)
\\0 NUL널문자
[\\b] 백스페이스
\\s 공백문자
    \\f, \\n, \\r, \\t, \\v, \\u00A0, \\u2028, \\u2029
\\S 공백이아닌문자
\\w 알파벳문자,숫자,_ [a-zA-Z0-9_]
\\W 알파벳문자,숫자,_가 아닌문자 [^a-zA-Z0-9_]).
\\d 정수(short for [0-9]).
\\D 정수가 아닌 문자 (short for [^0-9]).
\\b 단어의 경계 공백,새줄.
\\B 경계가 아닌문자.
\\cX 컨트롤+문자 E.g: \\cm matches control-M.
\\xhh 핵사코드
\\uhhhh 유니코드

 

 

 

예)
<script language="javascript">
  function chk(pstr) {
   var chkRep = /....-..-../;
   alert(chkRep.test(pstr));
  }
</script>

정규식은 다음과 같다.

(1) ^ (caret) : 라인의 처음이나 문자열의 처음을 표시
예 : ^aaa (문자열의 처음에 aaa를 포함하면 참, 그렇지 않으면 거짓)

(2) $ (dollar) : 라인의 끝이나 문자열의 끝을 표시
예 : aaa$ (문자열의 끝에 aaa를 포함하면 참, 그렇지 않으면 거짓)

(3) . (period) : 임의의 한 문자를 표시
예 : ^a.c (문자열의 처음에 abc, adc, aZc 등은 참, aa 는 거짓)
a..b$ (문자열의 끝에 aaab, abbb, azzb 등을 포함하면 참)

(4) [] (bracket) : 문자의 집합이나 범위를 나타냄, 두 문자 사이의 "-"는 범위를 나타냄
[]내에서 "^"이 선행되면 not을 나타냄
이외에도 "문자클래스"를 포함하는 [:문자클래스:]의 형태가 있다.
여기에서 "문자클래스"에는 alpha, blank, cntrl, digit, graph, lower, print, space, uppper, xdigit가 있다.
이에 대한 자세한 내용은 C언어의 [ctype.h]를 참조하면 된다.
예를 들어 [:digit:]는 [0-9]와 [:alpha:]는 [A-Za-z]와 동일하다.
이외에 [:<:]와 [:>:]는 어떤 단어(숫자, 알파벳, '_'로 구성됨)의 시작과 끝을 나타낸다.
예 : [abc] (a, b, c 중 어떤 문자, "[a-c]."과 동일)
[Yy] (Y 또는 y)
[A-Za-z0-9] (모든 알파벳과 숫자)
[-A-Z]. ("-"(hyphen)과 모든 대문자)
[^a-z] (소문자 이외의 문자)
[^0-9] (숫자 이외의 문자)
[[:digit:]] ([0-9]와 동일)

(5) {} (brace) : {} 내의 숫자는 직전의 선행문자가 나타나는 횟수 또는 범위를 나타냄
예 : a{3} ('a'의 3번 반복인 aaa만 해당됨)
a{3,} ('a'가 3번 이상 반복인 aaa, aaaa, aaaa, ... 등을 나타냄)
a{3,5} (aaa, aaaa, aaaaa 만 해당됨)
ab{2,3} (abb와 abbb 만 해당됨)
[0-9]{2} (두 자리 숫자)
doc[7-9]{2} (doc77, doc87, doc97 등이 해당)
[^Zz]{5} (Z와 z를 포함하지 않는 5개의 문자열, abcde, ttttt 등이 해당)
.{3,4}er ('er'앞에 세 개 또는 네 개의 문자를 포함하는 문자열이므로 Peter, mother 등이 해당)

(6) * (asterisk) : "*" 직전의 선행문자가 0번 또는 여러번 나타나는 문자열
예 : ab*c ('b'를 0번 또는 여러번 포함하므로 ac, ackdddd, abc, abbc, abbbbbbbc 등)
* (선행문자가 없는 경우이므로 임의의 문자열 및 공백 문자열도 해당됨)
.* (선행문자가 "."이므로 하나 이상의 문자를 포함하는 문자열, 공백 문자열은 안됨)
ab* ('b'를 0번 또는 여러번 포함하므로 a, accc, abb, abbbbbbb 등)
a* ('a'를 0번 또는 여러번 포함하므로 k, kdd, sdfrrt, a, aaaa, abb, 공백문자열 등) doc[7-9]* (doc7, doc777, doc778989, doc 등이 해당)
[A-Z].* (대문자로만 이루어진 문자열)
like.* (직전의 선행문자가 '.'이므로 like에 0 또는 하나 이상의 문자가 추가된 문자열이됨, like, likely, liker, likelihood 등)

(7) + (asterisk) : "+" 직전의 선행문자가 1번 이상 나타나는 문자열
예 : ab+c ('b'를 1번 또는 여러번 포함하므로 abc, abckdddd, abbc, abbbbbbbc 등, ac는 안됨)
ab+ ('b'를 1번 또는 여러번 포함하므로 ab, abccc, abb, abbbbbbb 등)
like.+ (직전의 선행문자가 '.'이므로 like에 하나 이상의 문자가 추가된 문자열이 됨, likely, liker, likelihood 등, 그러나 like는 해당안됨)
[A-Z]+ (대문자로만 이루어진 문자열)

(8) ? (asterisk) : "?" 직전의 선행문자가 0번 또는 1번 나타나는 문자열
예 : ab?c ('b'를 0번 또는 1번 포함하므로 abc, abcd 만 해당됨)

(9) () (parenthesis) : ()는 정규식내에서 패턴을 그룹화 할 때 사용

(10) | (bar) : or를 나타냄
예 : a|b|c (a, b, c 중 하나, 즉 [a-c]와 동일함)
yes|Yes (yes나 Yes 중 하나, [yY]es와 동일함)
korea|japan|chinese (korea, japan, chinese 중 하나)

(11) \\ (backslash) : 위에서 사용된 특수 문자들을 정규식내에서 문자를 취급하고 싶을 때 '\\'를 선행시켜서 사용하면됨
예 : filename\\.ext ("filename.ext"를 나타냄)
[\\?\\[\\\\\\]] ('?', '[', '\\', ']' 중 하나)

정규식에서는 위에서 언급한 특수 문자를 제외한 나머지 문자들은 일반 문자로 취급함

 

 

 

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

 

 

 

문자 설명
\\ 그 다음 문자를 특수 문자, 리터럴, 역참조, 또는 8진수 이스케이프로 표시합니다. 예를 들어, 'n'은 문자 "n"을 찾고 '\\n'은 줄 바꿈 문자를 찾습니다. '\\\\' 시퀀스는 "\\"를 찾고 '\\('는 "("를 찾습니다.
^ 입력 문자열의 시작 위치를 찾습니다. Multiline 속성이 설정되어 있으면 ^는 '\\n' 또는 '\\r'앞의 위치를 찾습니다.
$ 입력 문자열의 끝 위치를 찾습니다. Multiline 속성이 설정되어 있으면 $는 '\\n' 또는 '\\r'뒤의 위치를 찾습니다.
* 부분식의 선행 문자를 0개 이상 찾습니다. 예를 들어, 'zo*'는 "z", "zoo" 등입니다. *는 {0,}와 같습니다.
+ 부분식의 선행 문자를 한 개 이상 찾습니다. 예를 들어, 'zo+'는 "zo", "zoo" 등이지만 "z"는 아닙니다. +는 {1,}와 같습니다.
? 부분식의 선행 문자를 0개 또는 한 개 찾습니다. 예를 들어, "do(es)?"는 "do" 또는 "does"의 "do"를 찾습니다. ?는 {0,1}과 같습니다.
{n} n은 음이 아닌 정수입니다. 정확히 n개 찾습니다. 예를 들어, 'o{2}'는 "Bob"의 "o"는 찾지 않지만 "food"의 o 두 개는 찾습니다.
{n,} n은 음이 아닌 정수입니다. 정확히 n개 찾습니다. 예를 들어, 'o{2}'는 "Bob"의 "o"는 찾지 않지만 "foooood"의 모든 o는 찾습니다. 'o{1,}'는 "o+"와 같고, 'o{0,}'는 "o*"와 같습니다.
{n,m} mn은 음이 아닌 정수입니다. 여기서 mn보다 크거나 같습니다. 최소 n개, 최대 m개 찾습니다. 예를 들어, "o{1,3}"은 "fooooood"의 처음 세 개의 o를 찾습니다. "o{0,1}"은 "o?"와 같습니다. 쉼표와 숫자 사이에는 공백을 넣을 수 없습니다.
? 이 문자가 다른 한정 부호(*, +, ?, {n}, {n,}, {n,m}) 의 바로 뒤에 나올 경우 일치 패턴은 제한적입니다. 기본값인 무제한 패턴은 가능한 많은 문자열을 찾는 데 반해 제한적인 패턴은 가능한 적은 문자열을 찾습니다. 예를 들어, "oooo" 문자열에서 "o+?"는 "o" 한 개만 찾고, "o+"는 모든 "o"를 찾습니다.
. "\\n"을 제외한 모든 단일 문자를 찾습니다. "\\n"을 포함한 모든 문자를 찾으려면 '[.\\n]' 패턴을 사용하십시오.
(pattern) pattern을 찾아 검색한 문자열을 캡처합니다. 캡처한 문자열은 VBScript의 경우 SubMatches 컬렉션, Jscript의 경우 $0...$9 속성을 이용하여 결과로 나오는 Matches 컬렉션에서 추출할 수 있습니다. 괄호 문자인 ( )를 찾으려면 "\\(" 또는 "\\)"를 사용하십시오.
(?:pattern) pattern을 찾지만 검색한 문자열을 캡처하지 않습니다. 즉, 검색한 문자열을 나중에 사용할 수 있도록 저장하지 않는 비캡처 검색입니다. 이것은 패턴의 일부를 "or" 문자(|)로 묶을 때 유용합니다. 예를 들어, 'industr(?:y|ies)는 'industry|industries'보다 더 경제적인 식입니다.
(?=pattern) 포함 예상 검색은 pattern과 일치하는 문자열이 시작하는 위치에서 검색할 문자열을 찾습니다. 이것은 검색한 문자열을 나중에 사용할 수 있도록 캡처하지 않는 비캡처 검색입니다. 예를 들어, "Windows(?=95|98|NT|2000)"는 "Windows 2000"의 "Windows"는 찾지만 "Windows 3.1"의 "Windows"는 찾지 않습니다. 예상 검색은 검색할 문자열을 찾은 후 예상 검색 문자열을 구성하는 문자 다음부터가 아니라 마지막으로 검색한 문자열 바로 다음부터 찾기 시작합니다.
(?!pattern) 제외 예상 검색은 pattern과 일치하지 않는 문자열이 시작하는 위치에서 검색할 문자열을 찾습니다. 이것은 검색한 문자열을 나중에 사용할 수 있도록 캡처하지 않는 비캡처 검색입니다. 예를 들어, "Windows(?!95|98|NT|2000)"는 "Windows 3.1"의 "Windows"는 찾지만 "Windows 2000"의 "Windows"는 찾지 않습니다. 예상 검색은 검색할 문자열을 찾은 후 예상 검색 문자열을 구성하는 문자 다음부터가 아니라 마지막으로 검색한 문자열 바로 다음부터 찾기 시작합니다.
x|y x 또는 y를 찾습니다. 예를 들어, "z|food"는 "z" 또는 "food"를 찾습니다. "(z|f)ood"는 "zood" 또는 "food"를 찾습니다.
[xyz] 문자 집합입니다. 괄호 안의 문자 중 하나를 찾습니다. 예를 들어, "[abc]"는 "plain"의 "a"를 찾습니다.
[^xyz] 제외 문자 집합입니다. 괄호 밖의 문자 중 하나를 찾습니다. 예를 들어, "[^abc]"는 "plain"의 "p"를 찾습니다.
[a-z] 문자 범위입니다. 지정한 범위 안의 문자를 찾습니다. 예를 들어, "[a-z]"는 "a"부터 "z" 사이의 모든 소문자를 찾습니다.
[^a-z] 제외 문자 범위입니다. 지정된 범위 밖의 문자를 찾습니다. 예를 들어, "[^a-z]"는 "a"부터 "z" 사이에 없는 모든 문자를 찾습니다.
\\b 단어의 경계, 즉 단어와 공백 사이의 위치를 찾습니다. 예를 들어, "er\\b"는 "never"의 "er"는 찾지만 "verb"의 "er"는 찾지 않습니다.
\\B 단어의 비경계를 찾습니다. "er\\B"는 "verb"의 "er"는 찾지만 "never"의 "er"는 찾지 않습니다.
\\cx X 가 나타내는 제어 문자를 찾습니다. 예를 들어, \\cM은 Control-M 즉, 캐리지 리턴 문자를 찾습니다. x 값은 A-Z 또는 a-z의 범위 안에 있어야 합니다. 그렇지 않으면 c는 리터럴 "c" 문자로 간주됩니다.
\\d 숫자 문자를 찾습니다. [0-9]와 같습니다.
\\D 비숫자 문자를 찾습니다. [^0-9]와 같습니다.
\\f 폼피드 문자를 찾습니다. \\x0c와 \\cL과 같습니다.
\\n 줄 바꿈 문자를 찾습니다. \\x0a와 \\cJ와 같습니다.
\\r 캐리지 리턴 문자를 찾습니다. \\x0d와 \\cM과 같습니다.
\\s 공백, 탭, 폼피드 등의 공백을 찾습니다. "[ \\f\\n\\r\\t\\v]"와 같습니다.
\\S 공백이 아닌 문자를 찾습니다. "[^ \\f\\n\\r\\t\\v]"와 같습니다.
\\t 탭 문자를 찾습니다. \\x09와 \\cI와 같습니다.
\\v 수직 탭 문자를 찾습니다. \\x0b와 \\cK와 같습니다.
\\w 밑줄을 포함한 모든 단어 문자를 찾습니다. "[A-Za-z0-9_]"와 같습니다.
\\W 모든 비단어 문자를 찾습니다. "[^A-Za-z0-9_]"와 같습니다.
\\xn n을 찾습니다. 여기서 n은 16진수 이스케이프 값입니다. 16진수 이스케이프 값은 정확히 두 자리여야 합니다. 예를 들어, '\\x41'은 "A"를 찾고 '\\x041'은 '\\x04'와 "1"과 같습니다. 정규식에서 ASCII 코드를 사용할 수 있습니다.
\\num num을 찾습니다. 여기서 num은 양의 정수입니다. 캡처한 문자열에 대한 역참조입니다. 예를 들어, '(.)\\1'은 연속적으로 나오는 동일한 문자 두 개를 찾습니다.
\\n 8진수 이스케이프 값이나 역참조를 나타냅니다. \\n 앞에 최소한 n개의 캡처된 부분식이 나왔다면 n은 역참조입니다. 그렇지 않은 경우 n이 0에서 7 사이의 8진수이면 n은 8진수 이스케이프 값입니다.
\\nm 8진수 이스케이프 값이나 역참조를 나타냅니다. \\nm 앞에 최소한 nm개의 캡처된 부분식이 나왔다면 nm은 역참조입니다. \\nm 앞에 최소한 n개의 캡처가 나왔다면 n은 역참조이고 뒤에는 리터럴 m이 옵니다. 이 두 경우가 아닐 때 n과 m이 0에서 7 사이의 8진수이면 \\nm은 8진수 이스케이프 값 nm을 찾습니다.
\\nml n이 0에서 3 사이의 8진수이고 ml이 0에서 7 사이의 8진수면 8진수 이스케이프 값 nml을 찾습니다.
\\un n은 4 자리의 16진수로 표현된 유니코드 문자입니다. 예를 들어, \\u00A9는 저작권 기호(©)를 찾습니다.

 

 

http://msdn.microsoft.com:80/scripting/default.htm

다음 정규식은 그 기능을 제공합니다. JScript의 경우는 다음과 같습니다.

/(\\w+):\\/\\/([^/:]+)(:\\d*)?([^# ]*)/

VBScript의 경우는 다음과 같습니다.

"(\\w+):\\/\\/([^/:]+)(:\\d*)?([^# ]*)"

괄호로 묶은 첫 번째 부분식은 웹 주소의 프로토콜 부분을 캡처하도록 설계되었습니다. 이 부분식은 콜론과 두 개의 슬래시 앞에 오는 단어를 모두 찾습니다. 괄호로 묶은 두 번째 부분식은 주소 중 도메인 주소 부분을 캡처합니다. 이 부분식은 '^', '/' 또는 ':' 문자를 포함하지 않는 문자 시퀀스를 찾습니다. 괄호로 묶은 세 번째 부분식은 웹 사이트 포트 번호가 지정되어 있으면 이를 캡처합니다. 이 부분식은 콜론 다음에 오는 0 이상의 자리 수를 찾습니다. 그리고 마지막으로 괄호로 묶은 네 번째 부분식은 웹 주소로 지정된 경로 및/또는 페이지 정보를 캡처합니다. 이 부분식은 '#' 또는 공백 문자를 제외한 하나 이상의 문자를 찾습니다.

정규식을 위의 URI에 적용하면 부분 검색 문자열에 다음이 포함됩니다.

RegExp.$1은 "http"를 포함합니다.

RegExp.$2는 "msdn.microsoft.com"을 포함합니다.

RegExp.$3은 ":80"을 포함합니다.

RegExp.$4는 "/scripting/default.htm"을 포함합니다.

 

'dev' 카테고리의 다른 글

ejb  (0) 2008.08.05
jms  (0) 2008.08.04
script class  (0) 2008.08.01
정규화  (0) 2008.08.01
jndi  (0) 2008.08.01
Posted by 으랏차
,

script class

dev 2008. 8. 1. 17:37

1. 프로퍼티(클래스필드) 정의하기.


class_name = function ( parameter, ... ) {

    ....

    property declaration...

    ...

}


또는


function class_name ( parameter, ... ) {

    ....

    property declaration...

    ...

}


function 이 함수를 의미하는 것이 아니라 여기서는 클래스 선언을 위해서 사용하는 키워드임. 단어 자체가 주는 사전적 의미에 함몰되어서 자꾸 딴지 걸면 안됨.(내가 ... 그랬었음..). 클래스나 함수나 어차피 프로세스로 존재할때 메모리를 차지하는 모듈로서 본다면 객체와 메소드 따위의 구분이 의미가 없다.


    public class Student {

        String name;

        int age;

        public Student(String name, int age) {

              this.name = name;

              this.age = age;

        }

    }


위와같은 자바 클래스에 대응하는 자바스크립트의 클래스는 다음과 같음.


    Student = function(name, age) {

        this.name = name;

        this.age = age;

    } // 초간단. -_-;


다음과 같이 인스턴스를 생성하고 사용할 수 있음.


Student student = new Student("전지현", 26);

document.write("이름 : " + student.name + ", 나이 : " + student.age );


2. 메소드 정의하기.


메소드도 정의할 수 있는데 두가지 방법이 있음. prototype에 정의하는 방법과 클래스 자체에 정의하는 방법.


2-1. prototype 프로퍼티에 메소드 정의


위에서 만든 Student 클래스에 getter/setter 메소드를 정의하면 다음과 같다.


Student.prototype.getName = function() {

    return this.name;

}

Studoent.prototype.setName = function (name) {

    this.name = name;

}


이제 다음은 동일한 결과를 보여준다.


student.name;| student.getName();

student.name = "왕지현"; | student.setName("왕지현");


위에서 주의할 점은 Student.prototype.getName() 이 아니라 그냥 getName임. 괄호 넣으면 작동하지 않음. -_-a


여기서 잠깐 prototype 에대해서 짚고 넘어가보자.


자바에서 일반적인 객체 상속구조를 떠올려보자.


        student.getAddress();


라고 호출하면 Student 클래스에서 아직 정의되어 있지 않기 때문에 부모 클래스에서 getAddress() 메소드가 있는지 찾는다. 존재 하지 않으면 그 부모클래스의 부모 클래스를 조회하고 마지막으로 Object 객체까지 거슬러 올라가서 최종적으로 찾지 못하면 예외가 발생한다.


javascript에서 정의되는 class는 모두 prototype이라는 프로퍼티를 가지는데 객체에 존재하지 않는 메소드나 클래스 필드를 호출하면 prototype 프로퍼티에서 메소드나 클래스 필드를 찾는다. prototype에서 찾지 못하면 prototype의 prototype 프로퍼티까지 계속해서 탐색을 하고 최종적으로 찾지 못하면


        "undefined"(클래스 필드인 경우) , ""(메소드인 경우)


를 출력한다. 위에서 메소드를 정의한 방식이 바로 이 prototype 프로퍼티에 메소드를 정의한 것인데 엄밀히 말하면 위에서 정의한 메소드는 Student 클래스의 것이 아닌, prototype 프로퍼티의 메소드인 셈이다. 하지만 사용하는 입장에서는 중요한 내용은 아닌 듯.


기억해야 할 점은 prototype 프로퍼티는 인스턴스당 할당되는 것이 아니라, 클래스당 하나씩 할당된다는 것. 마치 자바에서 Object.class, ArrayList.class 와 같은 Class 를 떠올리면 좋을듯 하다.


student.getEmail() 을 호출하면 정의되지 않은 메소드이므로 TypeError가 발생하고 진행중인 메소드가 종료된다. 아무것도 출력이 안되는 것처럼 보이지만 예외가 던져져서 진행중이던 메소드를 벗어나버리는 것임.


        Student jenny = new Student(....);

        try {

              jenny.getEmail(); // NO!!!

        } catch(e) {

              alert(e); // firefox에서는 예외가 던져짐.

         }


        Student.prototype.getEmail = function() { return googler@gmail.com; };

        jenny.getEmail() ; // OK!!!!!!!!!!!


위처럼 prototype에 메소드를 정의하면 메소드를 정의하기 이전에 생성되어 사용되던 인스턴스라도 getEmail() 메소드를 사용할 수 있게 된다. 왜냐하면 위에서 말했듯이 prototype은 클래스당 하나이므로 하나의 클래스에서 생성된 인스턴스들은 prototype을 공유한다.


이때문에 언제든지 메소드나 클래스 필드를 마음대로 만들어낼 수 있으니 조낸! 탄력적이다!!


2-2. 클래스 자체에 메소드 정의


prototype 이 아닌 class 자체에 메소드를 생성하는 방법도 있다.


    Student = function(name, age) {

        this.name = name;

        this.age = age;

        this.getName = function() { return this.name ; }

        this.setName = function(name) { this.name = name;}

    } // 초간단. -_-;


그렇다면 메소드를 prototype에 정의하는 것과 클래스 자체에 정의하는게 어떻게 다를까?


클래스 자체에 정의할 경우, 클래스의 인스턴스를 여러개 생성할 때 메소드 코드를 인스턴스들마다 따로 갖게 된다. 하지만 prototype에 메소드를 정의하면 동일한 클래스로부터 생성된 인스턴스들은 하나의 prototype 프로퍼티를 공유하므로 인스턴스들마다 중복해서 메소드 코드를 가질 필요가 없게된다.


따라서 자바스크립트에서 클래스를 생성할 때 가능하면 prototype에 메소드를 정의하는게 메모리를 아낄 수 있는 길이다...라고 일단 정리하게 넘어가겠다. (그런데 메소드를 클래스 자체에 정의해야 하는 상황도 있지 않을까? 잠깐 고민해봤는데, 아직은 없는 것 같다. 인스턴스의 상태는 프로퍼티에 좌우되기 때문에 메소드를 인스턴스마다 따로 가져야할 상황은 없는 듯.)


하지만 다음과 같이 오버하면 안된다.


    Student = function(name, age) {

        Student.prototype.name = name;

        Student.prototype.age = age;

    }


이렇게 하면 클래스의 인스턴스들이 모두 동일한 이름과 나이를 갖게 된다. 즉,


    Student jane = new Student("jane", 21);


을 생성한 후


    Student jack = new Student("jack", 34);


로 jack을 생성하면 jane의 이름이 "jack", 나이가 34살로 둔갑한다.(뜨아~).


정리하면 상황에 맞게 잘 사용해야 한다는 것이다.


2-3 효율적인 클래스 정의 방법


2-1 방식의 문제점은 클래스를 정의할 때 코드가 난잡해져서 가독성이 떨어진다는 점이다. 클래스를 여러개 정의하는 상황이라면 메소드를 정의한 블록들이 산재해 있어서 클래스의 모습이 눈에 딱 들어오지는 않는다.


반면에 2-2는 코드가 깔끔해져서 가독성이 높아지지만 문제는 위에서 말했듯이 인스턴스들이 중복된 메소드 코드를 갖는다는 점이다.(간단한 클래스라면 상관없겠지만...)


그래서 생각해낸 방법은 다음과 같다.


    function Student(name, age)

    {

        var strName = name;

        var intAge = age;


        Student.prototype.getName() { return this.strName; }

        Student.prototype.setName(name) { this.strName = name; }

        Student.prototype.getAge() { return this.strAge; }

        Student.prototype.setAge(age) { this.intAge = age; }

    }


메소드 정의부를 클래스 내로 옮기면서


    fucntion class_name ( parameter, ... )

    {

        class_name.prototype.method_name ( parameter, ... ) { ... };

        .....

    }


로 바꿔주는 것. IE와 FF 에서 테스트해봤는데 아무 문제없이 잘 돌아간다. 흐화화~

   


3. JSON 표기법을 이용한 클래스 정의


아, 이런것도 있다. JSON에 대한 자세한 내용은 http://www.json.org/ 에서 보면 될 것 같다.


위에서 메소드 정의하는 부분을 아래의 코드로 표현했다.


    Student.prototype.getName = function() {

        return this.name;

    }

    Studoent.prototype.setName = function (name) {

        this.name = name;

    }


위의 코드를 JSON 을 이용해서 나타내면 다음과 같다.


    Student.prototype = {

        getName : function() {

            return this.name;

        }

        setName : function (name) {

            this.name = name;

        }

    }


4. 정보은닉(encapsulation)


위에서 설명한 클래스 정의 방법은 정보은닉이 안된다는 문제점이 있다. student.name 으로 프로퍼티에 접속이 가능한데 OOP 에서는 이런 접근을 꺼리기 때문에 자바스크립트에서도 이걸 흉내내려는 시도가 있지 않았나 추측해본다.


여자들의 몸무게 평균을 내는 저울이 있다고 치자. 몇 명의 여성의 정보를 가져와서 전체 몸무게을 내는데 여자들은 자신의 몸무게가 드러나는 것을 반대한다. 저울을 통해서 특정 여성의 몸무게를 알아서는 안되는 경우를 생각해보자.


이런 상황을 모델링하면 다음과 같다.


    function Scale (  ) {

        this.womans = [new Woman(...), new Woman(...), ...];

        this.prototype.getTotalWeights() {

            var totalWeight = 0;

            for( i = 0 ; i < womans.length ; i++) {

                    totlaWeight += this.womans[i].getWeight();

            }

            return totalWeight;

        }

    }


.....


    Scale scale = new Scale() ;

    scale.getTotalWeight();


위코드에서는


        scale.womans[0].getWeight();


로 특정 여성의 몸무게에 접근할 수 있다.


정보은닉이란 외부에 노출되어서는 안되는 데이터를 꼭꼭 감추는 것을 의미하는데 여기서 여성들의 몸무게 노출을 막기 위해서는 다음과 같이 프로퍼티 선언을 변경해준다.


    function Scale (  ) {

        var womans = [new Woman(....), new Woman(....), new Woman(...)];

        this.prototype.getTotalWeights() {

            var totalWeight = 0;

            for( i = 0 ; i < womans.length ; i++) {

                    totlaWeight += womans[i].getWeight();

            }

            return totalWeight;

        }

    }


이제 scale.womans 로 접근하면 "undefined"가 출력되기 때문에 여성들 개개인의 몸무게를 보여주는 메소드 호출을 할 수 없다.


5. 정리하면.


자바스크립트에서 클래스를 정의해서 사용하는게 불필요하고 이상해 보일 수도 있다. 왜냐하면 지금까지 이렇게 하지 않아도 자바스크립트를 잘만 써왔기 때문이다. 이런 편견은 예전에 자바스크립트가 화면을 동적으로 구성하는 도구로서 사용되어왔기 때문에 생긴 것이 아닌가 싶다. 또한 언어가 매우 탄력성이 높아서 초기에 자바스크립트를 어떠한 체계에 맞춰서 사용할지 뚜렷한 가이드라인이 없었기 때문이기도 하다.


하지만 ajax의 도입으로 자바스크립트가 데이터 처리를 위한 수단으로서 많이 사용되고 있다. 서버쪽에서는 데이터 처리만 해서 클라이언트에 전달해주고 클라이언트는 ajax 를 이용해서 데이터를 2차 가공한 후 display하는 ui 과정을 모두 떠안게 되는 것이다.


이러한 변화에 대응하기 위해서 자바스크립트도 class 를 도입해서 좀 더 체계적으로 코드를 만들어어지 향후 유지 보수하는데 어려움이 없을 것으로 생각된다. 예전에는 자바스크립트를 아주 우스운 언어, 웹 디자이너들이나 다루는 수준낮은 언어로 생각해왔지만 이제는 생각을 바꿀 때가 된 것 같다.


OOP의 개념을 자바스크립트에 이식해서 언어의 질을 한층 높일 때가 되지 않았나 싶다.

'dev' 카테고리의 다른 글

jms  (0) 2008.08.04
expression pattern  (0) 2008.08.01
정규화  (0) 2008.08.01
jndi  (0) 2008.08.01
ant  (0) 2008.07.25
Posted by 으랏차
,

정규화

dev 2008. 8. 1. 17:36

데이터 모델 정규화/반정규화의 실전 프로젝트 적용


정규화를 잘 이해하여 데이터 모델링을 해야 하는 프로젝트 모델러가 이를 정확하게 이해하지 못하는 경우가 종종 있다. 검증되어 있고 체계화된 이론적 기반 위에 데이터베이스라는 기초를 건축하지 않으면 그 데이터베이스는 모래 위에 세운 집처럼 금방 무너지고 말 것이다. 정규화의 이론은 건축물의 기초공사를 해야 하는 사상에 해당한다. 그저 어렴풋이, 알듯 모를듯 희미한 기억의 지식으로 튼튼하고 견고한 데이터 모델을 만들어 낼 수 없다.


“붕어빵에 붕어가 없다!”고 한다. 데이터 모델링을 학교나 학원에서 배운 사람이나 시스템 구축 프로젝트에서 데이터 모델링을 경험한 사람치고 정규화에 대한 이야기를 듣거나 이야기하지 않은 사람은 없을 것이다. 그만큼 정규화의 이론은 데이터를 분석하여 데이터 모델로 만들고 그것을 다시 데이터베이스화하는 이론의 뿌리가 되는 중요한 것이다. 그러나 붕어빵에 붕어가 없듯이 정규화에 대한 언급은 누구나 하지만 정규화에 대한 내용을 정확하게 이해하고 실전에 적용할 수 있는 사람은 의외로 극히 드물다는 사실을 즉시해야 하고 사태의 심각성을 인식할 필요가 있다.


정규화에 관련된 이론을 배운다고 하면 대부분 과목, 수강신청, 교수 등 항상 정해진 샘플 사례에 표시 방법도 과목코드->과목명과 같이 설명되어 실전 프로젝트에서 사용하는 표기법(notation)과 동 떨어져 있다. 따라서 학습할 때는 개념적으로 이해한다고 하더라도 혹은 적어도 시험문제가 출제되면 100점은 맞는다고 하더라도, 실전 프로젝트에서는 무엇을 어떻게 왜 그렇게 해야 하는지 도무지 이해하지 못하는 경우가 대부분이다. 잘 정리된 이론은 실세계에서 응용되어 다른 창조물을 도출할 수 있을 때 비로소 지식가치의 효용이 있다. 하지만 불행히도 정규화의 이론은 그 내용이 너무 훌륭함에도 불구하고 실전에 반영하는 방법을 정확하게 알지 못해 그의 가치를 제대로 적용하지 못하는 경우가 자주 나타나는 것이다. 이제 우리의 눈을 희미하게 하는 이론의 틀을 깨고 개념을 명확하게 하여 실전에서 곧 바로 적용할 수 있는 참된 지식가치로 정규화의 이론을 활용해 보자.


정규화 규칙은 어디에 쓰는 물건인가?

“그런데 실전 프로젝트에서는 정규화를 적용한 적이 없습니다!”라고 반문하는 독자도 있을 것이다. 맞는 이야기이다. 프로젝트에서는 정규화라고 하는 태스크(task)로 일을 진행하지는 않는다. 다만, 프로젝트에서 데이터 모델링을 할 때는 논리 모델->물리 모델 2단계로 수행하거나 개념 모델->논리 모델->물리 모델 3단계 또는 객체지향 분석설계에서는 클래스 다이어그램->OR(객체-관계형) 맵핑->물리 모델로 하거나 업무가 익숙하고 시스템의 규모가 작은 경우 곧 바로 물리적인 데이터 모델링을 하는 경우로 진행한다. “그렇다면 데이터베이스에서 그렇게 중요하다고 하는 정규화 방법은 활용되지 않는가?”라고 반문할 수 있다.

정규화 규칙은 실제 프로젝트에서 두 가지 성격으로 중요하게 반영이 된다. 

첫 번째는 엔티티 타입을 오브젝트 분석 방법에 의해 도출할지라도 분석 방법의 배경에는 이미 중복 제거 및 주식별자에 의한 종속과 속성에 의한 종속 등 제3정규화 규칙이 모델링 작업의 기초에 관여한다고 봐도 된다. 즉 숙련된 데이터 모델러는 이미 정규화에 대한 개념이 확보된 상태에서 각각의 오브젝트를 엔티티 타입으로 선정하며 새로운 엔티티 타입으로 분리될 때도 각 속성의 집합 개념과 종속성의 개념을 적용하여 분리시켜 나간다.


두 번째는 정규화 방법을 프로젝트에서 적절하게 활용하기 위해서는 오브젝트별로 엔티티 타입을 분석해가면서 각각의 오브젝트가 적절하게 도출이 되었는지 또는 더 분리되어야 해야 하는지를 정규화 규칙에 대입하며 검증하는 것이다. 또한 단계별로 작업이 수행된 이후에 정규화 규칙에 의해 모든 엔티티 타입에 대해서 검증하는 작업이 필요하고 이상이 있는 경우에는 정규화 규칙을 적용하여 엔티티 타입을 정제해 나가도록 한다.


정규화의 의미

그러면, 데이터 모델링에서 정규화는 무엇을 의미하는가? 1970년 6월 E.F Code 박사는 ‘대규모 데이터 저장을 위한 관계형 데이터 모델(A Relational Model of Data for Large Shared Databanks)’이라는 연구에서 새로운 관계형 모델을 발표했다. 수학자인 Code 박사에 의해 제안된 정규화의 이론은 실세계에서 발생하는 데이터들을 수학적인 방법에 의해 구조화시켜 체계적으로 데이터를 관리할 수 있도록 하였다. 처음에는 1차 정규화, 2차 정규화, 3차 정규화가 제시되었으나 이후에 보이스-코드 정규화가 제시되었고, 이후 4차 정규화, 5차 정규화의 이론이 발표되었다.


정규화(normalization)란 다양한 유형의 데이터 값 검사를 통해 데이터 모델을 더 구조화시키고 개선시켜 나가는 절차에 관련된 이론이다. 정규화가 프로세스를 나타내는 의미라면 정규형(normalform)은 정규화가 완성된 이후의 엔티티 타입(테이블)을 지칭하는 용어이다. 정규화를 이해하기 위해서는 이론적인 기반이 되는 함수 종속성을 이해할 필요가 있다. 함수의 종속성(functional dependency)은 데이터들이 어떤 기준 값에 의해 종속되는 현상을 지칭하는 것이다. 이 때 기준 값을 결정자(determinant)라 하고 종속되는 값을 종속자/의존자(dependent)라고 한다.


<그림 1> 함수의 종속성

<그림 1>을 보면 사람이라는 엔티티 타입에는 주민등록번호, 이름, 출생지, 호주라는 속성이 존재한다. 여기에서 이름, 출생지, 호주라는 속성은 주민등록번호 속성에 종속된다. 만약 어떤 사람의 주민등록번호가 신고되면 그 사람의 이름, 출생지, 호주가 생성되어 단지 하나의 값만을 가지게 된다. 이를 기호로 표시하면 다음과 같다.

주민등록번호 -> (이름, 출생지, 호주)

즉 ‘주민등록번호가 이름, 출생지, 호주를 함수적으로 결정한다’라고 말할 수 있다. 실세계의 데이터들은 대부분 이러한 함수 종속성을 가지고 있다. 함수의 종속성은 데이터가 가지고 있는 근본적인 속성으로 인식되고 있다. 정규화의 궁극적인 목적은 반복적인 데이터를 분리하고 각 데이터가 종속된 테이블에 적절하게(프로세스에 의해 데이터의 정합성이 지켜질 수 있어야 함) 배치되도록 하는 것이므로 이 함수의 종속성을 이용하여 정규화 작업이나 각 오브젝트에 속성을 배치하는 작업을 한다.

• 정규화는 적절한 엔티티 타입에 각각의 속성들을 배치하고 엔티티 타입을 충분히 도출해가는 단계적인 분석 방법이다.

• 정규화 기술은 엔티티 타입에 속성들이 상호 종속적인 관계를 갖는 것을 배경으로 종속 관계를 이용하여 엔티티 타입을 정제하는 방법이다.

• 각각의 속성들이 데이터 모델에 포함될 수 있는 정규화의 원리를 이용하여 데이터를 분석하는 방법에서 활용될 수 있다.

• 정규화는 현재 데이터를 검증할 수 있고 엔티티 타입을 데이터가 표현하는 관점에서 정의하는데 이용할 수 있다.

• 정규화는 엔티티 타입을 분석하는 관점이 오브젝트별 분석하는 방법이 아닌 개별 데이터를 이용한 수학적인 접근방법을 통해 분석하는 방법이다.

정규화에 대한 실전 프로젝트 적용 사례

<표 1>은 1차 정규화, 2차 정규화, 3차 정규화와 보이스-코드정규화 그리고 4차와 5차 정규화에 대한 정리이다. 정규화의 정의를 이용하여 실전 프로젝트에서는 어떻게 적용할 수 있는지 살펴보자.


<표 1> 정규화에 대한 정리
정규화 정규화 내용
1차 정규화 복수의 속성 값을 갖는 속성을 분리
2차 정규화 주식별자에 종속적이지 않은 속성의 분리
부분 종속 속성을 분리
3차 정규화 속성에 종속적인 속성의 분리
이전 종속 속성의 분리
보이스-코드 정규화 다수의 주식별자 분리
4차 정규화 다가 종속 속성 분리
5차 정규화 결합 종속일 경우는 두 개 이상의 N개로 분리

1차 정규화(복수의 속성 값을 갖는 속성의 분리)

1차 정규화(first normalization)는 복수의 속성 값을 가진 속성을 분리한다. 즉 테이블 하나의 컬럼에는 여러 개의 데이터 값이 중복되어 나타나지 않아야 한다는 것이다. 이는 각 속성에 값이 반복 집단이 없는 원자 값(atomic value)으로만 구성되어 있어야 한다는 것을 의미한다. 이를 다시 정의하면, “모든 엔티티 타입의 속성에는 하나의 속성 값만을 가지고 있어야 하며 반복되는 속성 값의 집단은 별도의 엔티티 타입으로 분리한다”로 정의할 수 있다. 이 때 전제조건은 결정자에 의존하는 의존자의 반복성을 나타낸다. 실전 프로젝트에서 나타나는 데이터 모델의 표기법을 이용한 사례를 보도록 하자.


1차 정규화 사례 1
‘한 번의 주문에 여러 개의 제품을 주문한다’는 업무 규칙이 있는데 <그림 2>의 왼쪽 편과 같이 데이터 모델링을 했다고 가정해 보자. 왼쪽의 엔티티 타입은 하나의 주문에 여러 개의 제품이 존재하므로 주문번호, 주문일자, 배송요청일자의 동일한 속성 값이 주문한 제품의 수만큼 반복해서 저장될 것이다. 따라서 오른쪽과 같이 1차 정규화를 적용하여 중복속성 값을 제거한다.


<그림 2> 1차 정규화의 응용 1


이 사례의 특징은 주문의 PK(Primary Key)인 주문번호가 중복 속성 값을 가지기 때문에 PK를 가진 데이터베이스 테이블 생성이 불가능하다는 특징이 있다.


1차 정규화 사례 2
로우(Row) 단위로 1차 정규화가 안 된 모델은 PK의 유일성이 확보되지 않으므로 인해 실전 프로젝트에서는 거의 찾아보기가 힘들다. 반면 로우 단위로 중복된 내용을 컬럼 단위로 펼쳐 중복하는 경우가 아주 많이 발견된다. 1차 정규화의 응용이 된 형태로 볼 수 있다. 계층형 데이터베이스에서 이와 같은 형식의 모델링을 많이 했는데 관계형 데이터베이스에서도 이러한 형식으로 모델링을 진행하는 경우가 많이 발견된다.


<그림 3> 1차 정규화의 응용 2


<그림 3>의 모델을 보면 왼쪽 모델의 일재고 엔티티 타입에는 3개월 분에 대한 장기재고 수량, 주문수량, 금액, 주문금액이 차례대로 기술되어 있다. 이렇게 되면 장기재고 관리가 4개월 이상으로 늘어날 때 모델을 변경해야 하는 치명적이 결함이 있다. 따라서 오른쪽과 같이 1차 정규화를 통해 모델을 분리함으로써 업무 변형에 따른 데이터 모델의 확장성을 확보하도록 해야 한다.


2차 정규화(주식별자에 종속적이지 않은 속성의 분리)

1차 정규화를 진행했지만 속성 중에 주식별자에 종속적이지 않고 주식별자를 구성하는 속성의 일부에 종속적인 속성인, 부분종속 속성(PARTIAL DEPENDENCY ATTRIBUTE) 을 분리하는 것이 2차 정규화(SECOND NORMALIZATION)이다. 2차 정규화는 반드시 자신의 테이블을 주식별자를 구성하는 속성이 복합 식별자일 경우에만 대상이 되고 단일 식별자일 경우에는 2차 정규화 대상이 아니다.


2차 정규화 사례
여러 개의 속성이 주식별자로 구성되어 있을 때 일반속성 중에서 주식별자에 일부에만 종속적인 속성이 있을 경우 2차 정규화를 적용하여 엔티티 타입을 분리하도록 한다.


<그림 4> 2차 정규화 응용


<그림 4>의 모델은 고객번호에 종속적이지 않은 속성들을 분리하여 고객점포라는 새로운 엔티티 타입을 생성하였다. 실전 프로젝트에서는 코드 유형의 엔티티 타입들이 2차 정규화가 되지 않고 하나의 엔티티 타입으로 표현되는 경우가 많이 발견된다. 이 모델에서 함수종속 관계 표기법으로 표기하자면 고객번호 -> (고객명)으로 표시하여 별도의 엔티티 타입으로 분리할 수 있다.


3차 정규화(속성에 종속적인 속성 분리)

3차 정규화(third normalization)는 속성에 종속적인 속성을 분리하는 것이다. 즉 1차 정규화나 2차 정규화를 통해 분리된 테이블에서 속성 중 주식별자에 의해 종속적인 속성 중에서 다시 속성 간에 종속 관계가 발생되는 경우에 3차 정규화를 진행한다. 3차 정규화의 대상이 되는 속성들을 이전 종속(transitive dependence) 관계 속성이라고 한다. 이것은 곧 주식별자에 의해 종속적인 속성 중에서 다시 다른 속성을 결정하는 결정자가 존재하여 다른 속성이 이 결정자 속성에 종속적인 관계를 나타낸다.


3차 정규화 실전 적용
결정자 역할을 하는 일반 속성이 존재하고, 결정자 역할 속성에 의존하는 의존자가 존재하는 엔티티 타입은 3차 정규화의 대상이 된다.


<그림 5> 3차 정규화 응용


<그림 5>의 모델은 고객 엔티티 타입에 등록카드에 대한 정보가 포함되어 있는 모습이다. 등록카드번호가 결정자 역할을 하고 있고 등록카드사명과 등록카드유효일자가 의존자 역할을 하는 속성 간의 종속적인 속성이 발견되었으므로 3차 정규화의 대상이 되는 모델이다. 따라서 등록카드에 대한 내용에 대해 별도의 엔티티 타입을 도출한 오른쪽 모델로 만듦으로서 3차 정규화를 완성하였다. 실전 프로젝트에서는 1:1관계의 엔티티 타입이 하나로 통합이 되었거나 업무분석 과정에서 하나의 엔티티 타입에 많은 속성이 포함되어 있을 때 3차 정규화의 대상이 되는 경우가 많이 나타난다. 이 모델에서 함수종속 관계 표기법으로 표기하자면 등록카드번호 -> (등록카드사명, 등록카드유효일자)으로 표시하여 별도의 엔티티 타입으로 분리할 수 있다.


보이스-코드 정규화

1차 정규화, 2차 정규화, 3차 정규화는 모두 하나의 주식별자를 가졌을 때를 가정하여 진행하였다. 만약 하나의 테이블에 여러 개의 식별자가 존재하면 비록 1, 2, 3 정규형을 모두 만족하더라도 데이터를 조작하는 데 문제가 발생될 수 있다. 복잡한 식별자 관계에 의해 발생되는 문제를 해결하기 위해 3차 정규화를 보완한 보이스-코드 정규화(boyce-code normalization)를 진행한다.
보이스-코드 정규화란 테이블에 존재하는 식별자가 여러 개 존재할 경우 식별자가 중복되어 나타나는 현상을 제거하기 위해 정규화 작업을 진행한다.


BCNF 실전 적용
납품 엔티티 타입의 주식별자는 부품번호, 부품이름, 납품번호 세 개의 속성의 구성이 되어 있고 세 개의 속성을 구성한 주식별자는 납품수량, 납품단가에 대해 결정자 역할을 한다. 그런데 부품번호+납품번호 만으로도 납품수량, 납품단가에 대해 결정자 역할을 할 수도 있고 부품이름+납품번호 만으로도 납품수량, 납품단가에 대해 결정자 역할을 할 수도 있다. 또한 부품번호와 부품이름은 상호간에 결정자역할을 하는 특성을 가지고 있다. 이러한 성격을 이용하여 데이터 모델에서는 최소의 속성의 조합이 주식별자를 갖게 하도록 BCNF(Boyce Codd Normal Form)를 적용한다. 즉, 부품번호를 주식별자로 하여 하여 부품을 구성하거나 부품이름을 주식별자로 하여 부품 엔티티 타입을 분리하여 납품과 관계를 갖게 하는 형식으로 정규화를 진행하는 방식이 바로 보이스-코드 정규화 방법이 된다.


<그림 6> BCNF 정규화의 응용


개념적 설명은 무척 까다롭지만 실전 사례를 통해서는 쉽게 이해되는 부분이다. 다시 한 번 정리하면, 주식별자 속성 중에 주식별자의 유일성을 확보하는 최소한의 속성이 아닌 쓸데없이 추가된 속성을 분리하는 것이 보이스-코드 정규화라고 할 수 있다. 또한 주식별자 속성 중에 상호간의 함수종속 관계를 가지는 것을 분리한다. <그림 6>의 부품번호와 부품이름 사례처럼 단독으로 주식별자에 참여할 수 있으면서 상호간의 종속 관계가 있는 코드, 코드명을 생각하면 쉽게 이해될 수 있다. 주식별자 속성이 많아질수록 보이스-코드 정규화의 대상이 되는 경우가 나타나므로 개념을 잘 정리하여 실전에서 데이터 모델을 검증할 수 있도록 해야 한다.


4차 정규화(특정 속성 값에 따라 선택적인 속성의 분리)

보이스-코드 정규화까지 정규화 작업을 진행하면 함수의 종속성에 관한 작업은 모두 정리가 되었다. 이제 더 이상 속성 사이의 종속적인 관계로 인해 발생하는 정규화 작업은 필요하지 않게 되는 것이다. 그러나 하나의 테이블에 두 개 이상의 독립적인 다가속성(multi-valued attribute)이 존재하는 경우에 다가종속(multi-valued dependency)이 발생되어 문제가 생긴다. 다가종속이라는 단어를 해석하면, 하나의 속성 값에 두 개의 이상의 의미를 가지는 값을 가지는 것을 의미한다. 4차 정규화의 대상이 되는 경우는 실제 프로젝트에서는 독립적인 엔티티 타입을 설계할 때 발생하기 보다는 동시에 여러 개의 엔티티 타입과의 관계에서 발생되는 경우가 많이 있다.


4차 정규화의 실전 적용
<그림 7>과 같은 업무 규칙이 있다. ‘한 명의 사원은 여러 개의 프로젝트를 지원할 수 있다’ 그리고 ‘한 명의 사원은 여러 개의 기술을 보유할 수 있다’ 즉 사원과 프로젝트, 사원과 기술 간의 업무적인 관계의 규칙이 있는 경우이다. 이 업무 규칙은 보유하는 기술이 있다는 사실을 관리하고 보유한 기술은 지원한 프로젝트와는 아무런 상관이 없다는 것이 특징이다. 그럼에도 불구하고 <그림 7>의 왼쪽처럼 사원과 프로젝트와 기술 간의 관계를 모두 연결하면 4차 정규화의 규칙을 위배하여 어떤 사원이 새로운 기술을 습득하여 사원내역 엔티티 타입에 등록하려고 하면 마치 금방 습득한 기술을 가지고 어떤 프로젝트를 지원한 것처럼 값을 채워줘야만 하는 현상에 빠지게 된다. 따라서 필요하지 않은 조인 관계를 해소하기 위해 오른쪽 모델과 같이 업무 규칙에 적합하게 관계를 분리하는 방법이 4차 정규화이다.


<그림 7> 4차 정규화의 응용


4차 정규화가 실전 프로젝트에서 거의 나타나지 않는다고 하는 사람들이 많은데 필자가 파악하기로는 2차 정규화나 BCNF보다 더 많이 발생된다. 단, 4차 정규화를 하지 않고 개발을 하다가 새로운 값을 채울 경우에 값을 기본 값(default value)으로 지정해버리는 경우가 많이 있다. 참조무결성 제약조건(FK)를 데이터베이스 테이블에 걸지 않는 경우에 가능한데 구축단계 때 많은 프로젝트에서 이와 같은 편법으로 프로그램을 작성한다. 좋지 않은 경우이다. 이와 같은 경우 데이터모델에 나타난 관계가 실제 데이터에서 불가피하게 단절되어 나타나므로 무결성 체크가 불가능해진다. 설계단계 때 불필요한 관계에 의해 나타나는 4차 정규화의 대상 엔티티 타입을 검증하여 정규화를 적용하도록 해야 한다.


반정규화

논리적인 데이터 모델링 단계에서는 모든 엔티티 타입과 속성들을 정규화 규칙에 적절하게 분석하여 데이터 모델링을 수행한다. 이 단계는 실전 프로젝트에서는 분석단계 때 수행하는 경우가 많고 설계단계 때는 데이터베이스 성능을 고려하여 물리적인 데이터 모델링을 수행하는데 물리적인 데이터 모델링의 여러 개의 타스크 중에 반정규화를 수행하게 된다. 반정규화라고 하면, 일반적으로 다른 엔티티 타입에 있는 속성을 중복한 것만을 생각하는 경우가 많이 있다. 훨씬 많은 반정규화 유형이 있고 각각은 유용하게 활용될 수 있음을 알 수 있다.


반정규화란 정규화된 엔티티 타입, 속성, 관계에 대해 시스템의 성능향상과 개발(development)과 운영(maintenance)의 단순화를 위해 데이터모델을 조정하는 프로세스를 의미한다. 단순하게 정규화 규칙에 반대되는 개념으로만 생각한다면 속성의 중복 정도가 반정규화의 범위에 해당되지만 물리적인 성능을 고려한 반정규화의 개념으로 생각한다면 테이블 통합/분리, 속성 중복, 속성 추가, 관계 중복 등이 반정규화의 범위에 해당된다.


반정규화를 적용하기 전에 반드시 중요하게 고려해야 할 점은 데이터의 무결성을 유지시킬 수 있는 방안을 마련하고 반정규화를 적용해야 한다는 것이다. 시스템을 개발할 때는 성공적인 오픈을 위해 성능을 중요하게 여겨 여러 테이블에 속성들을 반정규화하는 경우가 많은데 반정규화를 많이 할수록 데이터의 무결성은 깨져 이상한 데이터가 많이 남아있거나 돈의 액수가 맞지 않거나 등록된 접수 건수가 맞지 않은 현상이 시스템을 운영하는 중에 점점 많이 발생하게 되어 나중에는 시스템을 사용하지 못하게 되는 경우가 발생된다. 데이터 무결성을 중요하게 생각하고 반정규화를 적용할 필요가 있다.


반정규화에 대한 실전 프로젝트 적용 사례

반정규화를 하는 대상으로는 테이블, 속성, 관계에 대해 적용할 수 있으며 꼭 테이블과 속성, 관계에 대해 중복으로 가져가는 방법만이 반정규화가 아니고 테이블, 속성, 관계를 추가하거나 분할할 수 있으며 제거할 수도 있다.


1차 정규화에 대한 반정규화

고객에 대한 엔티티 타입에 방문을 두 번까지 가능하다고 할 때 고객번호, 고객명이 중복 속성 값을 갖기 때문에 1차 정규화의 대상이 되어 중간에 있는 고객방문 엔티티 타입으로 1차 정규화가 되었다. 그러나 최대 2회까지 방문이 가능하다는 업무 규칙을 이용하여 성능과 단순성을 고려하여 오른쪽에 있는 1차 정규화에 대한 반정규화 엔티티 타입으로 설계된 예이다.


<그림 8> 1차 반정규화의 응용


최대 발생하는 값을 이용한 이와 같은 반정규화의 유형은 실전 프로젝트에서 빈번하게 사용되지만 최대 발생 값을 변할 수 있는 경우는 정규화된 모습으로 모델링해야 확장성(flexible)이 보장된다는 것을 기억해야 한다.


2차 정규화에 대한 반정규화

주식별자가 두 개 이상일 때 일부 주식별자 속성에 의존적인 속성을 분리하는 2차 정규화에서 조인에 의한 성능저하와 단순성 확보를 위해 반정규화를 적용할 수 있다.


<그림 9> 2차 반 정규화의 모델


<그림 9>의 모델은 일자별 매각 물건 엔티티 타입에서 매각 일자가 결정자가 되고 매각 장소와 매각 시간이 의존자가 된 함수 종속성이 존재하여 2차 정규화를 적용했다가 다시 조인에 의한 성능저하 예방과 단순성을 위해 다시 일자별매각물건이라는 엔티티 타입에 반정규화를 한 경우이다.


3차 정규화에 대한 반정규화

<그림 10>의 모델을 보면 수납이라고 하는 엔티티 타입은 속성간의 결정자(수납확인번호)와 의존자가 존재하는 3차 정규화의 대상이 되는 모습이다. 따라서 수납확인번호를 결정자로 하고 수납확인방법, 수납확인일자, 수납확인자사번을 속성으로 하는 3차 정규화를 적용하였다.


<그림 10> 3차 반정규화의 응용


반정규화를 하는 대상으로는 테이블, 속성, 관계에 대해 적용할 수 있으며 꼭 테이블과 속성, 관계에 대해 중복으로 가져가는 방법만이 반정규화가 아니고 테이블, 속성, 관계를 추가할 수도 있고 분할할 수도 있으며 제거할 수도 있다. 정규화에 위배되는 것은 아니지만 성능을 위해 적용하는 반정규화의 방법 테이블 통합/분리, 속성 중복, 관계 중복 등 여러 가지가 있을 수 있다.


이력의 최근 변경 속성 값 반정규화

<그림 11>의 모델은 공급자에 대한 전화번호, 메일주소, 위치 등에 대한 변경 정보를 각각 관리하는 현재 데이터와 이력 데이터에 대한 데이터 모델이다. 모든 속성 값이 중복이 없어 완벽히 정규화된 모습이지만 이력 모델이 정규화되어 있음으로 인해 최근 값을 처리하는 데 상당한 시간이 소요되고 SQL 구문도 복잡하게 된다. 따라서 데이터를 조회할 때는 프로세스의 대부분은 가장 최근 값을 참조한다는 성격을 이용하여 오른쪽과 같이 최근 값에 대한 속성 값만을 관리하기 위해 공급자 엔티티 타입에 전화번호, 메일주소, 위치에 대한 속성을 추가하였다.


<그림 11> 최근 변경 값 속성의 반정규화


<그림 11>에서 공급자번호 1001~1005에 해당하는 공급자번호, 공급자명, 전화번호, 메일주소, 위치에 대한 정보를 조회하면 다음과 같이 작성된다.

SELECT A.공급자명, B.전화번호, C.메일주소, D.위치 FROM 공급자 A, (SELECT X.공급자번호, X.전화번호 FROM 전화번호 X, (SELECT 공급자번호, MAX(순번) 순번 FROM 전화번호 WHERE 공급자번호 BETWEEN '1001' AND '1005' GROUP BY 공급자번호) Y WHERE X.공급자번호 = Y.공급자번호 … WHERE A.공급자번호 = B.공급자번호 AND A.공급자번호 = C.공급자번호 AND A.공급자번호 = D.공급자번호 AND A.공급자번호 BETWEEN '1001' AND '1005' SELECT 공급자명, 전화번호, 메일주소, 위치 FROM 공급자 WHERE 공급자번호 BETWEEN '1001' AND '1005' 정규화된 모델에서 SQL반정규화된 모델에서 SQL 반정규화

적절한 반정규화를 통해 성능도 훨씬 향상되었을 뿐만 아니라 SQL 구문도 비교가 안될 만큼 단순해졌음을 알 수 있다.


관계 반정규화

속성의 반정규화에서 데이터를 조회하는 경로를 단축하기 위해 일반속성(주식별자가 아닌)을 중복할 수도 있고 주식별자 속성을 중복할 수도 있다. 주식별자 속성의 중복 중 전체 주식별자를 이루는 전체 속성의 중복은 곧 관계의 중복을 의미한다. 관계의 반정규화는 인위적인 속성의 중복 없이 조회경로 단축을 통해 조인에 의한 데이터 처리 속도를 향상시키는 장점이 있다.


<그림 12>의 왼쪽은 고객, 주문, 주문목록, 배송 엔티티 타입이 정규화가 잘 되어 있고 관계도 업무 규칙에 따라 식별자 관계/비식별자 관계로 적절하게 설정되어 있다. 그런데 배송 엔티티 타입에 발생되는 프로세스가 데이터를 처리할 때 항상 고객에 있는 속성의 모든 정보를 참조해야 하는데 왼쪽 정규화된 모델에서는 항상 주문목록과 주문을 경유하여 고객정보를 처리함으로 조인에 의한 성능저하가 예상된다. 따라서 조회경로를 단축하기 위해 오른쪽과 같이 관계를 추가로 연결하여, 즉, 이미 고객->주문->주문목록->배송으로 관계는 연결되어 있지만 성능을 위해 고객->주문으로 직접 관계를 연결한 관계의 반정규화를 적용한 사례이다.

<그림 12> 관계의 반정규화


<그림 12>의 데이터 모델에 왼쪽에 있는 데이터 모델에 대해 배송일시와 고객번호, 고객명을 가져오는 SQL 문장을 작성하면 다음과 같이 작성될 수 있다.



SELECT D.고객번호, D.고객명, A.일시
  FROM 배송 A, 주문목록 B, 주문 C, 고객 D
  WHERE A.배송번호 = ‘20031001001’
  AND 배송.주문번호 = 주문목록.주문번호
  AND 배송.제품번호 = 주문목록.제품번호
  AND 주문.주문번호 = 배송.주문번호
  AND 고객.고객번호 = 주문.고객번호

간단한 고객에 관련된 정보를 읽어오는데 2개의 테이블을 필요하지 않게 읽은 경우이다. 오른쪽과 같이 관계가 중복된 경우는 배송일시와 고객번호, 고객명을 가져오기 위해 다음과 같이 SQL 문장을 구성할 수 있다.

SELECT B.고객번호, B.고객명, A.일시
FROM 배송 A, 고객 B 
WHERE A.배송번호 = ‘20031001001’ 
AND 배송.고객번호 = 주문.고객번호

2개의 테이블에 대해서만 접근을 하므로 관계가 중복되지 않은 경우보다 훨씬 쉽게 SQL 문장도 구성되며 성능도 더 낫다. 테이블의 관계가 5단계 6단계까지 내려가면서 중간에 비식별자관계로 연결되어 있고 빈번하게 조인이 되는 경우라면 관계의 중복을 고려할 수 있다. 프로젝트 상황에 따라 관계의 반정규화는 성능과 단순성에 있어 매우 유용하다.


호두과자에는 호두가 있다!

중부지방을 경유하는 기차 여행을 하면 자주 호두과자를 먹게 된다. 붕어 없는 붕어빵과는 다르게 호두과자에는 호두 알갱이가 있어 제법 고소한 맛이 난다. 이처럼 관공서, 학교, 기업 등에 구축하는 데이터베이스가 견실하기 위해서는 잘 정리된 정규화 사상이 녹아져 있어 정규화 사상 맛이 나는 데이터 모델이어야 한다. 그리고 거기에 체계화된 방법과 타당성 있는 반정규화를 적용한 데이터 모델을 만들어 내야 한다. 이 일은 그렇게 해도 되는 선택적인 사항이 아니라 한 번 구축하면 변경이 불가능하고 잘못된 데이터베이스는 시간에 따라 엄청난 문제와 제정을 낭비하기 때문에 그렇게 해야 하는 당위성을 가지고 있는 중요한 작업이다.


그러기 위해서는 데이터 모델링을 수행하는 사람은 정규화/반정규화에 대해 거울로 자기 얼굴을 보듯 정확한 이해와 체계적인 사고를 바탕으로 데이터 모델링을 할 수 있는 능력을 가져야 한다. 이 글을 읽는 독자는 이론을 위한 이론, 학교시험에서 점수 획득을 위한 지식의 단계를 뛰어넘어 실전에서 무한한 가치를 창조해 내는 진정한 지식가치의 이론을 겸비하여 최고의 데이터 모델링을 수행하는 전문가가 되기를 희망한다.



'dev' 카테고리의 다른 글

expression pattern  (0) 2008.08.01
script class  (0) 2008.08.01
jndi  (0) 2008.08.01
ant  (0) 2008.07.25
정규식  (0) 2008.07.25
Posted by 으랏차
,

jndi

dev 2008. 8. 1. 11:56

JNDI의 소개

많은 J2EE개발자들이 환경 변수(environment entries), DataSource 객체, JMS 메시지 수신지(JMS message destinations) 그리고 엔터프라이즈 빈 홈 인터페이스(enterprise bean home interfaces)를 찾고자 Java Naming and Directory Interface (JNDI)를 이용한다. 하지만 많은 사람들은 JNDI에 대한 진정한 이해 없이 이러한 기능을 하는 코드를 단순히 복사해서 붙이고 고칠 뿐이다. 이 팁은 사용자의 엔터프라이즈 시스템에 배포된 리소스를 액세스하기 위해 JNDI를 사용하는 방법을 소개한다.

엔터프라이즈 애플리케이션들은 그것들의 특성상, 비즈니스 오퍼레이션을 지원하기 위해 여러곳에 배포된 리소스들을 한데 모아야 한다. 새로운 시스템이 생성되었다거나, 기존의 시스템이 업그레이드 되었다거나, 오래된 시스템이 더 이상 작동하지 않을 때 서비스들이 오가게 된다. 애플리케이션 서비스를 서로 분리하는 것은 시스템을 쉽게 유지/확장할 수 있게 한다. 하지만 서비스가 분리되었을 때, 각자의 역할을 제대로 수행하기 위해서는 서로를 찾아낼 수 있어야만 한다. 이 때가 바로 명명 서비스(naming services)와 디렉토리가 유용한 시점이다.

명명 서비스는 이름을 이용해서 객체나 객체에 대한 레퍼런스를 검색하는 방법을 제공한다. 그러한 객체로의 예는 메시지 큐(message queues), 데이터베이스 커넥션 팩토리(database connection factories), 환경 파라미터(environment parameters), 그리고 엔터프라이즈 빈과 같은 분산 컴포넌트(distributed components)등이 있다. 애플리케이션 개발자들은 명명 서비스내의 이름에 객체들을 바인딩해서 객체에 이름을 붙인다. 애플리케이션 코드는 이렇게 바인딩된 이름으로 객체들을 검색하기 위해 명명 서비스를 사용할 수가 있다. 이러한 분리(decoupling)는 네트워크 객체들을 사용하는 시스템 컴포넌트에 대한 어떠한 변경없이 유지 보수를 위해 올리거나 내릴 수 있고, 요청들을 리다이렉트(redirect)시킬 수 있으며, 서비스가 다이나믹하게 재조정될 수 있음을 의미한다.

이미 기존의 명명 서비스에 대해 잘 이해하고 있으리라고 생각된다.

  • DNS (Domain Name Service)는 java.sun.com과 같은 호스트네임을 %nslookup java.sun.com%과 같은 IP주로소 매핑한다.
  • CORBA (Common Object Request Broker Architecture)를 위해 쓰이는 COS (Common Object Services) 명명 서비스 는 CORBA 인터페이스 이름(interface names)을 객체 인터페이스로 매핑한다.

사용자는 컴퓨터의 파일시스템을 파일의 경로(pathname)에 파일의 컨텐츠를 매핑하는 일종의 명명 서비스로 생각할 수 있다.

밑의 그림은 명명 서비스가 서비스 네임을 데이터나 서비스 인터페이스로 매핑하는 방법을 보여주고 있다.

figure 1

이름(name)을 객체로 매핑하는 것을 바인딩이라 부른다. 바인딩은 명명 서비스를 형성하는 사람에 의해 생성된다. 대부분의 명명 서비스는 프로그램이 런타임시에 이름을 객체로 바인딩하거나 해제하는 방법도 제공한다.

컨텍스트는 이름을 객체로 바인딩한 집합이다. 예를 들면, 파일시스템에서 경로 /home 는 흔히 시스템의 유저 디렉토리를 포함하는 컨텍스트이다. 컨텍스트는 다른 컨텍스트를 포함할 수도 있다. /home 컨텍스트의 유저 디렉토리는 그 자체가 유저 파일을 포함하는 컨텍스트이다.

컨텍스트는 최소한 명명 규칙(naming convention)과 검색기능(lookup function)을 갖는다. 예를 들면, DNS는 가장 구체적인 스트링은 왼쪽, 도메인은 오른쪽에 나타내면서 스트링을 점으로 분리하는 명명 규칙을 갖는다. DNS의 검색기능은 nslookup 프로그램을 이용해서 커맨드라인으로부터 액세스가 가능하다.(물론, DNS 명명 서비스에 대한 API도 존재한다.) 컨텍스트는 대게 객체들을 바인딩하고 해제하는 방법과 그것들을 열거하는 방법을 제공한다.

때때로 명명 서비스 의 객체들은 다른 프로그램이 필요로 하는 데이터를 포함한다. 가령, J2EE애플리케이션에서 환경 변수를 나타내는 객체들은 대게 명명 서비스 에 저장된다. 하지만 이외에 명명 서비스의 객체는 객체에 대한 레퍼런스를 나타낸다. 예를 들면, 서버에 레퍼런스를 제공하는 객체는 통상적으로 오픈 서버 커넥션이 아닌 서버에 대한 레퍼런스로서 명명 서비스 에 의해 저장된다.명명 서비스 에 의해 리턴된 레퍼런스 객체는 필요시에 서버 커넥션을 생성하기 위해 사용될 수 있다.

다음 그림은 컨텍스트의 개념도이다. top 컨텍스트는 /top로 불리며 객체, 레퍼런스, 그리고 다른 컨텍스트들을 포함한다. 컨텍스트 /top은 subcontexts b 와 g를 갖는다. 컨텍스트 /top/g 는 subcontext "b"를 갖는다. A라고 불리는 객체가 하나 이상 일지라도, 특정 개체 /top/g/b/a는 그 위치가 컨텍스트 /top/g/b 임이 분명하기 때문에 찾을 수가 있다.

figure 2

바인딩된 객체들에 대한 데이터를 제공하는 명명 서비스를 디렉토리라고 부른다. 가령, 파일시스템 디렉토리는 일반적으로 크기, 타입, 접근 허용 그리고 파일을 생성하고 수정한 날짜에 관한 정보를 제공한다. 몇몇 디렉토리는 이름으로 검색, 애트리뷰트의 조합으로 검색 모두를 허용한다.

각각의 명명 서비스들은 각자의 태스크(task)에 잘 맞도록 되어있지만, 그들이 작동하는 방식은 서로 다르다. 각 명명 서비스는 고유의 명명규칙, 검색기능, 바인딩과 디렉토리 프로토콜(directory protocols)과 객체 서비스 인터페이스(object service interfaces)를 갖는다. JNDI 는 네트워크 서비스를 이름짓고 찾기 위해 일관된 방법을 제공한다.

Java Naming and Directory Interface

JDBC 데이터베이스 커넥션(database connections), JMS 큐(JMS queues) 혹은 엔터프라이즈 빈 홈 인터페이스(enterprise bean home interfaces)와 같은 네트워크 객체에 액세스하기 위해 JNDI 를 사용하는 방법을 이미 알고 있을 것이다. 사실 JNDI 는 이름들을 객체로 매핑하지만, JNDI 는 명명 서비스가 아니다. 그보다도 JNDI는 명명 서비스를 표준적인 방법으로 액세스가능하게 하면서 기존의 명명 서비스를 감추는(wrap) 인터페이스들의 집합이다.

다음 그림에서 보는 것과 같이, 자바 애플리케이션은 JNDI인터페이스를 이용해서 감춰진(underlying) 명명 서비스에 액세스한다.

figure 3

애플리케이션내의 코드는 JNDI 인터페이스 메소드를 호출한다. 이러한 메소드를 구현하는 객체들은 JNDI 인터페이스 호출을 감춰진 명명 서비스에 대한 호출로 매핑한다. 또한 JNDI는 통합된 명명 규칙도 정의한다. JNDI 이름들은 JNDI의 명명 관리자(naming manager)에 의해 감춰진 명명 서비스의 명명 규칙을 따르는 이름으로 매핑된다.

javax.naming 패키지는 다음과 같은 명명과 디렉토리에 관련된 인터페이스들을 포함한다.

  • javax.naming.Context는 컨텍스트를 나타내는데, 이것은 바인딩과 서브컨텍스트를 찾고 관리하는 데에 쓰인다.
  • javax.naming.Name는 명명 서비스의 이름을 추상적으로 표현(abstract representation)하게 해준다.
  • javax.naming.Binding은 명명 서비스 이름과 그 이름에 바인딩된 객체의 표현이다.
  • javax.naming.Reference는 객체의 복사본을 얻어낼 수 있게 해준다.

컨텍스트 찾기

이 팁에 포함된 샘플코드는 JNDI컨텍스트의 컨텐츠를 열거하는 방법을 보여준다. 샘플 서블릿 Oct2003Servlet는 사용자가 입력한 이름에 해당하는 JNDI namespace내의 컨텐츠를 찾고 디스플레이한다.

컨텍스트를 얻는 가장 쉬운 방법은 javax.naming.InitialContext 클래스의 인스턴스를 생성하는 것이다. 샘플 서블릿 메소드 jndiList 는 최초의 컨텍스트를 생성하고 명명된 객체를 찾을 때 그것을 사용한다.

   InitialContext ic = new InitialContext();
   Object objFound = ic.lookup(name);

여기에서 name은 사용자가 HTML페이지에서 입력한 HTTP GET 혹은 POST 변수명(vriable name)이다. 만약 리턴된 객체가 Context 라면 jndiListlistContext 메소드를 호출하고, ListContext메소드는 주어진 이름에 해당하는 컨텍스트의 컨텐츠를 열거한다. 객체가 DataSource이면, jndiList 는 명명된 데이터 소스에 관한 정보를 출력한다.

listContext 메소드는 주어진 JNDI 컨텍스트의 컨텐츠를 하나의 테이블로 출력한다. 이를 위해서는 Context 메소드 listBindings 를 이용하는데, 이는 NamingEnumeration 를 리턴한다.

      NamingEnumeration ne = context.listBindings("");

NamingEnumerationjava.util.Enumeration를 구현한 것이다. NamingEnumeration.next 메소드는 javax.naming.Binding 타입의 객체를 리턴하고, 이는 객체의 이름과 객체의 클래스 이름 그리고 저장된 객체 자체를 포함한다.

      
      while (ne.hasMore()) {
         Binding ncp = (Binding)ne.next();
         String objName = ncp.getName();
         String objClass = ncp.getClassName();
         Object objObj = ncp.getObject();

         ...
     }

단순히 Context내의 이름들과 클래스이름을 보고자 한다면 Context.list 메소드를 이용할 수 있다. Context.listNamingEnumeration를 리턴하지만, 그것이 담고있는 컬랙션은 Binding이 아닌 NameClassPair 타입이다. NameClassPair는 이름과 객체 클래스 이름만을 포함한다.

샘플애플리케이션을 배포하고 실행하는 방법은 샘플코드 실행하기 를 참고한다.

애플리케이션을 실행하면, 다음과 같은 시작페이지를 보게 된다.

jndichoice

컨텍스트를 입력하거나, 텍스트 필드를 빈 상태로 놔두고 명명된 컨텍스트의 컨텐츠를 보기 위해 List버튼을 클릭한다. 예를 들면 jdbc 의 엔트리는 다음과 같은 화면을 디스플레이한다.

jndilist

JNDI 에 관한 더 자세한 정보는 JNDI 튜토리얼를 참고한다.

 

'dev' 카테고리의 다른 글

script class  (0) 2008.08.01
정규화  (0) 2008.08.01
ant  (0) 2008.07.25
정규식  (0) 2008.07.25
검색 & 정규식  (0) 2008.07.25
Posted by 으랏차
,

ant

dev 2008. 7. 25. 15:43

ANT

작성자 : 진은영 ( 2004-08-02)

[메인] [목록]

목차

8.2.1 ANT 개요

① ANT란
자바로 개발을 하다보면 외부에서 jar파일을 가져와서 사용해야 할 때가 있을 것이다. 대표적으로 JDBC드라이버, servlet.jar 등이 있다. 이러한 패키지를 가져다 쓰려면 환경변수에 추가하거나 , 도스상에서 컴파일 혹은 실행할때 클래스패스를 나열하고 실행해야 한다. 패키지들이 많거나 매번 추가해야할 경우에는 굉장히 불편할 것을 느낄것이다.

Ant와 같은 빌드 도구를 사용하지 않을 경우 도스 창에서 클래스 패스에 클래스를 추가하거나 소스 파일 컴파일, jar 파일로 묶는 작업, 묶은 jar 파일을 특정 디렉토리로 이동시키고 수정된 내용을 javadoc으로 문서화 시키는 등의 작업은 소스가 수정될 때마다 한 단계씩 도스 창에서 모두 입력해야 한다. 하지만 Ant를 사용하면 마치 배치(bat) 파일을 실행한 것처럼 이와 같은 일련의 작업들을 Ant를 이용하여 단 한번에 수행할 수 있다.


② ANT의 주요 기능
Ant의 주요 기능을 알기 쉽게 몇 가지만 나열해 보면 아래와 같다.

  • 자바 소스 파일 컴파일(너무 당연하다)
  • jar, war, ear, zip 파일의 생성
  • javadoc을 실행하여 도움말 생성
  • 파일이나 폴더의 이동 및 복사, 삭제
  • 각각의 작업에 대한 의존성 설정
  • 유닉스에서처럼 파일이나 폴더에 퍼미션 설정
  • 파일의 변경 날짜를 설정하는 touch 기능
  • 외부 프로그램의 실행



8.2.2 ANT 설치

아래의 위치에서 파일을 다운로드 받는다.
위치 : http://ant.apache.org/bindownload.cgi 파일 : apache-ant-1.6.2-bin.zip

원하는 장소에 압축을 풀면 아래와 같은 폴더가 나타난다. (필자는 apache-ant-1.6.1-bin.zip로 다운받았다)

ANT를 사용하기 위해서는, 클래스 패스에 apache-ant-1.6.1을 포함해야 한다. 아래의 내용을 확인하면 추가하는 방법을 알 수 있다.
바탕화면 - 내컴퓨터 오른쪽버튼 클릭 - 등록정보 - 고급 - 환경변수를 클릭한다.



새로 만들기 버튼을 클릭하여 ANT의 폴더를 등록한다.



ANT를 실행하기 위해 path환경변수를 선택한 후 편집단추를 클릭하여 ANT의 bin폴더를 path에 등록한다.



등록하는 방법은 Log4J의 환경변수 추가와 유사하다.


8.2.3 ANT 구조

① 빌드 파일 구조
ANT는 프로젝트의 빌드, 테스트 , 배치등의 모든 단계를 어떻게 수행할지 명시하기 위하여 XML파일을 사용한다. 이 XML파일을 빌드 파일이라 하며, ANT를 사용하는 모든 프로젝트들은 적어도 하나 이상의 빌드파일을 가지고 있어야 한다.
ANT가 사용한느 디폴트 빌드 파일명은 build.xml이다.


ANT 빌드 파일의 루트 엘리먼트는 < project />이다. < project /> 하위에는 빌드 과정에서 사용할 속성을 정의할 수 있는 < property /> 엘리먼트와 각 Task에서 사용할 경로 정보를 포함하는 < path />엘리먼트 , 실질적인 작업을 수행하는 < target />엘리먼트를 가진다.
    < project name=“projectName” default=“first” basedir=“.”>
    • name : 프로젝트 이름
    • default : 초기치로 설정되는 작업(Task), 아무 것도 지정하지 않고 실행하면 이 작업을 수행
    • basedir : 프로젝트에 대한 기준 폴더를 지정한다. .은 현재폴더를 지정한다.

    < property name="src.dir" value="${basedir}/src“ />
    < property name="classes.dir" value="${basedir}/classes" />
    property는 변수를 지정하는거과 같다.
    • name : 변수명
    • value : 변수값
    변수를 호출할 때 ${변수명} 으로 사용한다.

    < target name="compile" >
       < javac srcdir="${src.dir}" destdir="${classes.dir}" />
    < /target>
    • name : target의 이름을 지정한다.
      나중에 build 할때 이 이름으로 호출할 수 있다.
    • 자바소스를 컴파일할 때 어디에 있는 무엇을 어디에 컴파일하는가를 지정한다.
      srcdir : 자바소스가 들어있는 폴더를 지정한다.
      destdir : 컴파일한 후 클래스파일이 위치할 폴더를 지정한다.



8.2.4 ANT 사용

① 웹애플리케이션
6-1강좌에서 사용한 웹 애플리케이션을 이용한다.


② build.xml
build.xml 파일은 WEB-INF에 생성한다.
<?xml version=“1.0” encoding="euc-kr"?>

<project name=“projectName”  default=“compile”   basedir=“.”>

    <property name="src.dir" value="${basedir}/src“  /> 
    <property name="classes.dir" value="${basedir}/classes" /> 

     <target name="compile" > 
         <javac srcdir="${src.dir}" destdir="${classes.dir}" /> 
    </target> 
</project>  


③ 자바문서 생성
자바문서는 src 폴더 안에 생성한다.
package kr.co.a ;

public class ANTTest {
	public static void main( String [] args ){
		System.out.println( "test" ) ; 
	}
}


④ 도스모드에서 컴파일
도스 창을 띄운 후 현 컨텍스트의 WEB-INF 폴더를 이동한다.
그냥 컴파일을 하면 현재 폴더안에 패키지가 생기면서 컴파일이 된다.


⑤ ant를 실행한다.
E:\03.src\jslt\WEB-INF>ant

ant를 실행하면 현재 폴더에 있는 build.xml 문서를 호출해서 project에 default로 선언되어 있는 task를 호출한다. 여기에서는 compile task를 호출한다.


⑦ 탐색기를 띄워 확인한다.
탐색기를 띄워 build.xml에 선언되어 있는것처럼 classes폴더에 가보면 컴파일 한 클래스가 해당 패키지 밑에 있는것을 확인할 수 있다.

이렇듯 build.xml문서만 잘 만들어 놓으면 컴파일을 수월하게 할 수 있다. 이외에도 굉장히 많은 내용들이 있지만 여러분들이 아래 참고문허을 살펴보며 추가해 보자. 혹시, 나중에 좀더 내용을 추가할 기회가 올지도...

참고문헌

 

'dev' 카테고리의 다른 글

정규화  (0) 2008.08.01
jndi  (0) 2008.08.01
정규식  (0) 2008.07.25
검색 & 정규식  (0) 2008.07.25
tomcat  (0) 2008.07.25
Posted by 으랏차
,

정규식

dev 2008. 7. 25. 15:34
Regular Expression(정규 표현식)
정규 표현식이란, 문자열에서 특정한 캐릭터 조합(character combination)을 찾아내기위한 패턴(pattern)입니다.
쉽게 문장에서 특정한 단어를 찾아내는 것이라고 생각하시면 될 것 같습니다
그렇기 때문에 정규 표현식 함수라고 불리는 것들은 대부분 문자열과 관련하여 특정 변수로 저장된 문자열 내에서 지정한 문자를 찾아내는 기능들을 수행합니다.
일반적으로 - 어느 랭귀지를 사용하던지 간에 - 정규 표현식을 구성하는 방법은 일반적인 문자열과 특정한 의미를 지니는 메타문자를 조합하는 것입니다.

JavaScript에서 정규 표현식은 1.2 버젼부터 사용가능하도록 추가되었습니다. 즉 그 이하버젼에서는 사용하지 못한다는 말이므로 아주 오래된 고려적 웹브라우저를 사용하시는 분들은 절대로 볼수가 없습니다. -_-;;
자바스크립트 객체부에서는 언급을 하지 않고 있지만, 정규표현식은 객체(objects)입니다.
정규 표현식의 패턴(pattern)은 object initializers(예, /abc/) 또는 RegExp constructor function(예, re = new RegExp("abc")로 표현할수 있습니다.
이러한 패턴들은 정규표현식의 exec, method 메소드(method)나 String 객체의 match, replace, search, split 메소드에서 함께 사용됩니다.

Regular Expression(정규 표현식) 생성
1. object initializers를 사용한 방법

re = /ab+c/

2. RegExp 객체의 constructor function를 사용한 방법

re = new RegExp("ab+c")

Regular Expression(정규 표현식) 쓰기
정규 표현식 패턴은 /abc/ 같은 단순한 캐릭터나 /ab*c/ ,/Chapter (\d+)\.\d*/ 와 같은 단순 캐릭터와 특별한 캐릭터의 조합으로 나타낼수 있습니다.
다음에 정규 표현식에 사용하는 특수 문자들에 대한 표를 나타내었지만, 이 외에도 상당히 많은 내용들이 있습니다.
타 사이트나 책을 참고로 하여 보시기 바랍니다.

정규 표현식에서 사용하는 Special characters

Character

의미

\

\ 다음에 나오는 특수 문자를 문자열로 인식
가령, /라는 특수문자는 일반적으로 프로그램상에서 나누기로 인식하게 되어있습니다. 이것을 나누기가 아닌 그냥 문자열 / 로 인식시키려면 \/ 로 써주면됩니다.

^

라인의 처음과 패턴과 매치
가령, ^A 라고 써주면 검색하고자 하는 문장의 시작문자가 A인지를 검사하는 것입니다.

$

라인의 끝과 패턴과 매치
가령, ^A 라고 써주면 검색하고자 하는 문장의 마지막문자가 A인지를 검사하는 것입니다.

*

0개 이상의 문자와 매치(모든것이라는 의미)

+

1개 이상의 문자와 매치, {1,}와 같은 의미임.

?

0 또는 1개의 문자 의미.
즉, A?b 라면 A라는 문자와 b라는 문자사이에 문자가 0개 또는 1개 가 들어갈 수 있다는 말입니다. 즉, Ab, Aab, Acb등과 같은..

.

1개의 문자와 일치

()

한번 match를 수행해서 나온 결과를 기억함.
예: /(foo)/ 는 foo라는 단어를 검색한 후, 그 단어를 배열등과 같은 저장장소에 남겨두어 나중에 다시 호출할 수 있도록 합니다.

|

OR

{n}

정확히 n개의 문자
예: a{2} 는 a 문자 두 개, 즉, aa를 의미합니다.

{n,}

n개 이상의 문자

{n,m}

n이상 m이하의 문자

[xyz]

문자들의 set를 의미. 가령, [a-z]라면 a부터 z까지의 모든 문자와 매치하는 것으로 []안의 -는 범위를 나타냅니다.

[^xyz]

네가티브(-) 캐릭터 셋

[\b]

백스페이스와 매치

\b

단어의 시작 또는 끝에서 빈 문자열과 매치

\B

단어의 시작 또는 끝이 아닌 곳에서의 빈 문자열과 매치

\cX

control 문자와 매치

\d

0부터 9까지의 아라비아 숫자와 매치. [0-9]과 같은 의미

\f

form-feed와 매치

\n

linefeed와 매치

\r

캐리지 리턴과 매치

\s

화이트스페이스 문자와 매치. [ \t\n\r\f\v]과 같은 의미

\S

\s가 아닌 문자들과 매치. [^ \t\n\r\f\v]과 같은 의미

\t

탭 의미

\v

수직 탭 의미

\w

w는 문자가 아닌 0, 1, 2, 3 ... 등과 같은 숫자를 의미

\W

W는 문자가 아닌 요소, 즉 % 등과 같은 특수 문자를 의미함

\n

n은 마지막 일치하는 문장

\ooctal
\xhex

8(octal)진수, 10(hex)진수 값

Regular Expression(정규 표현식)과 함께 사용하는 함수들

exec

문장에서 매치를 위해 검색을 수행하는 정규 표현식 메소드
배열을 리턴

test

문장에서 매치를 위해 테스트하는 정규표현식 메소드
True 또는 False 리턴

match

문장에서 매치를 위해 검색을 수행하는 string 메소드
배열 또는 null 문자 리턴

search

문장에서 매치를 위해 테스트하는 string 메소드
목차나 -1 리턴

replace

문장에서 매치를 위해 검색을 실행하고 문장을 대체하는 String 메소드

split

문장에서 매치하는 부분을 배열에 할당하는 String 메소드

이와 같은 정규 표현 식들의 실행 결과는 예제의 테스트를 통해 확인해 보도록 하십시요.

<SCRIPT LANGUAGE="JavaScript1.2">
myRe=/d(b+)d/g;
myArray = myRe.exec("cdbbdbsbz");

document.writeln("The value of lastIndex is " + myRe.lastIndex);
</SCRIPT>

<SCRIPT LANGUAGE="JavaScript1.2">
myArray = /d(b+)d/g.exec("cdbbdbsbz");
document.writeln("The value of lastIndex is " + /d(b+)d/g.lastIndex);
</SCRIPT>

<SCRIPT LANGUAGE="JavaScript1.2">
re = /(\w+)\s(\w+)/;
str = "John Smith";
newstr = str.replace(re, "$2, $1");
document.write(newstr)
</SCRIPT>

<SCRIPT LANGUAGE="JavaScript1.2">
function getInfo(){
re = /(\w+)\s(\d+)/
re.exec();

window.alert(RegExp.$1 + ", your age is " + RegExp.$2);
}
</SCRIPT>
<BODY>
Enter your first name and your age, and then press Enter.
<FORM>
<INPUT TYPE="text" NAME="NameAge" onChange="getInfo(this);">
</FORM>
</BODY>

<SCRIPT LANGUAGE="JavaScript1.2">

// The name string contains multiple spaces and tabs,
// and may have multiple spaces between first and last names.

names = new String ( "Harry Trump ;Fred Barney; Helen Rigby ;\Bill Abel ;Chris Hand ")
document.write ("---------- Original String" + "<BR>" + "<BR>")
document.write (names + "<BR>" + "<BR>")
// Prepare two regular expression patterns and array storage.

// Split the string into array elements.
// pattern: possible white space then semicolon then possible white space

pattern = /\s*;\s*/

// Break the string into pieces separated by the pattern above and and store the pieces in an array called nameList
nameList = names.split (pattern)

// new pattern: one or more characters then spaces then characters.
// Use parentheses to "memorize" portions of the pattern.
// The memorized portions are referred to later.

pattern = /(\w+)\s+(\w+)/

// New array for holding names being processed.

bySurnameList = new Array;

// Display the name array and populate the new array
// with comma-separated names, last first.
// The replace method removes anything matching the pattern
// and replaces it with the memorized string--second memorized portion
// followed by comma space followed by first memorized portion.
// The variables $1 and $2 refer to the portions
// memorized while matching the pattern.

document.write ("---------- After Split by Regular Expression" + "<BR>")
for ( i = 0; i < nameList.length; i++) {
document.write (nameList[i] + "<BR>")
bySurnameList[i] = nameList[i].replace (pattern, "$2, $1")
}

// Display the new array.

document.write ("---------- Names Reversed" + "<BR>")
for ( i = 0; i < bySurnameList.length; i++) {
document.write (bySurnameList[i] + "<BR>")
}

// Sort by last name, then display the sorted array.

bySurnameList.sort()
document.write ("---------- Sorted" + "<BR>")
for ( i = 0; i < bySurnameList.length; i++) {
document.write (bySurnameList[i] + "<BR>")
}
document.write ("---------- End" + "<BR>")
</SCRIPT>

출처: http://members.tripod.lycos.co.kr

 

'dev' 카테고리의 다른 글

jndi  (0) 2008.08.01
ant  (0) 2008.07.25
검색 & 정규식  (0) 2008.07.25
tomcat  (0) 2008.07.25
jdk  (0) 2008.07.25
Posted by 으랏차
,