마지막 연재를 시작하며

 오라클 제품의 세계화 방식과 기술을 설명하기 위해 틈틈이 써온 글이 어느덧 마지막 회가 되었다. 첫 회와 두 번 째 회에서 우리는 오라클의 데이터베이스 제품을 중심으로 기본적인 NLS 지원과 올바른 사용 방법에 관해 알아보았다. 그리고 세 번 째 회에서는 오라클이 가지고 있는 NLS 정보를 접근할 있는 API의 집합인 GDK(Globalization Development Kit)을 이용하여, 사용자의 애플리케이션이 어떻게 그 정보를 열람 및 사용할 수 있는가에 대해 알아보았다. 이제 마지막으로는 오라클의 다국어 지원 제품들이 어떻게 태어나는지, 그 이면에 숨은 비밀을 공개할까 한다. "오라클의"이라는 수식어를 붙였지만, 오히려 일반적인 소프트웨어 세계화 프로세스를 기술하려고 노력할 것이다. 오라클은 다음에 기술된 세계화 프로세스를 충실히 구현해 운영하고 있다.

"우리도 세계화된 소프트웨어 좀 만들어보자 카이~"

특정 지역에서만 사업을 운영하는 기업을 위한 프로젝트를 수행한다거나, "한국"과 같이 특정 지역에 한정된 사용자만을 위해 뚝딱뚝딱 소프트웨어를 개발하는 경우에는 "세계화"라는 개념이 크게 중요하지 않을 것이다. 사용자의 요구사항에 맞는 기능들을 문제없이 수행해 주는 소프트웨어를 개발하고 운영해 주기만 하면 된다. 하지만, 첫 회에서 강조했듯이, 현실 세계의 글로벌화 추세는 소프트웨어 산업에서도 외면할 수 없는 화두가 된지 오래다. 세상은 점점 좁아지는데, 소프트웨어만은 각자 개발한다거나, 한 지역만을 위한 소프트웨어만을 고집한다면, 시대에 뒤떨어졌다는 말을 들어도 어쩔 수 없을 것이다. 특히, 다국적 기업의 운영 소프트웨어나, 또는 오라클과 같이 범용 패키지 소프트웨어를 생산할 경우에는 "세계화"된 소프트웨어가 필요하다.

"하긴 해야겠는데, 어떻게 준비해야 하나?"

이 글을 읽는 개발자의 현실은 어떠한지, 꿈은 어떠한지 일일이 다 알 수 없다. 현실에서 여러분들은 여러 나라에서 동시다발적으로 사업을 전개하는 업체의 내부 개발자일 수도 있고, 또는 오라클이나 마이크로소프트처럼 특정 나라나 특정 사업에 국한되지 않는 범용 소프트웨어를 개발하는 회사에 몸담고 있거나, 그런 제품을 만들어낼 꿈을 가지고 있을 지도 모른다. 국제화/현지화된 소프트웨어를 생산하기 위해서는 어떻게 조직을 정비하고, 어떤 프로세스로 작업해야 하는 것이 좋을까? 오라클을 비롯한 세계적인 소프트웨어 업체들은 이런 질문에 대해 이미 선진화된 체계를 이용한 답을 가지고 있다는 점을 주목해야 한다.

"세계화된 소프트웨어 생산을 위해 첫걸음을 내딛자"

오라클은 "한글"만을 위해서 어떤 개발을 진행하지는 않았다. 그래서 이 글의 제목에 포함된 "한글화된 오라클 제품"이라는 문구는 오라클의 입장에서는 "세계화된 오라클 제품"이라는 말과 동일한 셈이다. 지금부터 "세계화된 오라클 제품", "세계화된 소프트웨어"에 숨은 프로세스에 접근해 보기로 하자.

소프트웨어 세계화의 개념

우선, 뒤섞여 사용되고 있는 용어들의 개념부터 확실히 정리하고 넘어가자. 혹자는 "세계화" 또는 "글로벌화"를 외치기도 하고, 혹자는 소프트웨어의 국제화, 현지화를 외치기도 한다. 비슷한 개념으로 받아들여지지만, 그 범주는 확실히 다르므로, 독자 여러분도 구분하여 사고할 수 있기를 바란다. 이들 개념을 설명하기 위해서 현지화 산업 표준 협회(LISA)의 현지화 산업 입문서를 참고하였다.

국제화(Internationalization, I18N)

국제화는 특정 지역이나 특정 언어에 국한되지 않고, 어떠한 지역이나 언어에서도 동작할 수 있도록 소프트웨어를 설계하고 개발하는 절차를 의미한다. 제대로 된 "세계화"를 실행하기 위해서는 아래 언급된 "현지화"이전에 반드시 소프트웨어 자체의 "국제화"가 선행되어야 한다. 소프트웨어 생산 후, 판매 지역이 늘어날 때마다, 각 지역별 버전을 재생산하느라 땀을 쏟는 장면을 상상해 보라. 얼마나 비생산적인 프로세스인가? 지금 여러분들이 개발하고 있는 소프트웨어를 중국에서도 판매 및 서비스하려 할 경우, 많은 변화가 필요하다면 여러분들은 그만큼 국제화의 개념에 멀어진 상태에서 소프트웨어를 생산하고 있는 셈이다.

오라클의 소프트웨어들은 그 자체의 성능만으로 시장을 지배하는 것이 아니라, 거의 모든 제품의 국제화를 달성함으로써 더욱 강력해 지고 있다. 오라클 데이터베이스의 경우 완전히 동일한 소프트웨어로 아랍계부터 베트남 지역가지 140개가 넘는 종류의 로케일(Locale)을 지원하고 있다.

현지화(Localization,L10N)

LISA의 말을 빌리면, 현지화는 "개별 시장의 차이점을 고려하여 제품 또는 서비스를 수정하는 절차"라고 한다. 가장 대표적인 것이 소프트웨어 화면 및 도움말의 번역이지만, 현지화는 번역만을 포함하는 것은 아니다.  번역과 더불어 소프트웨어를 현지의 법규과 문화에 맞추는 작업을 반드시 수행해야 한다. 자동차를 세계화하는 작업에 빗댄다면, 매뉴얼과 조작 장치의 태그를 번역하는 것도 중요하지만, 영국의 교통 법칙과 문화에 맞게 핸들을 오른쪽으로 옮기는 작업도 필요한 것이다. 우리가 글을 읽을 때 왼쪽에서 오른쪽으로 읽는다고, 아랍계 언어를 사용하는 사람들에게도 그것을 강요한다면 통할 리가 없다.

오라클도 물론 중요한 판매 지역들에 대해 소프트웨어의 사용자 인터페이스 및 도움말에 대해 번역을 제공한다. 현재 영어를 포함한 10개의 언어에 대해서는 일반 사용자가 아닌 전문 관리자가 사용하는 화면과 도움말, 그리고 개발 API의 메시지(JDBC 등) 및 각종 유틸리티(SQLLoader, Exp/Imp)의 화면 출력의 번역을 제공하고 있다. 한국어 또한 전 세계 주요 10개 언어에 포함되니, 비록 일개 글로벌 기업의 서비스이지만, 한국의 위상을 느낄 수 있다. 그리고, SQLPlus와 같이 일반 사용자들도 빈번히 사용하는 애플리케이션에 대해서는 30가지 언어로 번역을 제공하며, 이 서비스로 혜택을 보는 나라들의 수는 훨씬 많은 셈이다.

물론 오라클의 현지화는 번역 이상의 조건도 만족한다. 오라클의 입장에서는 미국 세법에 맞게 개발된 E-Business Suite 애플리케이션을 한국에서도 판매할 수 있으리라 기대할 수는 없을 것이다.  오라클의 E-Business Suite은 각 지역별 세법 및 회계 전문가와 같은 지역화 전문가들이 제품의 지역화를 쉽게 할 수 있도록 국제화 방식으로 구현되어 있기 때문에 지역화가 쉽게 구현되고 있다.

세계화(Globalization)

세계화는 소프트웨어의 국제화와 현지화를 포함한 포괄적 개념으로, 소프트웨어를 전 세계 어느 곳에서나 사용할 수 있도록 개발 및 변경하는 절차를 의미한다. 결국 소프트웨어의 "세계화"는 한 마디로 다음과 같이 정의될 수 있다.

"국제화된 소프트웨어를 현지화 서비스와 함께 제공하는 것"

참고로, 본 글은 세계화된 소프트웨어의 생산,개발 측면에 관해서만 집중할 것이다, 세계화 정보의 수집 및 현지 마케팅과 같은 사업 영역에 대해서는 사업자의 지식을 참고해야 할 것이다.

세계화된 소프트웨어의 요구 조건

 오라클은 한국을 비롯한 전 세계의 모든 나라에 자사의 소프트웨어를 판매하고, 사용자들이 운영을 쉽게 할 수 있도록 도와주기 위해서 이미 수 년 전부터 소프트웨어의 설계시부터, 국제화/현지화를 고려해 왔다. 누누이 자랑해 온 바 대로 오라클 소프트웨어는 "지구 버전"만 존재할 뿐, "한국어판", "중국어판"이라는 것이 애시당초 존재하지 않는 것도 다 설계 자체부터 "지구 버전"으로 설계를 한 후 개발, 생산했기 때문이다.  초기 개발 비용은 크지만, 한 번 설계가 견고하게 완성되면 새로운 버전이 나오거나, 새로운 언어를 지원할 때의 확장 비용은 미미하다.

  국제화 및 현지화된 소프트웨어는 다음의 조건을 만족해야 한다.

 첫째, 지역의 고유한 환경에 관계없이 같은 기능을 오류없이 제공해야 한다.  특정 지역에서는 해당 기능이 동작하지만, 다른 지역 환경에서는 동작하지 않는 일이 있어서는 안된다. 사용자의 운영체제가 Windows 한국어판이든지, Windows 일본어판이든지 상관없이 소프트웨어가 가진 모든 기능을 제공해야 한다는 의미이다.
 
 둘째, 각 지역의 고유한 특성에 맞는 입력을 받아들여, 역시 그 특성에 맞는 출력 결과를 제공해야 한다.  애플리케이션은 사용자에게, 각자의 환경이 아닌 특정한 환경에 맞는 입력을 강요하거나, 특정한 환경에만 맞춘 출력 결과를 이해하도록 강요해서는 안 된다. 한국의 소수점 기호(.)와 숫자 분리 기호(,)를 사용한 숫자 출력 결과를 프랑스인에게 제공한다고 가정해 보자. 기본적으로 이 애플리케이션은 첫 번 째 조건인 "오류없이 동작"을 성공적으로 만족시키기는 했지만, 올바르다로 할 수 없다. 프랑스인이 소프트웨어를 사용하고 있다면, 당연히 같은 숫자 값이라도 프랑스의 소수기호와 숫자분리 기호를 사용한 출력 결과를 제공해야 하는 것이다.

 셋째, 양질의 번역된 인터페이스와 도움말을 제공해야 한다.  소프트웨어 현지화의 조건 중 가장 드라마틱한 결과를 제공해 주는 부분이 바로 번역이다. 소프트웨어 자체의 겉모습을 완전히 새로 치장하는 것으로 가장 이상적인 상황에서 이해한다면, 사용자의 입장에서는 지금 사용하고 있는 소프트웨어가 마치 자신의 나라에서 직접 생산된 소프트웨어인 듯한 편안함을 가지게 해 주는 기능이라고 할 수 있다. 표준화된 방식에 따라 소프트웨어의 구조를 변경한 이후로는 개발 비용이 크지 않은 첫 번 째나 두 번 째 조건과는 달리, 번역은 상당히 큰 "서비스"이며 비용 또한 만만치 않다. 첫 째, 둘 째 조건들이 모든 나라에서 반드시 제공되어야 하는 조건이라고 한다면, 셋 째 조건인 "번역"은 주로 다수의 사용자들을 이미 확보한, 또는 다수의 사용자가 있을 것으로 확실히 예측되는 지역에 대해 제공되는 것이 일반적이다.

탄탄한 소프트웨어 국제화/현지화 조직

위와 같은 조건을 맞족하는 국제화/현지화된 소프트웨어를 만들어내기 위해서는 다음과 같은 조직이 필요하다. 규모가 크지 않은 생산 업체라면, 각 조직이 소수의 전문가로만 구성될 수도 있겠지만, 그 기능의 분리 및 협력은 이와 같이 명백히 정의되어야 한다.

소프트웨어 국제화 조직

 소프트웨어의 기능을 각 나라별로 직접 구현하는 조직을 의미하는 것이 아니다. 소프트웨어 자체만 "지구 버전"인 것이 아니라, 개발 자체도 "한국어 기능을 포함하기 위한 개발", "베트남어를 지원하기 위한 개발"이라는 것이 존재해서는 곤란하다. 단순히 "국제화를 지원하기 위한 개발"이라는 목표만이 있어야 할 것이며, 이 조직은 소프트웨어를 그와 같은 방식으로 개발하기 위해 필요한 개발 방식을 정확히 알고, 실제 개발 조직이 그 방향으로 움직일 수 있도록 도와 주는 조직이다. 크게 보아 반드시 다음과 같은 기능을 제공해야 한다
  • 소프트웨어 현지화 정보 수집 및 관리 : 특정 지역에서 사용되는 여러 가지 고유의 정보(통화 기호, 날짜 포맷 등)들을 모두 수집하고 관리해야 한다. 이런 정보들은 주로 고정적이지만, 통화 기호처럼 사회적 상황에 따라 변화되는 정보들도 있다. 예를 들어, 유럽 연합의 통화 기호가 "유로"로 통일된다는 것이 고지되었을 때, 이런 조직들은 상당히 발빠르게 움직였을 것이다. 자신들이 보유하고 있는 정보를 수정하는 것과 더불어, 해당 지역들이 상당 기간 기존의 통화와 새로운 통화를 동시에 사용하게 될 때 개발될 소프트웨어를 유동적으로 동작시키는 방법에 대한 고민을 했을 것이다.
  • NLS 기능 라이브러리 제공 : 보유한 현지화 정보를 개발자들이 사용할 수 있도록, 통일된 방법을 제공하는 것도 이 조직의 몫이다. 여러 소프트웨어의 개발자들에게 각자 이 정보들을 직접 접근하도록 맡긴다면 효율이 떨어질 것은 분명할 뿐더라, 실수가 빈번할 것이 분명하다. 개발자들이 주로 사용하는 프로그래밍 언어로 개발된 통일된 라이브러리와 매뉴얼을 제공하는 것이 중요하다. 오라클의 인프라스트럭쳐 소프트웨어(데이터베이스, 웹 애플리케이션 서버 등)들의 경우, 이를 기반으로 개발을 진행하는 수많은 고객 개발자들이 존재하며, 이들에게도 이런 라이브러리들은 점점 중요해지고 있다. 따라서 지난 회에 언급한 대로 오라클은 GDK(Globalization Development Kit)을 오라클 소프트웨어 기반의 개발자들이 사용할 수 있도록 배포하고 있다.
  • 국제화 테스팅 : 개발자들이 소프트웨어 국제화 기능(첫 째 조건)을 제대로 구현했는지 꼼꼼히 테스트하는 것도 이 조직의 중요한 임무이다.  이 테스팅은 모든 나라나 모든 언어에 대해 이루어질 수도 있지만, 그렇지 않을 수도 있다. 지역별  테스팅은 아래 언급할 현지화 테스터들에 의해 수행되게 된다. 이 조직의 테스트에서 그보다 중요한 것은, 하나의 멀티바이트 언어나 지역을 이용해 테스트할 지라도,  모든 기능에 대해 시험하는 것이다.
소프트웨어 국제화에 대한 지식을 가지고 있는 엔지니어들이 주로 이 조직의 구성원이 된다.

소프트웨어 번역 조직

소프트웨어의 번역은 비용이 많이 드는 작업으로 특히 그 운영이 효율적이어야 한다. 소프트웨어를 번역하는 조직은 다음과 같은 조건을 만족해야 한다.
  • 정확한 용어 사용 : 단순히 사전을 열람하여 번역을 시도했다가는 터무니없는 번역 결과가 나올 수 있다. 번역 담당자는 반드시 해당 기술에 대해 지식을 가졌거나, 아니면 전문가 혹은 표준화된 산업 용어를 사용하여 번역해야 할 것이다.
  • 일관성 :  특정 위치에서는 해당 단어를 번역하고(예:자바), 다른 위치의 화면에서는 해당 단어를 번역하지 않거나(예:Java) 다른 용어를 사용하여 번역해서는 안 된다. 소프트웨어는 그 특성상 외국에서 유입된 용어들이 상당히 많으며 이들이 단순히 소리나는 대로 표기되기도 하고(예: 버튼), 우리 말로 번역되어 사용되기도 한다(예: 단추). 어떤 방향이든 일관성을 유지하는 것도 중요하다.
  • 비용 절감 : 번역시 불필요한 비용이 발생해서는 곤란하다. 예를 들어, 이전 버전의 소프트웨어 개발시 이미 번역했던 문장을, 단지 화면이 변경되었다고 해서 새로운 버전의 개발시 다시 번역한다면 이중의 비용을 들이는 셈이 된다.
이 조건들을 만족하는 번역 프로세스를 수행하기 위해 다음의 조직들이 필요하다.

번역 시스템 운영 조직

번역은 기계적은 프로세스만으로는 해결될 수 없다. 개발 과정에서 사람의 손을 가장 많이 거치게 되는 부분이라 할 수 있다. 그래서 앞서 말한 대로 프로세스가 느리고 상당히 값비싸다. 따라서,  번역에 대한 사전 준비 과정(preprocessing)과 번역 후의 처리 과정(postprocessing), 그리고 번역 결과의 보존 및 관리가 매우 중요하다. 소프트웨어 번역 조직의 시스템은 일단 그 완벽성을 위해서는 다음과 같은 금지의 법칙을 지켜야 한다.
  • 번역 누락 금지 : 개발조직에서 번역 대상으로 분류한 리소스들은 반드시 누락 없이 번역 수행 조직으로 보내야 한다. 그리고 번역 수행 조직으로부터 돌아온 결과는 반드시 안전하게 개발 조직으로 공급해야 한다.
  • 번역 반복 금지 : 이미 번역된 결과를 다시 번역 수행 조직으로 보내어서는 안 된다.
일단 운영 조직은 번역 수행 조직에게 번역 요청을 하기 전에 다음과 같은 작업을 선행해야 한다.
1) 번역 전 작업
개발 조직에서 전송되는 번역 리소스들은 상당히 다양한 형식을 취하고 있는 것이 보통이다. 그리고 번역 전문가들이 사용하고 있는 도구들 또한 다양하다. 따라서 번역이 필요한 리소스들을 번역가들에게 일관된 형식으로 전달하는 것이 중요하다. 번역가들은 각 지역별 환경에서 작업하므로, 지역별 특성도 함께 고려해야 한다. 운영 조직은 번역 리소스들에 사전 작업을 가하여 번역가들이 작업할 수 있는 환경을 만들어내야 한다.

또한 번역 리소스들 중에는 이미 번역한 결과가 포함될 수 있다. 이미 번역한 결과는 아래 언급되어 있는 번역 결과 데이터베이스에 저장이 되어 있다. 번역 전 작업시 번역 리소스들을 데이터베이스와 대조하여 이미 번역된 리소스를 다시 번역하는 일이 없도록 해야 할 것이다.

예를 들어 개발 조직에서 오라클 데이터베이스를 위한 메시지 파일을 번역하고자 할 때, 사전에 이 메시지 파일에서 번역 리소스들을 추출해 현재 번역 데이터베이스와 대조한다. 아직 번역된 일이 없는 새로운 리소스들만을 추출하여번역 조직에서 사용하는 도구에 맞는 번역 패키지를 생성한다.
2) 번역 후 작업
번역 수행 조직으로부터 돌아온 번역 결과는 다시 번역 전 작업의 역순으로 진행해 개발 조직으로 전송할 수 있는 형태로 만들어내야 한다. 그리고 새로운 번역 결과는 번역 결과 데이터베이스에 저장하여 다음 번역 수행시 활용할 수 있도록 해야 한다.

예를 들어, 번역 수행 조직에서 돌아온 번역 결과를 다시, 개발조직에서 처음에 전달했던 메시지 형식으로 복원하여 전달하게 된다. 그리고 새로운 번역 결과는 아래의 번역 결과 데이터베이스에 잘 저장을 한다.
3) 번역 결과 보존
앞서 언급했듯이 번역은 값비싼 프로세스이므로 그 결과는 잘 보존되어야 한다.
  • 번역 결과 데이터베이스 : 번역된 결과를 문장 혹은 단락 단위로 저장하여 번역의 재사용성을 높이는 데 사용된다.
  • 용어 데이터베이스 :  일관된 번역을 위해 어떤 제품에서 어떤 용어가 어떤 단어로 번역되었는지를 보존한다.
번역 프로세스를 잘 이해하고 있는 엔지니어들이 주로 이 조직의 구성원들이다.

번역 수행 또는 번역 수행 업체 관리 조직

소프트웨어의 번역을 실제로 수행하는 조직이다. 이 조직은 번역 시스템 운영 조직으로부터 전달받은 번역 대상물을 번역하여 그 결과를 약속된 형식으로 돌려주는 역할을 한다. 규모가 큰 소프트웨어 개발업체는 일반적으로 번역 자체를 내부 인력으로 감당할 수 없다. 그러기에는 번역의 규모가 너무 방대하기 때문이다. 따라서, 이 조직은 일반적으로 양질의 번역 협력사와 계약을 맺어 번역을 아웃소싱한다.
번역 프로세스의 실제 수행
  • 번역 프로세스의 실질적 비즈니스 운영 주체 : 번역이라는 프로세스를 수행하는 데 필요한 예산을 요청 부서와 협의하고, 그 테두리와 협력사와의 계약을 기반으로 번역을 수행한다.
  • 번역 수행 또는 보조 : 번역을 직접 수행하지 않는 경우에는 번역 전문가들이 문의하는 제품의 특성에 대해 적절히 답변하여 제대로 된 번역이 이루어지도록 해야 한다.
  • 번역 검수  : 최종 번역 결과를 운영 조직으로 전달하기 전에, 그 결과물을 검수하여 문제있는 번역이 제품에 포함되는 일이 없도록 한다.
번역 프로세스 외의 작업
  • 품질 관리 : 번역 결과가 개발 조직으로 전달되어 제품으로 출시된 이후에도 일반 사용자들로부터의 번역 품질 피드백에 따라 품질을 평가하고 향상을 도모하게 된다. 또는 자체 품질 평가 기준을 마련하여 해당 수치에 미달하는 품질을 판별 다음 계약을 안전하고 확실하게 할 수 있도록 해야 한다.
  • 번역가(협력사) 교육 : 새로운 프로세스가 소개되었을 경우나 새로운 제품이 출시되었을 때 번역가들에게 사전 지식을 심어준다.
원본 언어(한국 제품의 경우 한국어, 미국권 소프트웨어의 경우 영어)와 번역 대상 언어에 능통하며, 해당 소프트웨어 대해 포괄적 지식을 가진 전문가들이 주로 이 조직의 구성원들이다.

소프트웨어 현지화 테스팅 조직

국제화 테스팅 작업은 주로 기능(Functionality) 테스트에 집중하지만, 현지화 테스팅은 각 지역별 입출력 및 번역된 화면을 검사하는 역할을 한다. 각 언어 및 지역별 지식을 가진 구성원들은 주로 다음과 같은 임무를 수행한다.
  • 현지화 테스팅 : 각 지역별 환경(OS나 NLS_LANG과 같은 지역별 환경변수)을 정확히 구비하고, 그 환경 상에서 최종 사용자의 사용 시나리오를 기반으로 입출력 테스팅을 수행한다.
  • 번역 테스팅 : 번역 결과가 제품에 포함된 이후, 제품을 수행하여 번역으로 인해 제품이 동작하지 않거나, 번역 결과가 제대로 나오지 않는 경우, 또는 번역되지 않은 부분이 있는 경우를 검사한다.
각 지역별 지식을 갖추고, 소프트웨어를 매뉴얼 대로 설치 및 운영할 수 있는 테스트 엔지니어들이 주로 이 조직의 구성원들이다.

소프트웨어 현지화 구현 조직

소프트웨어 현지화 정보 중, 법규나 비즈니스 같은 경우는 빈번히 수정되는데다가 통합 수집 관리가 불가능한 경우가 많다. 이럴 경우, 각 지역별 전문가 조직이 직접 해당 요구 조건을 구현해야 한다. 오라클은 한국에서 국세청을 비롯 다수의 공공기관을 고객으로 유치하였다. 이들 기관은 법규에 따라 행정을 수행하며, 특히 "연말 정산"과 같이 빈번히 변하는 법규도 있다. 그러므로, 해당 고객의 시스템은 법규의 변화에 민첩하게 대응할 수 있어야 한다. 어떻게 한국에서 생산된 제품이 아닌, 오라클 소프트웨어가 그런 변화에 대응할 수 있을까? 정답은 바로 각 지역별로 상주하는 현지화 전문가들에 있다. 이들은 법규나 시장의 상황의 변화를 긴밀히 살피고 변화가 감지 또는 고지되면, 즉시 시스템 현지화 작업을 재시행해 변화된 법규를 반영한다.

시스템 운영의 지식과 함께 현지화 대상 비즈니스 영역에 대한 지식을 보유한 전문가들이 주로 이 조직의 구성원이 된다.

표준에 기반한 국제화/현지화 가이드라인

개발 조직과 국제화/현지화 조직들을 위해서는 다음과 같은 가이드라인을 미리 만들어 준수하도록 유도해야 한다. 개발 조직의 규칙 위반은 번역 시스템의 입력이 되는 번역 대상에 오류를 발생시킬 수 있다. 그리고 번역 시스템 운영 조직의 잘못된 운영은 번역 자체가 이루어지지 않거나 번역의 비용을 증가시킬 수 있다. 번역 수행 조직의 오류가 야기할 문제는 품질 저하로 이어진다. 그리고 테스팅 조직의 실수는 문제를 발견하지 못할 수도 있고, 문제가 아닌 것을 문제라고 판단할 수도 있다.

개발 조직을 위한 가이드라인

  • 국제화/현지화 지원을 위한 개발 가이드라인
    예) C혹은 Java로 구현된 NLS 라이브러리(API) 및 매뉴얼
  • 번역 리소스 생성 및 전달 및 돌아온 결과 병합에 관한 프로세스 가이드라인
    예) 메시지 파일 혹은 Java property 파일 처리 방법

번역 조직들을 위한 가이드라인

  • 번역 리소스들의 포맷에 대한 가이드라인
     예) java.util.ResourceBundle 클래스 형식의 리소스 처리 방법
  • 번역 가능성(Transtability)에 대한 가이드라인.
      예) 고유명사, 기계명, 명령어 등은 절대 번역해서는 안 된다.

테스팅 조직들을 위한 가이드라인

  • 각 지역별, 플랫폼 별 환경 설정 가이드라인
    예) 리눅스에서 한국어 및 한국지역 환경 설정 방법
  • 오류 테스트 방법 가이드라인
    예) 번역 테스팅을 위한 테스트 케이스
  • 버그(문제점) 보고 방식에 대한 가이드라인
    예) 버그 리포트 형식

소프트웨어 현지화 구현 조직을 위한 가이드라인

  • 지역화 구현 개발 요청 및 진행 절차 가이드라인

연재를 마치며

소스 코드 예제가 없잖아?

개발자들은 문서에 소스 코드가 있으면, 소스 코드에 자연스럽게 눈이 먼저 가게 되는 습성이 있다. 그래서, 이와 같이 예제 소스 코드가 없는 글에 대해 잘 적응이 안 되었을 지도 모르겠다.  백마디 말보다, 몇 줄의 소스 코드로도 모든 것을 이해해 버리는 한국의 개발자들의 명성은 높다. 하지만, 반대로 "매뉴얼을 읽지 않는다"라는 악명도 함께 가지고 있다. 소프트웨어의 세계화, 소스 코드의 몇 줄을 먼저 치기 전에 반드시 개념을 익히기를 바란다.

소프트웨어의 세계화, 만만치 않구나!

 소프트웨어 세계화를 위한 조직이 이렇게 복잡한가 하고 반문하는 독자가 있을 수도 있다. 일견 그럴 수도 있다. 값비싼 국제화 프로세스 없이 필요할 때마다 아웃소싱으로 현지화를 수행할 수도 있다. 하지만, 냉정하게 말하면 그렇게 단순히 생각할 수 있는 이유가 바로 근본적으로 국제화를 해야 할 만큼 유명세를 탄 소프트웨어라는 것이 한국에 존재하지 않기 때문이기도 하다. 국제화/현지화는 대세이며 거스를 수 없다. 단지 우리가 그런 소프트웨어를 보유하고 있지 않을 뿐인 이 시점에서 개발자들은 현재 국제적인 명성을 얻고 있는 업체들의 "세계화" 노력을 특히 먼저 이해하고 받아들여야 한다.

NLS의 중요성을 알게 되었습니까?


지금까지 4회에 걸쳐, 오라클의 NLS 서비스 제공 방법과, 한글 환경에서 오라클 제품을 제대로 사용하는 데 있어 가장 문제가 되는 점을 짚어 보았다. 오라클의 NLS 지원은 전 세계에서 동시에 일하고 있는 수많은 NLS 전문가들에 의해 이루어진다. "특정 언어 버전"의 제품이 없다는 점에서 오라클은 진정한 "세계 버전"의 제품이라 할 수 있다. 하지만, 무엇보다도 시스템에 대해 최초 구성을 잘 하는 것이 중요하다. 최초에 잘못된 변수, 잘못된 캐릭터셋을 사용한다면, 계속 잘못된 데이터 조작 연산을 수행할 가능성이 높고, 시간이 흐르면 흐를 수록 이것은 수정하기 힘들다.  오라클 사의 직원이 이렇게 오라클의 NLS 지원에 대해 아주 훌륭하다고 입이 마르도록 칭찬한다 할 지라도, 결국 그 의미가 "오라클은 잘 만들어진 도구" 이상은 아니다. 필자는 이 네 번의 연재로부터 독자들이 오라클 이라는 도구에 대한 지식을 익히는 것도 중요하지만, 그보다는 소프트웨어에 있어 NLS 지원이 얼마나 중요하고 복잡한 요소인가에 대한 인식을 하게 되기를 바란다.

더 궁금한 사항이 있습니까?

만일 NLS의 핵심 개념들 중 더 깊이 알고 싶은 사항이 있다면 한국오라클 OTN 홈페이지의 NLS 포럼을 이용할 수 있다.

OTN 포럼 바로 가기

이 포럼에서 여러분들께서는 번역 관련 질문들을 포함해 NLS에 관련된 어떠한 질문도 할 수 있으며, 추가적인 연재에 대한 요구도 물론 할 수 있다. 그리고 이 글을 쓰고 있는 시점까지는 두 사람이 외로이(?) 답변을 열심히 하고 있으나, 이제 여러분들의 지식과 경험이 날로 쌓여 현장에 발견한 모든 지식을 함께 공유해 줄 수 있으리라 믿어 의심치 않으며 연재를 모두 마친다.


[출처] http://www.oracle.com/technology/global/kr/pub/columns/oracle_nls_4.html
이번 호에서는 오라클 GDK(Globalization Development Kit)과 자바에서 제공하는 소프트웨어 지역화 기능을 이용하여, 개발자들이 항상 필요할 때마다 편하게 사용할 수 있는 유틸리티를 만들어보도록 하겠다. 개발자들은 종종 아주 단순한 다국어 문제를 가지고 시간을 소비한다.  필요할 때마다 고민할 필요없이 항상 사용할 수 있는 NLS 유틸리티를 구축해 놓는다면, 가끔씩 깜짝 놀랄 만큼 빠른 생산성이 자신으로부터 나오게 된다는 사실에 놀랄 것이다.

  1. 개발 준비
    1. Oracle GDK(Globalization Development Kit)
    2. 기타 라이브러리
  2. 다국어 유틸리티 시작
    1. Oracle GDK가 없어도 준비할 수 있는 유틸리티로 워밍업하기
      1. 다국어 - 유니코드 변환
  3. 로케일 정보를 한 눈에 볼 수 있는 유틸리티 작성
    1. 헤더
    2. 모든 오라클의 로케일 구성을 나열하기
    3. 로케일 인스턴스 획득
    4. 언어 및 국가(영역)명 정보
      1. Oracle GDK
      2. Java
    5. 날짜 및 달력 정보
      1. Oracle GDK
      2. Java
    6. 시간대(Timezone) 정보
      1. Oracle GDK
    7. 통화 기호 및 숫자 표기 정보
      1. Oracle GDK
      2. Java
    8. 캐릭터셋 정보
      1. Oracle GDK
      2. Java
    9. 기타 정보
      1. Oracle GDK
    10. 정리하며
      1. 기타 있으면 유용한 유틸리티
      2. 전체 소스

연.재.순.서.

1회 : 오라클과 NLS의 찰떡궁합 들여다보기(1)
2회 : 오라클과 NLS의 찰떡궁합 들여다보기(2)
3회 : 오라클 GDK를 사용하여 깔끔한 다국어 개발 유틸리티를 만들자
4회 : 한글화된 오라클 제품, 그 이면의 비밀.

개발 준비

제대로 된 다국어 유틸리티를 만들기 위해서는 다음과 같은 준비물이 필요하다.
  • JDK 1.4.2
  • Oracle GDK library(orai18n.jar)
  • Oracle JDeveloper
  • 기타 라이브러리

Oracle GDK(Globalization Development Kit)

 첫 회에서 미리 언급한 바가 있지만, 오라클은 무려 140개 이상의 로케일(Locale)을 지원한다. 이는 오라클 기반으로 만들어진 애플리케이션은 140가지 이상의 다른 환경을 동시에 지원할 수 있다는 의미가 된다. 오라클을 설치하면 이미 각각의 로케일에 대한 정보(날짜, 숫자 형식, 타임존, 캐릭터셋, 통화 기호 등)가 모두 설치되게 된다. 여러분들이 NLS_LANG 변수값을 설정할 때 빈번하게 사용하는 "AMERICAN_AMERICA"나 "KOREAN_KOREA"는 이 많은 로케일 중의 두 가지일 뿐이다.

 문제는 이 많은 로케일 정보를 어떻게 열람하고 사용할 수 있는가 말이다.  로케일 정보를 단지 "열람"하는 것은 예전부터 오라클 데이터베이스에 포함되어 있던 "Oracle Locale Builder"로 가능하다. 이 툴은 로케일 정보를 열람하는 것 뿐 아니라, 새로운 로케일을 만들어낼 수 있는 강력한 툴이지만, 새로운 로케일을 만들어내는 것보다는 필요할 때마다 애플리케이션이 필요한 로케일 정보를 잘 사용할 수 있도록 해 주는 것이 더 필요하다.

그래서, 오라클은 10g부터 GDK(Globalization Development Kit)을 포함하고 있다. 이는 자바 기반의 애플리케이션이 로케일 정보를 각 클라이언트의 상황에 맞게 실행시에 제공할 수 있도록 프로그래밍할 수 있게 해 준다. OTN에서 따로 다운로드받아서 사용할 수 있으며, 사용을 위해 꼭 데이터베이스가 필요한 것은 아니다. 10g 데이터베이스가 가지고 있는 NLS 정보를 모두 가지고 있으므로, 웹 기반의 애플리케이션이라면 데이터베이스 없이 오라클의 NLS 정보를 이용할 수 있는 것이다.

오라클 10g 데이터베이스가 설치되어 있다면, 다름 jar 파일들을 CLASSPATH에 포함시키기만 하면 된다. GDK의 Java API 레퍼런스는 위의 OTN 링크에서 역시 찾을 수 있다.
  • $ORACLE_HOME/jlib/orai18n.jar
  • $ORACLE_HOME/jlib/orai18n-lcsd.jar

기타 라이브러리

오라클 JDeveloper에 포함되어 있는 ordhttp11.jar는 파일 업로드 처리에 유용한 클래스들을 포함하고 있다. 그리고 데이터베이스를 사용할 경우 JDBC 라이브러리도 필수다.
  • $ORACLE_HOME/ord/jlib/ordhttp11.jar
  • $ORACLE_HOME/jdbc/lib/classes12.jar

다국어 유틸리티 시작

Oracle GDK가 없어도 준비할 수 있는 유틸리티로 워밍업하기

다국어 - 유니코드 변환

학교에서 교수님이 다음과 같은 화면을 보여주면서 같은 기능을 하는 스크립트를 개발하라고 하면 어떻겠는가? 아마 개발자 여러분들은 쉽게 할 수 있을 것이다. 하나의 JSP와 몇 개의 Javascript만 있으면 이런 페이지는 쉽게 만들어낼 수 있다.  세 가지 입출력 함수의 기능은 다음과 같다.
  • 다국어('한')를 입력받아 유니코드 이스케이프된 문자('\uC624')나 HTML 페이지에서 사용할 수 있는 엔티티 형식('오')으로 변환한다
  • 유니코드 이스케이프된 문자를 다시 역으로 다국어로 출력한다
  • 엔티티 형식을 다시 다국어로 출력한다


<그림 : 다국어 - 유니코드 변환 페이지> * 오늘의 깜짝 지식: 甲骨文은 오라클이 중국에서 사용하는 회사명이다.

이 중 유니코드 이스케이프의 경우 JDK에 포함된 native2ascii라는 유틸리티로도 같은 결과를 얻을 수 있다. 위의 그림은 native2ascii의 간단한 사용 방법 또한 보여주고 있으며, 그 입력과 출력은 이 유틸리티와 동일하다. 다음은 첫 번 째 함수의 Javascript 소스 코드로 보는 바와 같이 매우 간단하다. 핵심이 되는 함수는 붉은 색으로 표시를 했으며, 나머지는 입출력 형식을 맞추는 코드가 되겠다.

function to_native2ascii()
{
var s = document.native2ascii.nativeStr.value;
var len = s.length;
var i;
var c;
var charval;
var result = "";
var resultHTML = "";

for(i=0; i < len; i++)
{
c = s.charCodeAt(i);
charval = s.charAt(i);
if(c < 128)
{
result = result + charval;
switch(charval)
{
case '&':
resultHTML = resultHTML + "&amp;";
break;
case '<':
resultHTML = resultHTML + "&lt;";
break;
case '>':
resultHTML = resultHTML + "&gt;";
break;
default:
resultHTML = resultHTML + charval;
}
}
else
{
result = result + "\\u"+c.toString(16).toUpperCase();
resultHTML = resultHTML + "&#" + c + ";";
}
}
document.native2ascii.OutputStr.value = result;
document.native2ascii.OutputStrHTML.value = resultHTML;
}

그렇다면, 역함수를 구현하는 것도 그리 어려운 일이 될 수가 없다. string.charCodeAt(i)의 역함수인 "String.fromCharCode()" 사용하면 된다. 본 편에 들어가기 전에 이런 간단한 소스를 소개한 것은 한 가지 강조하고픈 점이 있기 때문이다. 개발자는 생산성이 높아야 하고, 그 생산성은 JDeveloper와 같은 통합 툴 같은 것만 가지고는 해결되지 않는다. 늘 자신의 생산성을 향상시킬 무기를 가지고 있어야 한다. 이런 간단한 유틸리티가 얼마나 본인의 생산성에 도움이 될 것인지는 수치로 측정할 수는 없지만,  확실한 것은 한국어가 영어 알파벳을 사용하는 언어가 아닌 이상, 여러분들은 언젠가는 웹 기반의 한국어 또는 다국어 애플리케이션을 작성하며 "아쉬운 순간"을 맞이하게 될 것이라는 점이다. 전투를 위한 여러분의 진지를 구축한다고 생각하자.

로케일 정보를 한 눈에 볼 수 있는 유틸리티 작성

오라클이 제공하는 로케일 정보를 Java 애플리케이션에서 열람하고 사용할 수 있는 방법을 알기 위해서, Oracle Locale Builder와 같이 로케일을 선택하면 해당 로케일에 대한 정보를 보여주는 JSP 기반의 웹 페이지를 만들어보도록 하자. 단순히 Locale Builder와 같은 내용을 보여주면 재미가 없고 효용도 떨어지니 JDK에서 제공하는 로케일 정보를 함께 보여주는 것이 더 유용할 것 같다. 같은 로케일에 대해서도 오라클의 로케일 정보와 자바의 로케일 정보가 완전히 일치하지는 않는다.  이를 함께 비교하는 것도 재미있으며 때로는 뜻하지 않게 의문을 풀어주기도 한다.
  • 오라클 데이터베이스와의 입출력시 사용되어야 하는 NLS정보는 GDK의 로케일 정보와 일치한다. 가령 오라클 형식의 한국 날짜 기본 형식은 'RR/MM/DD'('05/09/20')이다. 하지만, JDK에서는 'YY. MM. DD.'('05. 09. 20.')을 사용한다. 우리가 데이터베이스로 하여금 특별히 날짜 형식을 지정해 주지 않은 채 날짜 정보를 삽입하고자 한다면 오라클 로케일 형식에 맞추어 전달해야 하는 것이다.

    SQL> insert into datetable values('05/06/01');

    1 개의 행이 만들어졌습니다.

    SQL> insert into datetable values('05. 06. 01.');
    insert into datetable values('05. 06. 01.')
                                 *
    1행에 오류:
    ORA-01830: 날짜 형식의 지정에 불필요한 데이터가 포함되어 있습니다.

    SQL> select next_day(sysdate,'Tuesday') from dual;
    select next_day(sysdate,'Tuesday') from dual
                            *
    1행에 오류:
    ORA-01846: 지정한 요일이 부적합합니다


    SQL> select next_day(sysdate,'화요일') from dual;

    NEXT_DAY
    --------
    05/09/13
  • 참고로 오라클의 경우에도 일반적인 자바 기반 애플리케이션은 단순히 JDK에서 제공하는 로케일 정보를 사용하는 경우가 많다.  사실 날짜 형식을 제외하고는 서로 값의 차이가 뚜렷하게 나는 경우는 별로 없다.
이제부터 만들어볼 로케일 열람 유틸리티는 단순히 하나의 JSP 스크립트이다. 하지만 길이가 약간 길기 때문에 여기서는 각각의 기능별로 쪼개어 살펴보고 마지막에 전체 스크립트를 다운로드받아서 각자 실행해 보는 형식으로 진행하겠다.



그림에서 보는 바와 같이 오라클이 제공하는 모든 로케일을 리스팅하고 그 중 하나를 선택하면, 해당 로케일 구성(언어 - 영역)에 따라 각종 NLS 정보를 보여주는 페이지가 되겠다. 리스팅된 로케일 구성은 실제 NLS_LANG값에 사용될 수 있는 "언어"와 "영역"의 구성이다.

헤더

다국어를 출력해야 하므로 UTF-8 기반의 JSP 페이지를 생성해야 하는 것은 당연하다. 그리고, Oracle GDK의 다양한 클래스들의 도움과 JDK의 유틸리티를 사용하기 위해 여러 패키지명을 등록한다.

<%@ page contentType="text/html;charset=utf-8"%>
<%@ page import="oracle.i18n.servlet.*,oracle.i18n.servlet.localesource.*,oracle.i18n.util.*,oracle.i18n.text.*,java.util.Locale,java.util.*,java.text.*"%>

모든 오라클의 로케일 구성을 나열하기

  사용자에게 오라클이 지원하는 모든 로케일 정보를 나열하고 여기서 원하는 로케일을 선택하도록 하기 위한 코드이다. 위의 그림에 있는 선택 상자가 이에 해당된다.

String[] allLocales = OraLocaleInfo.getAvailableLocale();

....

<select name="localeChoice" onChange="localeForm.submit();">
<%
for(int i=0; i < allLocales.length; i++)
{
if(allLocales[i].equals(lstr))
{
%>
<option value="<%=allLocales[i]%>" selected><%=allLocales[i]%></option>
<%
}
else
{
%>
<option value="<%=allLocales[i]%>"><%=allLocales[i]%></option>
<%
}
}
%>
</select>

로케일 인스턴스 획득

사용자가 원하는 로케일을 선택했을 경우, 일단 이에 해당하는 로케일 객체를 얻어내야 한다. 이 로케일 객체는 해당 로케일에 대한 모든 정보를 포함하고 있는 것으로 사실상 우리가 만드는 스크립트의 핵심 코드이며, 이후의 모든 메소드 호출은 로케일 정보를 단순히 사용하고 화면에 표현함에 다름 아니다.

    String lstr = request.getParameter("localeChoice");    // KOREAN_KOREA를 선택했다면
..
String lang = null;
String terr = null;
if(lstr != null)
{
int _idx;
_idx = lstr.indexOf('_');
lang = lstr.substring(0,_idx); // KOREAN
terr = lstr.substring(_idx+1); // KOREA
oraLocaleInfo = OraLocaleInfo.getInstance(lang,terr);
javaLocale = oraLocaleInfo.getLocale();
}
else
{
// 만일 사용자의 선택이 없는 경우, 사용자 브라우저의 로케일을 사용하기로 한다
javaLocale = request.getLocale();
oraLocaleInfo = OraLocaleInfo.getInstance(javaLocale);
javaLocale = oraLocaleInfo.getLocale();
lstr = oraLocaleInfo.getLanguage()+"_"+oraLocaleInfo.getTerritory();
}

/* 영문명과 각 국가의 고유 표기명을 병기하기 위해 다음과 같은 객체를 미리 생성해 두자. 예를 들어
국가명의 경우 영문으로 표기되는 이름도 있고, 각 국가의 언어를 사용하여 표기하는 국가명도 있다 */
OraDisplayLocaleInfo oraUSDisplayLocaleInfo = OraDisplayLocaleInfo.getInstance(Locale.US);
OraDisplayLocaleInfo oraDisplayLocaleInfo = OraDisplayLocaleInfo.getInstance(javaLocale);

만일 사용자가 아직 로케일을 선택하지 않은 경우, 사용자 브라우저에서 설정한 언어 정보를 바탕으로 로케일을 획득하기로 한다. 이 정보는 인터넷 익스플로러의 경우 "도구 - 인터넷 옵션 - 언어"를 차례대로 선택하여 설정할 수 있다. 그리고 넷스케이프의 경우 "Edit - Preference - Navigator - Languages"를 따라가며 선택할 수 있다.


<인터넷 익스플로러에서 언어 설정하기>



<넷스케이프에서 언어 설정하기>


언어 및 국가(영역)명 정보

각 사용자별로 위치한 나라의 이름이나, 사용자가 원하는 언어의 이름을 애플리케이션에 하드 코드로 저장하지 않고, GDK나 JDK로부터 직접 사용할 수 있다. 언어명의 경우 "Korean"과 같은 한국어의 영문 Display명 뿐 아니라, "한국어"(Native Display Name), "ko"(ISO 639표준: 2 char), "kor"(ISO 639-2 표준: 3 char)과 같이 여러 가지 포맷으로 획득할 수 있다. 국가명 역시 "Korea"뿐 아니라 "한국"(오라클 Native Display Name), "대한민국"(Java Native Display Name), "KR"(ISO3166) 등 여러 가지 포맷으로 얻어낼 수 있다.

Oracle GDK

오라클 GDK의 API를 통해 오라클 고유의 언어명과 국가명을 획득할 수 있다. 언어명은 특히 오라클의 번역된 메시지 리소스를 저장하는 파일명에 이용되고 있다. $ORACLE_HOME/rdbms/mesg 디렉토리에는 원 파일 명에 언어명이 붙어 있는 메시지 파일들이 많이 저장되어 있다.
2004-03-08  오후 02:28             5,632 dbvus.msb
2004-03-08 오후 02:27 5,632 dbvar.msb (Arabic)
2004-03-08 오후 02:27 6,144 dbvca.msb (Catalan)
2004-03-08 오후 02:27 6,144 dbvcs.msb (Czech)
2004-03-08 오후 02:27 6,144 dbvd.msb (German)
2004-03-08 오후 02:27 5,632 dbvdk.msb (Danish)
2004-03-08 오후 02:27 6,144 dbve.msb (Spanish)
2004-03-08 오후 02:27 6,144 dbvel.msb (Greek)
2004-03-08 오후 02:27 6,144 dbvf.msb (French)
2004-03-08 오후 02:27 6,144 dbvhu.msb (Hungraian)
2004-03-08 오후 02:27 6,144 dbvi.msb (Italian)
2004-03-08 오후 02:28 5,632 dbviw.msb (Hebrew)
2004-03-08 오후 02:27 5,632 dbvja.msb (Japanese)
2004-03-08 오후 02:28 5,632 dbvko.msb (Korean)
2004-03-08 오후 02:28 5,632 dbvn.msb (Norwegian)
....

// 언어명 정보

<td width="48%">
<TABLE BORDER=0>
<TR><TD>Oracle</TD><TD><%=LocaleMapper.getOraLanguage(javaLocale)%></TD></TR>
<TR><TD>Oracle Short Name</TD><TD><%=oraLocaleInfo.getShortLanguage()%></TD></TR>
<TR><TD>Display Name</TD><TD><%=oraUSDisplayLocaleInfo.getDisplayLanguage(javaLocale)%></TD></TR>
<TR><TD>Display Name(Native)</TD><TD><%=oraDisplayLocaleInfo.getDisplayLanguage(javaLocale)%></TD></TR>
</TABLE>
</td>


...
// 영역 이름 정보
<td width="48%">
<%
String oraTerr = oraLocaleInfo.getTerritory();
%>
<TABLE BORDER=0>
<TR>
<TD>Oracle Territory Name</TD><TD><%=oraTerr%></TD>
</TR>
<TD>Display Country</TD><TD><%=oraUSDisplayLocaleInfo.getDisplayCountry(javaLocale)%></TD>
</TR>
<TD>Display Country(Native)</TD><TD><%=oraDisplayLocaleInfo.getDisplayCountry(javaLocale)%></TD>
</TR>
<TD>Display Territory</TD><TD><%=oraUSDisplayLocaleInfo.getDisplayTerritory(oraTerr)%></TD>
</TR>
<TD>Display Territory(Native)</TD><TD><%=oraDisplayLocaleInfo.getDisplayTerritory(oraTerr)%></TD>
</TR>
</TABLE>
</td>

번역의 묘는 다음 마지막 연재에서 느낄 수 있으니 기대하시라. 오라클은 소프트웨어 업체 중 가장 진보적인 번역 시스템을 갖추고 있다.

Java

Java의 경우 ISO 표준명에 따른 언어 및 국가 기호를 따르고 있다. 오라클도 자바 클래스 리소스(java.utill.ResourceBundle 객체)나 프로퍼피 파일(.properties)의 경우 자바 애플리케이션에 의해 이용되므로, 리소스 명에 붙은 언어 및 국가명을 ISO 표준 기반으로 사용하고 있다. 앞서 얻어낸 Java Locale 객체를 기반으로 다음과 같이 언어 및 국가명을 얻어낼 수 있다.

//언어명 정보:
<TABLE BORDER=0>
<TR><TD>ISO639(2-char)</TD><TD><%=javaLocale.getLanguage()%></TD></TR>
<TR><TD>ISO639-2(3-char)</TD><TD><%=javaLocale.getISO3Language()%></TD></TR>
<TR><TD>Display Name</TD><TD><%=javaLocale.getDisplayLanguage(Locale.US)%></TD></TR>
<TR><TD>Display Name(Native)</TD><TD><%=javaLocale.getDisplayLanguage(javaLocale)%></TD></TR>
</TABLE>

//영역(국가)명 정보:
<TABLE BORDER=0>
<TR><TD>Java Territory</TD><TD><%=LocaleMapper.getJavaTerrFromOraTerr(oraTerr)%></TD></TR>
<TR><TD>ISO 3166(2-char)</TD><TD><%=javaLocale.getCountry()%></TD></TR>
<TR><TD>ISO 3166(3-char)</TD><TD><%=javaLocale.getISO3Country()%></TD></TR>
<TR><TD>Display Country</TD><TD><%=javaLocale.getDisplayCountry(Locale.US)%></TD></TR>
<TR><TD>Display Country(Native)</TD><TD><%=javaLocale.getDisplayCountry(javaLocale)%></TD></TR>
</TABLE>

다음은 오라클 로케일 정보(좌측)과 자바 로케일 정보(우측) 호출 결과를 보여준다.



날짜 및 달력 정보

Oracle GDK

여러분들은 java.text.DateFormat 객체를 이용하여 날짜를 스트링으로 변경하는 애플리케이션 코드를 작성해 보았을 것이다. java.text.SimpleDateFormat 객체를 이용하면 로케일에 관계없이 원하는 날짜 형식을 출력할 수 있고, java.text.DateFormat 객체로부터는 직접 로케일을 입력하여 원하는 로케일의 기본 날짜 형식을 얻어낼 수 있다. 그런데 같은 로케일이라고 해도 오라클 데이터베이스를 위한 날짜 형식은 이런 JDK의 날짜 형식과 일치하지 않는다. 나라별로 꼭 정확히 같은 형식의 날짜 형식을 표준으로 정해 놓았다고 볼 수도 없기 때문이다. 예를 들어 2005년 9월 20일을 "2005-9-20"이라고 표기하든, "2005/9/20"이라고 표기하든 연월일의 순서만 맞으면 우리 나라에서 사용되는 데 별 지장이 없다.

 오라클 소프트웨어를 위한 날짜 정보는 오라클의 소프트웨어에 포함이 되어 있으며, 한국어의 경우 일반적인 오라클의 표기법에 "연월일"의 순서를 한국에 맞게 조합한 결과라고 볼 수 있겠다. SQLPLUS같은 클라이언트에서 질의문을 생성할 때 사용할 날짜 포맷은 Oracle GDK에 의존하는 것이 좋다. Oracle GDK는 java.text.DateFormat 객체와 유사한 형식을 가진 OraDateFormat이라는 객체를 제공하며, 그 사용방법은 거의 동일하다. 한 가지 더 편리한 점은 TO_DATE와 같은 함수에서 사용할 수 있는 날짜 형식 또한 쉽게 얻을 수 있다는 것이다.
  • OraDateFormat.getDefaultDateFormatPattern(OraDateFormat.DEFAULT,oraLocaleInfo) : 한국의 경우 "RR/MM/DD" 반환

// 날짜, 날짜시간, 시간 표기 정보

OraDateFormat oradefaultDateFormat = OraDateFormat.getDateInstance(OraDateFormat.DEFAULT,oraLocaleInfo);
OraDateFormat orasimpleDateFormat = OraDateFormat.getDateInstance(OraDateFormat.SHORT,oraLocaleInfo);
OraDateFormat oralongDateFormat = OraDateFormat.getDateInstance(OraDateFormat.LONG,oraLocaleInfo);
OraDateFormat oradefaultDateTimeFormat = OraDateFormat.getDateTimeInstance(OraDateFormat.DEFAULT,oraLocaleInfo);
OraDateFormat orashortDateTimeFormat = OraDateFormat.getDateTimeInstance(OraDateFormat.SHORT,oraLocaleInfo);
OraDateFormat oralongDateTimeFormat = OraDateFormat.getDateTimeInstance(OraDateFormat.LONG,oraLocaleInfo);
OraDateFormat orashortTimeFormat = OraDateFormat.getTimeInstance(oraLocaleInfo);
...
<TD colspan=2><B>[Date Format]</B></td>
</TR>
<TR>
<TD>Default</TD>
<TD><%=OraDateFormat.getDefaultDateFormatPattern(OraDateFormat.DEFAULT,oraLocaleInfo)%><BR>
<B><%=oradefaultDateFormat.format(testDate)%></B></TD>
...

<TR>
<TD colspan=2><B>[Date/Time Format]</B></td>
</TR>
<TR>
<TD>Default</TD>
<TD><%=OraDateFormat.getDefaultDateTimeFormatPattern(OraDateFormat.DEFAULT,oraLocaleInfo)%><BR>
<B><%=oradefaultDateTimeFormat.format(testDate)%></B></TD>
</TD>
</TR>
....
<TD colspan=2><B>[Time Format]</B></td>
</TR>
<TR>
<TD>Default</TD>
<TD><%=OraDateFormat.getDefaultTimeFormatPattern(oraLocaleInfo)%><BR>
<B><%=orashortTimeFormat.format(testDate)%></B></TD>
</TD>
</TR>


// 년/월/일/요일 등 날짜 표기에 사용되는 단위 및 기호 정보
<%
OraDateFormatSymbols dateSymbols = new OraDateFormatSymbols(oraLocaleInfo);
%>
<TABLE BORDER=0>
<TR>
<TD>Months(From Jan.)</TD>
<TD>
<% // 월 이름
String[] months = dateSymbols.getMonths(); //1월, 2월, 3월 , January, February,..
for(int i=0; i < months.length; i++)
{
.....
<% // 짧은 월 이름
String[] shortMonths = dateSymbols.getShortMonths(); // 한국어는 동일, Jan, Feb
for(int i=0; i < shortMonths.length; i++)
{
...
<% // 요일명
String[] weekDays = dateSymbols.getWeekdays(); // 일요일, 월요일,
for(int i=0; i < weekDays.length; i++)
{
..
<% // 짧은 요일명
String[] shortWeeks = dateSymbols.getShortWeekdays(); // 일, 월, 화
for(int i=0; i < shortWeeks.length; i++)
{
..
<% // AM/PM 표기 방법
String[] ampmStrs = dateSymbols.getAmPmStrings(); // 오전, 오후
for(int i=0; i < ampmStrs.length; i++)
{
..
<% // 연대 표기 방법
String[] eras = dateSymbols.getEras(); // 기원전, 서기
..

// 한 주일의 첫 번 째 요일
int orafirstDayOfWeek = oraLocaleInfo.getStartDayOfTheWeek(); // 한국은 일요일, 프랑스는 월요일
switch(orafirstDayOfWeek)
{
case 0:
out.println("Sunday");
break;

Java

JDK에서 날짜 포맷을 얻어내는 방법은 비교적 잘 알려져 있다. 앞서 Oracle GDK를 설명하면서 언급한 java.text.DateFormat 객체를 사용하면 해당 로케일에 맞는 날짜 포맷을 쉽게 얻어낼 수 있다.  여기서는 구체적인 소스 코드를 소개하겠다. 아마, 이번 회에 나온 소스 코드들 중 실제 애플리케이션에서 가장 빈번하게 사용되는 코드일 것이다.

<%
DateFormat simpleDateFormat = DateFormat.getDateInstance(DateFormat.SHORT,javaLocale);
DateFormat mediumDateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM,javaLocale);
DateFormat longDateFormat = DateFormat.getDateInstance(DateFormat.LONG,javaLocale);
DateFormat fullDateFormat = DateFormat.getDateInstance(DateFormat.FULL,javaLocale);

DateFormat shortDateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT,javaLocale);
DateFormat mediumDateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,DateFormat.MEDIUM,javaLocale);
DateFormat longDateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG,javaLocale);
DateFormat fullDateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.FULL,javaLocale);

DateFormat shortTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT,javaLocale);
DateFormat mediumTimeFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM,javaLocale);
DateFormat longTimeFormat = DateFormat.getTimeInstance(DateFormat.LONG,javaLocale);
DateFormat fullTimeFormat = DateFormat.getTimeInstance(DateFormat.FULL,javaLocale);

%>
...
<TABLE BORDER=0>
<TR><TD colspan=2><B>[Date Format]</B></TD></TR>
<TR><TD>Short</TD><TD><%=simpleDateFormat.format(testDate)%></TD></TR>
<TR><TD>Medium</TD><TD><%=mediumDateFormat.format(testDate)%></TD></TR>
<TR><TD>Long</TD><TD><%=longDateFormat.format(testDate)%></TD></TR>
<TR><TD>Full</TD><TD><%=fullDateFormat.format(testDate)%></TD></TR>
</TABLE>
<BR>
...

<% // 날짜 표기에 필요한 각종 기호들을 포함한 객체
DateFormatSymbols jdateSymbols = new DateFormatSymbols(javaLocale);
%>
<TR>
<TD>Months(From Jan.)</TD>
<TD>
<% // "월" 이름
months = jdateSymbols.getMonths();
for(int i=0; i < months.length; i++)
{
...
<% // 짧은 "월" 이름
shortMonths = jdateSymbols.getShortMonths();
for(int i=0; i < shortMonths.length; i++)
{
..
<% // 짧은 요일 이름
shortWeeks = jdateSymbols.getShortWeekdays();
...
<% // 오전 오후를 표기하는 기호
ampmStrs = jdateSymbols.getAmPmStrings();

<% // 기원전, 후를 표기하는 기호
eras = jdateSymbols.getEras();

<% // 달력 객체 획득
Calendar localCalendar = Calendar.getInstance(javaLocale);
%>
<% // 한 주의 첫 번 째 요일
int firstDayOfWeek = localCalendar.getFirstDayOfWeek();
switch(firstDayOfWeek)
{
case Calendar.SUNDAY:
out.println("Sunday");
break;

..
<TD>Minumum day of the first week of a month</TD>
<TD><B><%=localCalendar.getMinimalDaysInFirstWeek()%></B></TD>


이상과 같이 가볍게 객체들의 메소드를 호출함으로써 해당 로케일의 날짜 및 달력 정보를 아주 쉽게 얻어낼 수 있다. 다음은 위의 메소드를 호출한 결과로 왼쪽은 Oracle GDK의 API를 사용한 결과이고, 오른쪽은 JDK를 이용한 결과이다. 오라클 데이터베이스에서는 특별히 TO_DATE를 이용하여 날짜 포맷을 지정해 주지 않으려면 왼쪽의 Oracle GDK가 보여주는 기본 날짜 형식을 사용해야 한다. 아래의 예는 프랑스어를 쓰는 프랑스 지역(FRENCH_FRANCE)의 날짜 포맷을 보여주고 있다.


<FRENCH_FRANCE: 프랑스의 날짜 및 시간 표기 정보>

따라서, 해당 로케일 정보를 가진 클라이언트에서 날짜를 입력하려면 TO_DATE를 사용하여 정확히 포맷을 제시하든지 아니면 기본 형식(DD/MM/RR)을 사용해야 한다. 다음의 예를 살펴보자. NLS_LANG을 아래와 같이 설정하여 프랑스어를 사용하는 프랑스 영역으로 설정하자. 두 번 째 SQL 문장은 프랑스에 맞는 기본 날짜 형식을 TO_DATE  없이 입력한 것으로, 문제없이 입력되는 것을 확인할 수 있다.
C:\Documents and Settings\jwryoo>set NLS_LANG=FRENCH_FRANCE.WE8ISO8859P1
SQL> insert into dtest values('2005/09/20');
insert into dtest values('2005/09/20')
*
ERREUR α la ligne 1 :
ORA-01861: le littΘral ne concorde pas avec le format chaεne de caractΦres


SQL> insert into dtest values('20/09/05');

1 ligne crΘΘe.

SQL> insert into dtest values(TO_DATE('2005/09/20','RRRR-MM-DD'));

1 ligne crΘΘe.

시간대(Timezone) 정보

Oracle GDK

오라클 GDK의 시간대 정보를 얻어내는 API는 특히 JDK와 비교했을 때 간략하다. JDK의 경우 해당 시스템의 기본 시간대 정보를 얻어내는 방법은 쉽지만, 특정 로케일의 시간대 정보를 얻어내는 것은 그 시간대를 상징하는 문자열 이름(Asia/Seoul)을 알아야 하므로 불편하다.

Timezone 객체를 얻어내고 사용할 때 주의해야 할 것은 해당 시간대가 DST(Daylight Saving Time)을 사용하고 있는지에 대한 여부를 확인하는 것이다. 그리고 영역이 아주 넓은 경우 한 로케일에 속하는 시간대가 매우 많을 수 있다. 예를 들어 AMERICAN_AMERICA의 경우 무려 8개의 Timezone 객체를 얻어낼 수 있으며, 이 중 "Pacific/Honolulu"를 제외한 나머지는 모두 DST를 사용한다.

<%      
TimeZone[] localTimezones = oraLocaleInfo.getLocalTimeZones();
for(int i=0; i < localTimezones.length; i++)
{
%>
<TABLE>
<TR><TD colspan=2><B>[<%=localTimezones[i].getID()%>]</B></TD></TR>
<TR><TD>Display Name: </TD><TD><%=oraDisplayLocaleInfo.getDisplayTimeZone(localTimezones[i])%></TD></TR>
<%
if(localTimezones[i].useDaylightTime())
{
%>
<TR><TD colspan="2" style="text-decoration: underline;">Standard Time</TD><TR>
<%
}
%>
<TR><TD>Short Name</TD><TD><%=localTimezones[i].getDisplayName(false,TimeZone.SHORT,javaLocale)%></TD></TR>
<TR><TD>Long Name(English)</TD><TD><%=localTimezones[i].getDisplayName(false,TimeZone.LONG,Locale.US)%></TD></TR>
<TR><TD>Long Name(Native)</TD><TD><%=localTimezones[i].getDisplayName(false,TimeZone.LONG,javaLocale)%></TD></TR>
<TR><TD colspan=2>&nbsp;</TD></TR>
<%
if(localTimezones[i].useDaylightTime())
{
%>
<TR><TD colspan="2" style="text-decoration: underline;">Daylight Saving Time</TD><TR>
<TR><TD>Short Name</TD><TD><%=localTimezones[i].getDisplayName(true,TimeZone.SHORT,javaLocale)%></TD></TR>
<TR><TD>Long Name(English)</TD><TD><%=localTimezones[i].getDisplayName(true,TimeZone.LONG,Locale.US)%></TD></TR>
<TR><TD>Long Name(Native)</TD><TD><%=localTimezones[i].getDisplayName(true,TimeZone.LONG,javaLocale)%></TD></TR>
<%
}
%>
</TABLE>

다음의 예는 아일랜드의 시간대 정보를 나타낸다. 영국과 같이 Greenwich 표준 시간대임을 알 수 있다. 유럽과 미국은 DST의 시작 및 종료 날짜가 조금씩 다르므로 Timezone.inDaylightTime(Date date)"과 같은 메소드를 통해 확인하는 것이 필요하다. JDK의 경우 따로 Timezone 객체를 사용하는 예를 보여줄 필요는 없을 것 같다. 위의 오라클 GDK의 예제가 반환하는 Timezone 객체는 JDK의 java.util.Timezone 객체로 결국 차이점은 해당 로케일에 속한 Timezone 객체를 얼마나 쉽게 얻어내는가 뿐이다.


<ENGLISH_IRELAND : 아일랜드의 시간대 정보>

통화 기호 및 숫자 표기 정보

Oracle GDK

Oracle GDK는 특별히 Currency 객체를 가지고 있지 않지만, JDK와 동등한 결과를 보여주는 데 문제가 없다. 그리고, 부가적으로 두 가지 이상의 통화가 사용되고 있는 경우까지 다룰 수 있다. 개인적으로는 시간대와 같이 java.util.Currency 객체들을 반환하는 것이 더 좋다고 생각이 되긴 하지만, 현재 상태로도 통화 기호라든지 통화명을 뽑아 쓰는 데에는 문제없다.

// 통화 정보 획득

<STRONG>Currency</STRONG>
</td>
<td width="48%">
<%
String oraCurrencySym = oraLocaleInfo.getCurrencySymbol();
%>
<TABLE border=0>
<TR><TD>Currency Symbol</TD><TD><B><%=oraCurrencySym%></B></TD></TR>
<TR><TD>Display Currency</TD><TD><B><%=oraDisplayLocaleInfo.getDisplayCurrency(oraCurrencySym)%></B></TD></TR>
<TR><TD>Currencies</TD><TD><B>
<%
String[] currencies = oraLocaleInfo.getLocalCurrencies();
for(int i=0; i < currencies.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=currencies[i]%>
<%
}
%>
</B></TD></TR>
<TR><TD>Default Currency Pattern</TD><TD><B><%=OraNumberFormat.getDefaultCurrencyFormatPattern(oraLocaleInfo)%></B></TD></TR>
<TR><TD>Currency Example</TD><TD>
<%
OraNumberFormat oracurrencyFormat = OraNumberFormat.getCurrencyInstance(oraLocaleInfo);
%>
<B>
<%=oracurrencyFormat.format(123456789L)%><BR>
<%=oracurrencyFormat.format(123456789.1234D)%><BR>
<%=oracurrencyFormat.format(-123456789.1234D)%>
</B>
</TD></TR>
</TABLE>
</td>

// 숫자 형식 정보

<TR><TD>Default Number Pattern</TD><TD><B><%=OraNumberFormat.getDefaultNumberFormatPattern(oraLocaleInfo)%></B></TD></TR>
<TR><TD>Number Example</TD><TD><B>
<%
OraNumberFormat oraNumberFormat = OraNumberFormat.getNumberInstance(oraLocaleInfo);
%>
<%=oraNumberFormat.format(123456789L)%><BR>
<%=oraNumberFormat.format(123456789.1234D)%><BR>
<%=oraNumberFormat.format(-123456789L)%><BR>

<%
OraDecimalFormatSymbols oraDecSymbols = new OraDecimalFormatSymbols(oraLocaleInfo);
%>
</B>
</TD></TR>
<TR><TD>Decimal Separator</TD><TD><B><%=oraDecSymbols.getDecimalSeparator()%></B></TD></TR>
<TR><TD>Grouping Used</TD><TD><B><%=((oraNumberFormat.isGroupingUsed()==true)?"Yes":"No")%></B></TD></TR>
<TR><TD>Grouping Separator</TD><TD><B><%=oraDecSymbols.getGroupingSeparator()%></B></TD></TR>
</TABLE>

Java

Oracle GDK에 비해 DecimalFormatSymbols 객체가 아주 풍부한 내용을 담고 있음을 알 수 있다. 다국어 기반의 개발자라면 숫자를 표기할 때 "똑같은 소스 코드로 해당 나라의 형식에 맞게끔 출력될 수 있는" 프로그램을 만들 수 있어야 할 것이다. 한국어만을 기반으로 하는 것이 아니라, 중국, 일본, 태국 등 아시아, 그리고 유럽에 걸쳐 서비스를 하려면, 여기에 사용되는 클래스들은 항상 머릿속에 넣어두어야 한다.

// 통화 기호 정보

<%
Currency javaCurr = Currency.getInstance(javaLocale);
%>
<TABLE border=0>
<TR><TD>Currency Symbol</TD<TD><B><%= javaCurr.getSymbol()%></B></TD></TR>
<TR><TD>Currency Name(ISO4217)</TD<TD><B><%= javaCurr.toString()%></B></TD></TR>
<TR><TD>Currency Code</TD<TD><B><%= javaCurr.getCurrencyCode()%></B></TD></TR>
<TR><TD>Currency Example</TD<TD><B>
<%
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(javaLocale);
%>
<%=currencyFormat.format(123456789L)%><BR>
<%=currencyFormat.format(-123456789L)%><BR>
<%=currencyFormat.format(-123456789.55)%>

// 숫자 형식 정보
        <TABLE BORDER=0>
<TR><TD>Number Example</TD><TD>
<B>
<%
NumberFormat numberFormat = NumberFormat.getNumberInstance(javaLocale);
%>
<%=numberFormat.format(123456789L)%><BR>
<%=numberFormat.format(123456789.1234D)%><BR>
<%=numberFormat.format(-123456789L)%>

</TD>
</TR>
<TR><TD>Integer Example</TD><TD><B>
<%
NumberFormat intFormat = NumberFormat.getIntegerInstance(javaLocale);
%>
<%=intFormat.format(123456789L)%><BR>
<%=intFormat.format(-123456789L)%>
</B></TD></TR>
<TR><TD>
Percentage Example</TD><TD><B>
<%
NumberFormat pcFormat = NumberFormat.getPercentInstance(javaLocale);
%>
"0.4" = <%=pcFormat.format(0.4)%><BR>
"0.9999" = <%=pcFormat.format(0.9999)%><BR>
"-0.5" = <%=pcFormat.format(-0.5)%>
</B></TD></TR>
<%
DecimalFormatSymbols decSymbols = new DecimalFormatSymbols(javaLocale);
%>
<TR><TD>Decimal Separator</TD><TD><B><%=decSymbols.getDecimalSeparator()%></B></TD></TR>
<TR><TD>Grouping Separator</TD><TD><B><%=decSymbols.getGroupingSeparator()%></B></TD></TR>
<TR><TD>Zero Digit</TD><TD><B><%=decSymbols.getZeroDigit()%></B></TD></TR>
<TR><TD>Percent</TD><TD><B><%=decSymbols.getPercent()%></B></TD></TR>
<TR><TD>Mil-Percent</TD><TD><B><%=decSymbols.getPerMill()%></B></TD></TR>
<TR><TD>NaN(Not A Number)</TD><TD><%=decSymbols.getNaN()%></TD></TR>
<TR><TD>Infinity</TD><TD><B><%=decSymbols.getInfinity()%></B></TD></TR>
<TR><TD>Minus Sign</TD><TD><B><%=decSymbols.getMinusSign()%></B></TD></TR>
<TR><TD>Pattern Separator</TD><TD><B><%=decSymbols.getPatternSeparator()%></B></TD></TR>
<TR><TD>Decimal Separator</TD><TD><B><%=decSymbols.getDecimalSeparator()%></B></TD></TR>
<TR><TD>Monetary Decimal Separator</TD><TD><B><%=decSymbols.getMonetaryDecimalSeparator()%></B></TD></TR>
</TABLE>

다음은 독일어를 사용하는 독일 지역(GERMAN_GERMANY)의 환율 및 숫자 표기 정보를 나타낸 것이다. 우리와는 정 반대로 소수점 표기에 콤마(,)를, 천 단위로 숫자를 그룹핑할 때 마침표(.)를 사용하고 있는 것을 볼 수 있다. 때에 따라서는 정말 엉뚱하게 숫자의 값이 전달될 수 있으므로 특히 조심해야 한다는 것을 알려주는 좋은 예이다.


<GERMAN_GERMANY: 독일의 환율 및 숫자 표기 정보>

캐릭터셋 정보

Oracle GDK

Oracle GDK는 해당 로케일의 언어를 저장할 수 있는 오라클 데이터베이스의 캐릭터셋을 쉽게 알아낼 수 있는 방법을 각 운영체제 별로 제공한다. 그렇다고 UNIX 운영체제에 WINDOWS 코드페이지 기반의 캐릭터셋을 설치할 수 없다는 것은 아니다. 한국어의 경우 UNIX 캐릭터셋으로 KO16KSC5601과 AL32UTF8이 출력되지만, 그렇다고 UNIX 운영체제에 KO16MSWIN949 캐릭터셋의 데이터베이스 인스턴스를 생성할 수 없는 것은 아니다.

        <B>ALL:</B><BR>
<%
// 모든 캐릭터셋
String[] allCharsets = oraLocaleInfo.getLocalCharacterSets();
for(int i=0; i < allCharsets.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=allCharsets[i]%>
<%
}
%>
<BR><BR>
<B>UNIX:</B><BR>
<%
// UNIX 운영체제
String[] unixCharsets = oraLocaleInfo.getLocalCharacterSets(LocaleMapper.UNIX);
for(int i=0; i < unixCharsets.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=unixCharsets[i]%>
<%
}
%>
<BR><BR>
<B>WINDOWS:</B><BR>
<%
// Windows 운영체제
String[] winCharsets = oraLocaleInfo.getLocalCharacterSets(LocaleMapper.WINDOWS);
for(int i=0; i < winCharsets.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=winCharsets[i]%>
<%
}
%>

Java

일반적인 다국어 지원 애플리케이션에서는 오라클 캐릭터셋보다 IANA 표준 캐릭터셋 이름이나 자바 캐릭터셋 이름들이 더 빈번히 사용될 것이다. KO16MSWIN949라는 캐릭터셋명은 오로지 데이터베이스 인스턴스를 생성할 때나, NLS_LANG 변수값을 지정할 때 사용될 뿐, 다른 애플리케이션에서는 MS949라는 자바 캐릭터셋 명을 사용해야 한다.
  • export NLS_LANG=KOREAN_KOREA.KO16MSWIN949 (O)
  • export NLS_LANG=MS949 (X)
  • Reader r = new InputStreamReader(new FileInputStream("file.txt"),"KO16MSWIN949"); (X)
  • Reader r = new InputStreamReader(new FileInputStream("file.txt"),"MS949"); (O)
아래의 소스 코드는 자바 로케일 객체를 기반으로 IANA 또는 자바 캐릭터셋 이름들을 출력하는 코드이다. Java 란에 출력이 되지만, 소스 코드를 들여다보면 Oracle GDK를 이용했음을 알 수 있다. Oracle GDK는 이렇듯 오라클의 로케일 정보와 JDK의 로케일 정보에 대해 훌륭한 매핑을 제공하고 있다.

      <B>[IANA Standard Charsets]</B><BR>
<B>UNIX:</B><BR>
<%
String[] unixIANAcharsets = LocaleMapper.getIANACharSetFromLocale(LocaleMapper.UNIX,javaLocale);
for(int i=0; i < unixIANAcharsets.length; i++)
{
if(i > 0)
out.println(",");
String fontUrl = "/tools/locale/fonttest.jsp?charset="+unixIANAcharsets[i]+"&lang="+javaLocale.getLanguage()+"&cont="+javaLocale.getCountry();
%>
<!--<a href="<%=fontUrl%>" target=_blank><%=unixIANAcharsets[i]%></a>-->
<%=unixIANAcharsets[i]%>
<%
}
%>
<BR><BR>
<B>WINDOWS:</B><BR>
<%
String[] winIANAcharsets = LocaleMapper.getIANACharSetFromLocale(LocaleMapper.WINDOWS,javaLocale);
for(int i=0; i < winIANAcharsets.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=winIANAcharsets[i]%>
<%
}
%>
<BR><BR>
<B>EMAIL_UNIX:</B><BR>
<%
String[] unixEmailIANAcharsets = LocaleMapper.getIANACharSetFromLocale(LocaleMapper.EMAIL_UNIX,javaLocale);
for(int i=0; i < unixEmailIANAcharsets.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=unixEmailIANAcharsets[i]%>
<%
}
%>
<BR><BR>
<B>EMAIL_WINDOWS:</B><BR>
<%
String[] winEmailIANAcharsets = LocaleMapper.getIANACharSetFromLocale(LocaleMapper.EMAIL_WINDOWS,javaLocale);
for(int i=0; i < winEmailIANAcharsets.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=winEmailIANAcharsets[i]%>
<%
}
%>

<BR><BR>
<B>[Java Charsets]</B><BR>
<B>JAVA_UNIX:</B><BR>
<%
for(int i=0; i < unixCharsets.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=LocaleMapper.getJavaCharacterSet(LocaleMapper.ORACLE,unixCharsets[i])%>
<%
}
%>

<BR><BR>
<B>JAVA_WINDOWS:</B><BR>
<%
for(int i=0; i < winCharsets.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=LocaleMapper.getJavaCharacterSet(LocaleMapper.ORACLE,winCharsets[i])%>
<%
}
%>

다음 그림은 위의 소스 코드의 실행 결과로, 일본어를 사용하는 일본에 대한 캐릭터셋 정보를 나타내 주고 있다.


<JAPANESE_JAPAN:  일본에 대한 캐릭터셋 정보>

기타 정보

Oracle GDK

기타 Oracle GDK에서는 해당 언어를 좌측에서 우측으로 표기할 지, 우측에서 좌측으로 표기할 지를 판별할 수 있는 메소드와 함께 SQL 문장에서 사용할 수 있는 정렬 방식의 이름을 제공해 준다.

// 널리 알려진 대로 Arabic의 경우 Right To Left 표기 방식이다.
<%
int wDir = oraLocaleInfo.getWritingDirection();
%>
<%=((wDir==0)?"LEFT to RIGHT":"RIGHT to LEFT")%>

// 정렬 방식
<%
String[] sorts = oraLocaleInfo.getLocalLinguisticSorts();
for(int i=0; i < sorts.length; i++)
{
if(i > 0)
out.println(",");
%>
<%=sorts[i]%>
<%
}
%>

다음은 KOREAN_KOREA 로케일에 대해 실행한 결과로, 한글은 물론 좌측에서 우측으로 표기하는 것이 맞다. 그리고 한글 정렬을 위해서는 "KOREAN_M"을 사용할 수 있는데, 이에 대해서는 연재 첫 회를 참고하기 바란다. 하지만, 기본적으로는 BINARY 정렬이 된다는 것을 이 API로는 알기 어렵다. 그리고 로케일에 무관하게 "UNICODE_BINARY" 정렬 방식을 사용할 수도 있다.


<한국어의 쓰기 순서와 정렬 방식>

정리하며

기타 있으면 유용한 유틸리티

이 외에도 다음과 같은 유틸리티를 마련하면 매우 유용하다
  • 바이트 코드와 실제 글자 매핑 : UTF8의 " 236 152 164 235 157 188 237 129 180" == EUC-KR "191 192 182 243 197 172" =="오라클"
  • 해당 글자가 특정 캐릭터셋에서 지원되는지 여부를 확인하는 툴 : oracle.i18n.util.OraSQLUtil를 이용하여 간단히 구현할 수 있다.
  • 캐릭터셋에 지원되지 않는 글자를 삽입하기 위해 유니코드 이스케이프로 변환하는 유틸리티 : racle.i18n.util.OraSQLUtil.escapeUNISTR로 간단히 구현할 수 있다. "오라클" == UNISTR('\c624\b77c\d074')
  • 파일 인코딩을 변환하는 유틸리티(현재 WINDOWS949로 인코딩된 일반 한글 텍스트 파일을 UTF8로 변환하기)

전체 소스

자, 그럼 이제 전체 소스를 다운로드 받아 본인의 JSP 엔진에 설치해 보는 과정을 해 보는 것은 어떨까? 한 번 설치해 두면 두고두고 써먹을 수 있는 것 중 하나가 바로 이 NLS 정보 열람 유틸리티이다.

실행하기 위해 필요한 라이브러리는 이미 "개발 준비" 섹션에 소개한 바 있다. 단순히 설치로만 그칠 게 아니라, 이리저리 변경해 보고 Oracle GDK와 JDK의 레퍼런스 문서를 보며 사용되지 않은 API를 추가해 보는 것도 재미와 가치를 더할 것이다.

GDK는 독보적 존재

Java 기반의 다국어 지원 애플리케이션 개발 킷이라는 것은 어떻게 보면 참 독보적인 존재이다. 공개 소프트웨어 개발 포럼에서나 개발될 것 같은 라이브러리인데, 상업용 소프트웨어 개발 회사가 리소스를 투자해 개발하고 배포하며 자사의 대표적인 소프트웨어에도 포함시키고 있다는 것은 대형 소프트웨어 업체가 가진 매력이자 힘이라고 볼 수 있다. 첫 회에 언급했듯 "한국어 버전", "중국어 버전"의 오라클 소프트웨어는 존재하지 않는다. 오로지 "지구 버전"의 오라클 소프트웨어가 있을 뿐이다. 글로벌 시대를 부정할 수는 없는 법, 갈 수록 GDK와 같은 라이브러리는 보이지 않는 곳에서 힘을 발휘할 것이다. 오라클 데이터베이스 없이도 즐겁게 사용할 수 있는 오라클의 로케일 정보 라이브러리, GDK를 이용하여 "즐프"하시기를 바라며 이만 마치겠다.


[출처] http://www.oracle.com/technology/global/kr/pub/columns/oracle_nls_3.html



이번 회는 캐릭터셋을 변경하는 작업에 대한 준비와 그 방법에 대한 것을 알아보고자 한다. 캐릭터셋 변경은 미래의 시스템 확장과 개발의 용이함을 위해 권장되는 작업이지만, 실제 위험성이 크기 때문에 매우 조심해야 한다. 그래서인지 OTN의 NLS 포럼에서도 빈번히 등장하는 질문이기도 한 "캐릭터셋 변경"에 대해 이번 회에서 집중적으로 다루어보기로 하겠다.

  1. 잘못된 캐릭터셋을 사용해온 시스템, 치료해야 하나?
    1. 캐릭터셋 오용의 예
    2. 권장 사항
      1. 1. 바로잡기의 필요성을 인식하라
      2. 2. 절대 함부로 변경하지 말라
  2. 캐릭터셋을 변경 방식과 위험성
    1. 캐릭터셋 변경이란?
      1. 변경 케이스 1) 캐릭터셋 딕셔너리 정보 변경 + 데이터 불변
      2. 변경 케이스 2) 캐릭터셋  딕셔너리 정보 변경 + 데이터 변경
    2. 캐릭터셋 변경의 위험성
      1. 1) 데이터 절삭
      2. 2) 데이터 깨짐
        1. US7ASCII 데이터베이스의 한글 데이터를 exp/imp를 이용하여 KO16MSWIN949 또는 UTF8로 마이그레이션
        2. KO16KSC5601 데이터베이스의 한글 데이터를 exp/imp를 이용하여 KO16MSWIN949 또는 UTF8로 마이그레이션
      3. 3) 애플리케이션 오동작
  3. 캐릭터셋 변경의 실제
    1. ALTER DATABASE 명령을 이용한 딕셔너리 변경
      1. 절차
    2. 데이터 마이그레이션
      1. 준비작업
      2. CSSCAN 실행
        1. 1) CSSCAN 설치
        2. 2) CSSCAN 구동
      3. exp/imp를 이용한 변경
      4. CSALTER를 이용한 변경
  4. 글을 마치며


연.재.순.서.

1회 : 오라클과 NLS의 찰떡궁합 들여다보기(1)
2회 : 오라클과 NLS의 찰떡궁합 들여다보기(2)
3회 : 오라클 GDK를 사용하여 깔끔한 다국어 개발 유틸리티를 만들자
4회 : 한글화된 오라클 제품, 그 이면의 비밀.

잘못된 캐릭터셋을 사용해온 시스템, 치료해야 하나?

캐릭터셋 오용의 예

캐릭터셋과 전혀 맞지 않는 데이터를 저장하고 검색할 수 있을까? 정답은 당연히 없다. 그렇지만, 현실적으로 많은 시스템의 데이터베이스에 그런 데이터가 저장되어 있을 것이라고 믿고 있다.
  1. 오용 1) US7ASCII 데이터베이스에 한글을 저장한 경우
  2. 오용 2) US7ASCII 데이터베이스에 한글, 중국어, 일본어 등 다양한 언어를 저장한 경우
  3. 오용 3) KO16KSC5601 데이터베이스에 확장 한글(비완성형)을 저장한 경우
이 세 가지 경우는 잘못된 캐릭터셋을 사용하고 있는 경우 중 문제가 발생할 만한 대표적인 경우들이다. 다음 표는 이 경우들에 대해 발생할 수 있는 문제점과 취할 수 있는 치료방법을 요약하고 있다.


문 제 감시 시점
지 속적 사용 가능 여부
해 결책
오용1
1) 다른 정상적인 데이터베이스와 DBLINK로 연결시 한글 전달 실패
2) 캐릭터 관련 함수들의 예측할 수 없는 결과
3) 다른 캐릭터셋으로 변경 시도시 데이터 마이그레이션 실패
4) exp/imp를 사용해 다른 데이터베이스로 데이터 마이그레이션 실패
1) 똑같이 잘못된 구성을 가진 데이터베이스 집합 시스템에서는 지속 운영 가능

2) 정상적인 데이터베이스와 정상적인 통신은 불가능함

3) 정상적은 데이터베이스로 업그레이드시 난관 봉착
Full Backup이 필요없다고 절대 말하지 말라!

오라클 엔지니어로 하여금 캐릭터셋을 강제로 KO16MSWIN949로 변경하게 함으로써 해결할 수 있다.

단, 변경 전에 정말 데이터들이 순수하게 KO16MSWIN949라는 한 캐릭터셋으로 커버되는 것이 맞는지 확인해야 한다. 그렇지 않으면 "예 2"의 경우가 된다.

이런 캐릭터셋 변경은 오라클 엔지니어나, 지원 계약을 맺은 고객이 충분히 엔지니어로부터 위험성에 대해 설명을 들은 후에야 시도할 수 있다는 사실을 잊지 말기 바란다. 그렇지 않은 시도는 누구도 책임져 주지 않는다.

캐릭터셋 변경 후 애플리케이션의 디버깅은 필수이다.
오용2
1) 다른 데이터베이스와 DBLINK로 연결시 한글 전달 실패
2) 캐릭터 관련 함수들의 예측할 수 없는 결과
3) 다른 캐릭터셋으로 변경 시도시 실패
4) exp/imp를 사용해 다른 데이터베이스로 데이터 마이그레이션 실패
1) 똑같이 잘못된 구성을 가진 데이터베이스 집합 시스템에서는 지속 운영 가능

2) 정상적인 데이터베이스와 정상적인 통신은 불가능함

3) 정상적은 데이터베이스로 업그레이드시 난관 봉착
최악의 케이스. 다양한 캐릭터셋이 한자리에 모인 잔치집으로, 어떤 한 캐릭터셋으로의 강제 변환이 의미없다.

그나마 각 언어별로 데이터를 구분하여 조회할 수 있다면, 한국어는 한국어별로, 중국어는 중국어별로 physical OS file로 덤프를 떠서 해결하기를 권장한다.

각 언어별로 Manual하게 고쳐나가는 것 밖에는 도리가 없다.

Physical dump file은 SQL Loader를 통해 새로운 UTF8 데이터베이스에 안전하게 로딩할 수 있다. 물론 지난 호에 나온 대로 각 언어별로 NLS_LANG변수값은 확실히 설정해주어야 한다.

SQL Loader를 사용할 때 NLS_LANG값은:
  • 한국어 파일 업로드시 : .KO16MSWIN949
  • 중국어간체 : .GB2312
  • 중국어번체 : .ZHT16MSWIN950 또는 .BIG5

이런 내용은 자신이 없거나, 책임질 수 있는 위치가 아니면, 절대 함부로 시도해서는 안된다.

데이터베이스를 제대로 고쳤을 경우에는 애플리케이션도 재개발 수준의 디버깅을 해야 한다.
오용3
1) exp/imp를 사용해 다른 데이터베이스로 데이터 마이그레이션 실패
2) 가끔 이런 데이터베이스를 기반으로 한 애플리케이션에서 깨진 글자들을 발견하게 됨(종종 우리나라 게시판들에서 볼 수 있는 현상)
3) 운영자들이 자신의 시스템은 정상이라고 생각하는 경우가 많이 감지가 어려움
4) KO16KSC5601(완성형)이 우리나라 한글을 대표하는 캐릭터셋이라는 인식이 문제
1) 일부 비완성형 한글 데이터들의 처리를 주의하면 지속 사용할 수 있고, ,다른 KO16MSWIN949, UTF8기반의 정상적인 한글 데이터베이스와도 어느 정도 통신이 가능하다

KO16KSC5601에서 KO16MSWIN949로 캐릭터셋을 변경한다. KO16MSWIN949는 KO16KSC5601의 수퍼셋이므로 이러한 변경은 자연스럽다. 가장 쉽게 해결될 수 있는 케이스라고 할 수 있다.

그렇지만, 여전히 캐릭터셋 변경이라는 것은 위험하므로, 반드시 지원을 받아야 하는 것은 필수이다.


권장 사항

1. 바로잡기의 필요성을 인식하라

잘못된 캐릭터셋을 가진 데이터베이스를 계속 사용하는 것은 결국 잘못된 애플리케이션 개발을 유도해 전체적으로 잘못된 요소들만으로 이루어진 시스템이 되고 만다. 초기 구성의 부적절함으로 인해 특히 개발자가 고생할 수 있다. 개발자의 입장에서는 KO16MSWIN949나 UTF8기반이 아닌, US7ASCII 데이터베이스를 기반으로 한글을 입출력하는 프로그램을 개발하도록 강요받는다면 결국 억울하게 부가적인 업무를 전가받는 것이다.

캐릭터셋 변경은 비단 데이터베이스 자체 뿐 아니라, 애플리케이션을 비롯한 전체 시스템에 영향을 주기 때문에 경우에 따라 단순한 작업이 아니며, 짜투리 시간을 활용하여 감행할 수 있는 사항이 아니다. 적절한 시기를 선택해야 한다.

여러분이 자바 기반의 웹 개발자라면 다음과 같은 코드를 사용하여 개발한 적이 있는가?

저장:
String p_UserComment = new String(request.getParameter("comment").getBytes("KSC5601"),"8859_1");
pstmt.setString(1,p_UserComment);
pstmt.executeUpdate();

조회:
if(resultSet.next())
{
    String v_UserComment = new String(resultSet.getString("comment").getBytes("8859_1"),"KSC5601");
..
{

이런 억지스런 인코딩의 변환이야말로 잘못된 구성으로 인해 개발자들이 부가적으로 고생하는 단적인 예이다.  한글을 쓰든, 중국어든, 베트남어 기반의 애플리케이션이든, 올바른 구성의 시스템상에서는 저런 식의 코드 변환이 필요할 리가 없다.

꼬여진 개발은 결국 가까운 미래에 "확장 불가", "마이그레이션 불가"라는 벽에 부닥칠 수 있다.

2. 절대 함부로 변경하지 말라

 여러분들 중에 함부로 Production(Live) Database를 변경할 만한 경솔함과 만용을 가진 사람이 있을 거라고 생각해서 이를 언급하는 것은 아니다. 하지만, 제아무리 실력이 뛰어난 DBA라고 할 지라도, 데이터 전체를 무력화 시킬 수 있는 작업을 홀로 감행하지 않기를 거듭 강조하고 싶다. 반드시 적어도 다음 사람들이 연관되고 협력해야 한다
  • 해당 사용업체의 DBA
  • 의사결정 권한을 가지고, 시스템 전체의 운영을 책임지는 책임자
  • 오라클의 지원 엔지니어

캐릭터셋을 변경 방식과 위험성

캐릭터셋 변경이란?

일반적으로 우리가 "캐릭터셋을 변경한다"고 표현하는 작업은 사실 다음 두 가지 작업을 동시에 의미한다. 따라서, 이 말이 사용될 때에는 다음 두 가지 중 어떤 의미를 가리키는지 확실히 짚고 넘어갈 필요가 있다.

변경 케이스 1) 캐릭터셋 딕셔너리 정보 변경 + 데이터 불변

캐릭터셋 변경 시도 시점을 기준으로, 데이터베이스에 저장되어 있는 데이터 자체는 전혀 변경하지 않은 채,  딕셔너리에 있는 캐릭터셋 정보만 변경하는 작업이다.  일반적으로 캐릭터셋에 맞게 데이터를 잘 저장시켜 온 데이터베이스에는 사용할 수 없는 방법이다. 예를 들어 KO16MSWIN949 캐릭터셋을 가진 데이터베이스에 한글을 Windows-949의 코드페이지에 맞게 잘 저장하고 사용해 왔다면 이 데이터베이스의 딕셔너리 정보만을 UTF8으로 바꾸어서는 전혀 엉뚱한 결과를 얻게 되는 것이다. 주로 다음과 같은 상황에서 사용할 수 있다.
  • 비어있는(empty) 데이터베이스 인스턴스 : DBCA(Database Configuration Assistants)를 이용해 데이터베이스 인스턴스를 생성할 때, 캐릭터셋을 잘못 선택했다면, 도중에 작업을 중단하는 방법도 있지만(사실 중단하기를 권장한다),  생성을 마친 후 캐릭터셋 정보를 변환함으로써 문제를 해결할 수 있는 경우가 있다. KO16MSWIN949로 생성해야 할 데이터베이스를 KO16KSC5601로 설정하여 생성했다면, 생성을 마친 후 간단한 절차를 거쳐 KO16MSWIN949로 변경할 수 있는 것이다.

  • 캐릭터셋과는 전혀 엉뚱하고도 다른 캐릭터셋으로 인코딩된 정보를 강제 저장해 온 인스턴스 : 이 경우가 상당히 많은데, 데이터베이스 캐릭터셋은 US7ASCII밖에 없는 것으로 생각을 했던 것일까? US7ASCII은 그 이름만 보아도 절대 한글을 저장할 수 없을 것 같은 캐릭터셋인데, 여기에 한글 데이터를 버젓이 저장해온 시스템들이 상당히 많다. 왜 이런 일이 발생할 수 있는가에 대해서는 지난 연재의 NLS_LANG 변수 코너를 읽어보고 생각해 보기 바란다. 캐릭터셋은 US7ASCII이지만, 실제로는 완성형 + 한글 윈도우즈 코드 페이지(KO16MSWIN949)의 데이터를 저장해 온 경우, 데이터는 현재 상태에서 가공하지 않은 채, 딕셔너리에 있는 캐릭터셋 정보만을 강제로 KO16MSWIN949로 변환하는 방식으로 문제를 해결할 수 있다.
"ALTER DATABASE CHARACTER SET" 명령어가 이에 해당하며, 여기에 대해서는 다음 섹션에서 자세한 절차를 다루도록 하겠다.

변경 케이스 2) 캐릭터셋  딕셔너리 정보 변경 + 데이터 변경

캐릭터셋의 변경과 함께 데이터베이스가 가지고 있는 데이터의 인코딩까지도 변경하는 마이그레이션 작업을 포함하는 경우로, "변경 케이스 1"에 비해 상당한 시간이 소요되는 방대한 작업이며 그에 따라 위험성이 크다. 기존 데이터베이스의 백업은 물론 당연하며, 새로운 캐릭터셋을 가진 새 데이터베이스 인스턴스를 생성하여 그곳에 현재 데이터베이스로부터 데이터를 마이그레이션을 하는 것이 가장 안전하다. 이 경우 보통 exp/imp를 사용하여 해결할 수 있다. 현존하는 데이터베이스의 데이터를 직접 변경하려고 할 경우를 대비해 오라클에서는 CSSCAN이라는 툴을 제공하고 있다.

캐릭터셋 변경의 위험성

"내가 책임지고 변경하겠소!"

일반적으로 존재하는 데이터베이스의 캐릭터셋을 변경하는 작업은 상당히 위험하다. 그래서, 현업에서는 누구도 쉽사리 총대를 매고 이 명령어를 수행하려고 들지 않는다. 괜한 용기를 부렸다가는 돌이킬 수 없는, 또는 돌이키기가 너무나 고통스러운 상황을 맞이할 수 있는 게 이런 작업이다.

실제로 데이터베이스 캐릭터셋을 변경할 때 대략 다음과 같은 문제가 발생할 수 있다.

1) 데이터 절삭

 KO16KSC5601이나 KO16MSWIN949 캐릭터셋을 가진 데이터베이스에서는 한글 한 글자당 2바이트의 물리적 공간이 소모된다.  그래서, 대부분의 데이터베이스 스키마 설계시 한글 한 글자를 2바이트로 감안하는 경우가 많다. 예를 들어, 한글 및 영문 50자까지 허용할 수 있는 컬럼의 경우, 대략 컬럼 길이를 설정할 때 100바이트로 설정하게 된다. 이런 상황에서, 다국어 지원의 필요성이 있어, UTF8로 마이그레이션해야 하는 상황을 생각해 보자. UTF8 데이터베이스에서는 한글 한 글자당 3바이트가 소모되므로 당장 100바이트의 컬럼은 50자가 아닌 33자밖에 저장하지 못하는 컬럼으로 전락한다.

다음과 같은 상황을 생각해 보자. KSC5601데이터베이스에 길이 10바이트의 컬럼을 가진 테이블이 있다고 가정하고, 여기에 5자의 한글을 꽉 채워보자.

SQL> CREATE TABLE t (sval VARCHAR2(10), svalchar VARCHAR2(5 CHAR));
SQL> INSERT INTO t VALUES('한국오라클','한국오라클');

C:\Documents and Settings\jwryoo>set NLS_LANG=.KO16KSC5601
C:\Documents and Settings\jwryoo>exp scott/tiger@KSC5601
...
. . exporting table                              T          1 rows exported
Table(T) or Partition(T:P) to be exported: (RETURN to quit) >
...

C:\Documents and Settings\jwryoo>imp scott/tiger@AL32UTF8
. importing SCOTT's objects into SCOTT
. . importing table                            "T"
IMP-00019: row rejected due to ORACLE error 12899
IMP-00003: ORACLE error 12899 encountered
ORA-12899: value too large for column "SCOTT"."T"."SVAL" (actual: 15, maximum: 10)
Column 1 한국오라클
Column 2 한국오라클          0 rows imported
Import terminated successfully with warnings.

마이그레이션하기 전에 반드시 테이블의 컬럼 길이는 비율에 맞게 확장해야 한다.

-- 10 * 3 / 2 = 15
SQL> alter table t
  2  modify sval varchar2(15);

Table altered.

SQL> alter table t
  2  modify svalchar varchar2(15);

Table altered.

..
C:\Documents and Settings\jwryoo>set NLS_LANG=.KO16KSC5601
C:\Documents and Settings\jwryoo>exp scott/tiger@KSC5601
....
Export terminated successfully without warnings.
:\Documents and Settings\jwryoo>imp scott/tiger@AL32UTF8
Enter table(T) or partition(T:P) names. Null list means all tables for user
Enter table(T) or partition(T:P) name or . if done: T

Enter table(T) or partition(T:P) name or . if done: .

. importing SCOTT's objects into SCOTT
. . importing table                            "T"          1 rows imported
Import terminated successfully without warnings.

C:\Documents and Settings\jwryoo>sqlplus scott/tiger@AL32UTF8
SQL> SELECT * FROM t;

SVAL                           SVALCHAR
------------------------------ -----------
한국오라클                      한국오라클

US7ASCII 데이터베이스에 한글을 저장하게 될 경우, 한 글자당 거의 100% 2바이트로 저장된다. 사용자들이 일반적으로 사용하는 한글 클라이언트는 대부분 Windows-949(한글 Windows OS)나 KSC5601 완성형(UNIX계열)이고 이들은 모두 2바이트이기 때문이다. 따라서, 여기에 한글을 저장하므로 마찬가지로 "한글최대길이 * 2"의 길이만큼 컬럼을 지정하게 된다. 하지만, US7ASCII 데이터베이스를 바로잡을 때에는 KO16MSWIN949 캐릭터셋으로 강제변환하게 되므로 데이터에 대한 변경은 없다. 따라서, 이 부분만큼은 크게 문제가 없다고 하겠다. 하지만 역시 USASCII에서 UTF8으로 갈 경우에는, KO16MSWIN949로 강제 변경 후, 데이터 마이그레이션을 해야 하므로, 반드시 스키마 점검을 해야 할 것이다.

2) 데이터 깨짐

데이터 마이그레이션 시 일부 데이터 혹은 전체 데이터가 손상되는 경우가 발생한다. 그래서 특히 타겟 데이터베이스와 동일한 조건의 데이터베이스를 이용해 테스트를 충분히 하는 것은 필수 과정이다. 특히 exp/imp를 이용하여 데이터 마이그레이션을 할 때 이런 현상이 나타난다.
US7ASCII 데이터베이스의 한글 데이터를 exp/imp를 이용하여 KO16MSWIN949 또는 UTF8로 마이그레이션
데이터 전체가 손상된다. 불가능하며 시간낭비이다. 애초에 시도하지 않는 것이 좋다. 일부 DBA들이 꼼수를 사용하고 있으나, 그 위험은 시도하는 본인의 부담이라는 것을 잘 인식해야 한다(at their own risk).
KO16KSC5601 데이터베이스의 한글 데이터를 exp/imp를 이용하여 KO16MSWIN949 또는 UTF8로 마이그레이션
이 경우가 데이터 일부 깨짐 현상이 발생할 수 있는 대표적인 케이스이다. KO16KSC5601은 만능 한글 캐릭터셋이 아니라 완성형 2350자만을 지원한다는 사실은 이미 누누히 강조해 왔을 것이다. 하지만 NLS_LANG=.KO16KSC5601으로 설정함으로써, 많은 데이터베이스가 비완성형 한글 8822자들의 일부를 저장하고 사용해 왔을 것으로 믿는다. 이와 같은 데이터는 정상적인 절차를 거쳐 exp/imp를 이용해 다른 캐릭터셋의 데이터베이스로 마이그레이션이 불가능하다.

C:\Documents and Settings\jwryoo>set NLS_LANG=.KO16KSC5601

C:\Documents and Settings\jwryoo>sqlplus scott/tiger@KSC5601
SQL> select sval from t;

SVAL
---------------
커피숖

C:\Documents and Settings\jwryoo>exp scott/tiger@KSC5601
....

C:\Documents and Settings\jwryoo>imp scott/tiger@AL32UTF8
..
Enter table(T) or partition(T:P) name or . if done: .

. importing SCOTT's objects into SCOTT
. . importing table                            "T"          1 rows imported
Import terminated successfully without warnings.

SQL> sqlplus scott/tiger@AL32UTF8
SQL> select sval from t;

SVAL
------------------------------
커피?

SQL> select dump(sval) from t;

DUMP(SVAL)
-------------------------------------------------

Typ=1 Len=12: 236,187,164,237,148,188,239,191,189

-- 231 191 189 : Corrupted character  (ㅤ숖 : 236 136 150)

이런 경우는 "ALTER DATABASE" 명령어로 KO16KSC5601에서 KO16MSWIN949로 딕셔너리 변경(변경 케이스 1)만을 한 후 작업하면 문제를 피할 수 있다.

3) 애플리케이션 오동작

US7ASCII 데이터베이스에 한글을 저장하고 그것을 기반으로 만들어진 애플리케이션은 과연 캐릭터셋을 올바르게 변경한 후에 안전할 수 있을까? 정답은 물론 "없다"이다. 문자열을 처리하는 연산에서 대혼란이 올 수 있다. US7ASCII 데이터베이스에 저장된 한글의 모습을 들여다보자.

US7ASCII:



SQL> select sval from t;

SVAL
-------------------------------------

한국오라클

SQL> select length(sval) from t;

LENGTH(SVAL)
------------
          10

SQL> select instr(sval,'오') from t;

INSTR(SVAL,'오')
----------------
               5

SQL> select substr(sval,3,2) from t;

SU
--


SQL> select concat(substr(sval,3,3),'좋아') from t;

CONCAT(
-------
국옥종

KO16KSC5601:
KO16MSWIN949:
UTF8, AL32UTF8:


SQL> select sval from t;

SVAL
-------------------------------------

한국오라클

SQL> select length(sval) from t;

LENGTH(SVAL)
------------
           5

SQL> select instr(sval,'오') from t;

INSTR(SVAL,'오')
----------------
               3

SQL> select substr(sval,3,2) from t;

SUBSTR(SVAL,3,2)
----------------
오라

SQL> select concat(substr(sval,3,3),'좋아') from t;

CONCAT(SUBSTR(SVAL,3,3),'좋아')
------------------------------------
오라클좋아

예에서 보는 바와 같이 스트링 연산들이 US7ASCII에서는 왜곡될 수 밖에 없다. 따라서, 애플리케이션 개발자들도 이 왜곡된 결과를 바로잡는 코드를 부가적으로 애플리케이션에 넣도록 강요받게 된다.

US7ASCII:
왜곡된 애플리케이션 코드를 이용한 결과 바로 잡기


SQL> select sval from t;

SVAL
-------------------------------------

한국오라클

SQL> select length(sval)/2 from t;

LENGTH(SVAL)/2
------------
          5

SQL> select instr(sval,'오') from t;    -- 왜곡된 결과 그대로 사용

INSTR(SVAL,'오')
----------------
               5

SQL> select substr(sval,5,4) from t;

SU
--


SQL> select concat(substr(sval,5,6), '좋아') from t;

CONCAT(SUB
----------
오라클좋아

KO16KSC5601:
KO16MSWIN949:
UTF8, AL32UTF8:


SQL> select sval from t;

SVAL
-------------------------------------

한국오라클

SQL> select length(sval) from t;

LENGTH(SVAL)
------------
           5

SQL> select instr(sval,'오') from t;

INSTR(SVAL,'오')
----------------
               3

SQL> select substr(sval,3,2) from t;

SUBSTR(SVAL,3,2)
----------------
오라

SQL> select concat(substr(sval,3,3),'좋아') from t;

CONCAT(SUBSTR(SVAL,3,3),'좋아')
------------------------------------
오라클좋아

보는 바와 같이 원하는 결과를 얻기 위해서는 개발자가 일일이 "한글은 2바이트"라는 가정 하에 바이트 단위의 연산을 해 가며 프로그래밍을 해야 한다.

오라클을 이용해 바이트단위의 프로그래밍을 하는 것은:
- 최신식 오디오 시스템을 구축해 놓고 라디오 가능만 사용하는 것
- 전자동 세탁기를 사 놓고, 손으로 통을 돌려 세탁하는 것

- 가스렌지를 구비해 놓고  성냥으로 불을 켜는 것

대조표의 오른쪽에 있는 것처럼, 한글을 지원하는 데이터베이스의 경우 한글을 2바이트로 저장하든, 3바이트로 저장하든 스트링 연산 함수는 기본적으로 캐릭터 단위로 동작하므로 애플리케이션의 코드가 동일함을 알 수 있다. 잘못된 데이터베이스 캐릭터셋은 잘못된 애플리케이션 코딩을 유도하고, 이는 나중에 제대로 된 데이터베이스 캐릭터셋으로 마이그레이션시 심각한 휴유증을 양산할 소지가 있다. 따라서, 캐릭터셋 변경시 애플리케이션의 스트링 연산 부분을 손보는 것은 필수작업이다.

캐릭터셋 변경의 실제

ALTER DATABASE 명령을 이용한 딕셔너리 변경

앞서 살펴본 "변경 케이스 1"에 해당하는 것으로 데이터에 대한 어떠한 검토나 수정은 없이 딕셔너리의 캐릭터셋 정보만을 교체하는 작업이다. KO16KSC5601을 KO16MSWIN949로 변경할 때 유용하다. 그리고 한글만을 저장해 온 US7ASCII 데이터베이스 또한 이 방식으로 KO16MSWIN949로 변경할 수 있다.

절차

 SQL> SHUTDOWN IMMEDIATE;
<do a full backup>
SQL> STARTUP MOUNT;
SQL> ALTER SYSTEM ENABLE RESTRICTED SESSION;
SQL> ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0;
SQL> ALTER SYSTEM SET AQ_TM_PROCESSES=0;
SQL> ALTER DATABASE OPEN;
SQL> ALTER DATABASE CHARACTER SET KO16MSWIN949;
SQL> SHUTDOWN IMMEDIATE;
SQL> STARTUP;

SQL> SHUTDOWN IMMEDIATE;
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup mount
ORACLE instance started.

Total System Global Area  171966464 bytes
Fixed Size                   777956 bytes
Variable Size             145760540 bytes
Database Buffers           25165824 bytes
Redo Buffers                 262144 bytes
Database mounted.
SQL> ALTER SYSTEM ENABLE RESTRICTED SESSION;

System altered.

SQL> ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0
  2  ;

System altered.

SQL> ALTER SYSTEM SET AQ_TM_PROCESSES=0;

System altered.

SQL> ALTER DATABASE OPEN
  2  ;

Database altered.

SQL> ALTER DATABASE CHARACTER SET KO16MSWIN949;

Database altered.

-- 한편 현재 캐릭터셋의 Superset이 아닌 캐릭터셋으로는 변경이 불가능하다.

SQL> ALTER DATABASE CHARACTER SET WE8DEC;
ALTER DATABASE CHARACTER SET WE8DEC
*
ERROR at line 1:
ORA-12712: new character set must be a superset of old character set

-- 다시 데이터베이스를 재구동하면 이제 어엿한 KO16MSWIN949 데이터베이스가 되어 있다.

C:\Documents and Settings\jwryoo>set NLS_LANG=.KO16MSWIN949

C:\Documents and Settings\jwryoo>sqlplus scott/tiger@KSC5601

SQL> select sval from t;

SVAL
---------------
커피숖

변경 후에는 이제 exp/imp를 이용하여 UTF8 데이터베이스로 마이그레이션하는 것도 가능해진다.

이 방식을 사용하려면 반드시 새로운 캐릭터셋은 기존 캐릭터셋의 Superset이어야 한다. 그렇지 않으면 기존 데이터의 안전이 보장될 수 없기 때문이다.

기존 캐릭터셋
새로운 캐릭터셋
변경 가능 여부
US7ASCII
KO16KSC5601/KO16MSWIN949/UTF8/AL32UTF8
가능
KO16KSC5601
KO16MSWIN949
가능
KO16MSWIN949
UTF8
불가능
UTF8
AL32UTF8
가능

강제로 캐릭터셋을 변경하는 옵션 또한 알려져 있으나, 이 지면에 소개할 수 없는 점 양해 바란다. 간단하지만 극히 위험한 작업이다. 이미 강조했지만, 반드시 오라클 엔지니어나 오라클과 지원계약을 맺은 고객의 담당자가 충분히 그 위험성과 방법에 대해 숙지한 후에 작업할 수 있따는 것을 명심하기 바란다.

데이터 마이그레이션

준비작업

  • 데이터베이스 컬럼 길이 점검 : 데이터 자체가 변경되므로 데이터의 길이 또한 변경될 수 있다.
  • 애플리케이션 점검 : 애플리케이션 또한 스키마 및 캐릭터셋 변경으로 인해 영향을 받지 않는지 점검한다. 특히 UTF8으로 갈 경우, 한글을 2바이트로 가정하고 작성한 코드가 없는지 점검한다.

CSSCAN 실행

CSSCAN은 캐릭터셋 변경시 발생할 수 있는 문제점을 미리 감지하고 보고서를 생성해준다는 점에서 매우 유용하다. 비록 이것을 실행하는 것이 완전히 필수적인 요소라고 할 수는 없어도(CSALTER 방식에서는 필수, 지난 일주일 내에 CSSCAN을 수행한 결과가 있어야 함) 지금 보유하고 있는 데이터가 소중하다고 생각된다면 반드시 돌려보는 것이 좋을 것이다.

1) CSSCAN 설치
SQL> connect / as sysdba
Connected.
SQL> @/home/oracle/oracle/rdbms/admin/csminst.sql
2) CSSCAN 구동
[oracle@krrnddel oracle]$ csscan system/..

Connected to:
Oracle Database 10g Enterprise Edition Release 10.1.0.2.0 - Production
With the Partitioning, OLAP and Data Mining options

(1)Full database, (2)User, (3)Table: 1 > 2

Current database character set is KO16KSC5601.

Enter new database character set name: > KO16MSWIN949

Enter array fetch buffer size: 102400 >

Enter number of scan processes to utilize(1..32): 1 >

Enter user name to scan: > SCOTT

Enumerating tables to scan...

. process 1 scanning SCOTT.DEPT[AAAL+oAAEAAAAAJAAA]
. process 1 scanning SCOTT.EMP[AAAL+qAAEAAAAAZAAA]
. process 1 scanning SCOTT.BONUS[AAAL+sAAEAAAAApAAA]
. process 1 scanning SCOTT.TESTTBL[AAAME1AAEAAAAA5AAA]
. process 1 scanning SCOTT.KANGYS[AAAME5AAEAAAABBAAA]
. process 1 scanning SCOTT.DEPTENTITY_GRADUATE_DEPTCOMP[AAAMI8AAEAAAABJAAA]
. process 1 scanning SCOTT.DEPTENTITY_DEPT_DEPTCOMP[AAAMJAAAEAAAAB5AAA]
. process 1 scanning SCOTT.NLSTECH_SAMPLE_SORT_KOREAN_M[AAAMm8AAEAAAABZAAA]
. process 1 scanning SCOTT.NLSTECH_SAMPLE_SORT_KSC5601[AAAMm9AAEAAAACBAAA]
. process 1 scanning SCOTT.CHARSET_TEST[AAAMwLAAEAAAACJAAA]
. process 1 scanning SCOTT.T[AAAM9IAAEAAAACRAAA]

Creating Database Scan Summary Report...

Creating Individual Exception Report...

Scanner terminated successfully.

$ ls scan*
scan.err  scan.out  scan.txt

  • scan.out : STDOUT에 출력된 결과를 저장해 놓은 파일
  • scan.err : 캐릭터셋 변경시 손실되는 데이터

    scan.err의 내용물 예:

    [Application data individual exceptions]

    User  : SCOTT
    Table : TESTTBL
    Column: VAL
    Type  : VARCHAR2(100)
    Number of Exceptions         : 1
    Max Post Conversion Data Size: 6

    ROWID              Exception Type      Size Cell Data(first 30 bytes)
    ------------------ ------------------ ----- ------------------------------
    AAAME1AAEAAAAA9AAA lossy conversion         <8c>c
    ------------------ ------------------ ----- ------------------------------
    ...

  • scan.txt : 종합 보고서. 다음 예에 나온 섹션들이 가장 핵심적인 부분이라 하겠다. Data Dictionary 분석 테이블이 비어 있는 것은위의 CSCSAN 실행을 User단위로(SCOTT 사용자에 한해) 테스트를 했기 때문이다. 새로운 캐릭터셋에서도 변경이 없는 데이터(Changeless, 주로 아스키 영문 데이터)와 변경 가능한(Convertible) 데이터들의 합이 정상적으로 마이그레이션되는 데이터의 비중을 보여준다.
    • Truncation : 변경 후 Truncate될 데이터
      Lossy : 변경 후 손상될(깨질) 데이터

    ...
    [Data Dictionary Conversion Summary]

    Datatype                    Changeless      Convertible       Truncation            Lossy
    --------------------- ---------------- ---------------- ---------------- ----------------
    VARCHAR2                             0                0                0                0
    CHAR                                 0                0                0                0
    LONG                                 0                0                0                0
    CLOB                                 0                0                0                0
    VARRAY                               0                0                0                0
    --------------------- ---------------- ---------------- ---------------- ----------------
    Total                                0                0                0                0
    Total in percentage              0.000%           0.000%           0.000%           0.000%


    [Application Data Conversion Summary]

    Datatype                    Changeless      Convertible       Truncation            Lossy
    --------------------- ---------------- ---------------- ---------------- ----------------
    VARCHAR2                            37               20                1                5
    CHAR                                 0                0                0                0
    LONG                                 0                0                0                0
    CLOB                                 4                0                0                0
    VARRAY                               0                0                0                0
    --------------------- ---------------- ---------------- ---------------- ----------------
    Total                               41               20                1                5
    Total in percentage             61.194%          29.851%           1.493%           7.463%

    [Distribution of Convertible, Truncated and Lossy Data by Table]

    USER.TABLE                                              Convertible       Truncation            Lossy
    -------------------------------------------------- ---------------- ---------------- ----------------
    SCOTT.CHARSET_TEST                                                0                0                3
    SCOTT.DEPTENTITY_DEPT_DEPTCOMP                                    2                1                0
    SCOTT.NLSTECH_SAMPLE_SORT_KOREAN_M                                5                0                0
    SCOTT.NLSTECH_SAMPLE_SORT_KSC5601                                 5                0                0
    SCOTT.T                                                           0                0                1
    SCOTT.TESTTBL                                                     8                0                1
    -------------------------------------------------- ---------------- ---------------- ----------------

    [Distribution of Convertible, Truncated and Lossy Data by Column]

    USER.TABLE|COLUMN                                       Convertible       Truncation            Lossy
    -------------------------------------------------- ---------------- ---------------- ----------------
    SCOTT.CHARSET_TEST|CHARCOL                                        0                0                3
    SCOTT.DEPTENTITY_DEPT_DEPTCOMP|DNAME                              2                1                0
    SCOTT.NLSTECH_SAMPLE_SORT_KOREAN_M|TEXT                           5                0                0
    SCOTT.NLSTECH_SAMPLE_SORT_KSC5601|TEXT                            5                0                0
    SCOTT.T|SVAL                                                      0                0                1
    SCOTT.TESTTBL|VAL                                                 8                0                1
    -------------------------------------------------- ---------------- ---------------- ----------------

    [Indexes to be Rebuilt] : 캐릭터셋 변경 후 재생성되어야 하는 인덱스들의 리스트

    USER.INDEX on USER.TABLE(COLUMN)                                                        
    -----------------------------------------------------------------------------------------
    -----------------------------------------------------------------------------------------

 이 결과를 보면 어떤 테이블의 어떤 행에서 어떤 컬럼에 어떤 데이터가 손상이 될 수 있는지 살펴볼 수 있다. 단 US7ASCII 데이터베이스에 한글을 저장해 놓고 KO16MSWIN949 또는 UTF8로 데이터 마이그레이션 및 캐릭터셋 변환이 가능한지 CSSCAN을 돌려보고 싶다면 그러지 않기를 권장한다. 그것은 불가능하다는 것은 명백하고 잘못하면 엄청난 크기의 리포트만을 하염없이 기다려야 할 지도 모른다.

그런데 데이터 딕셔너리에는 Changeless만 있는 것이 사실 가장 안전하다. 변경 가능한(Convertible) 데이터가 존재하는 것이 100% 나쁘다고 할 수는 없겠지만, 딕셔너리가 수정되었을 때의 파장은 알 수 없으므로 이는 조심해야 한다.
  • CLOB에 저장된 데이터 : US7ASCII에서는 CLOB에 있는 데이터 역시 US7ASCII 형식으로 저장되지만, KO16MSWIn949나 UTF8같은 멀티바이트 캐릭터셋에서는 데이터가 UCS-2와 호환되는 고정 바이트로 CLOB에 저장된다. 따라서, US7ASCII에서 다른 멀티바이트 캐릭터셋으로 마이그레이션하려고 CSSCAN을 구동했을 때에는 CLOB내의 데이터는 모두 Changeless가 아니라 Convertible로 처리될 것이다.
  • 당연한 이야기이지만, 캐릭터들은 기존 캐릭터셋과 새로운 캐릭터셋에 모두 존재하지만 실제 바이너리값에서는 차이가 있다. KO16MSWIN949에서의 "가"(176 160)와 UTF8에서의 "가"(234 176 128)는 엄연히 바이너리 값이 다르다. 따라서, 멀티바이트(한글)로 테이블명이나 인덱스명을 생성했을 때 CSSCAN은 이들을 Convertible로 처리할 것이다.
exp/imp를 사용하여 새로운 데이터베이스로 데이터와 딕셔너리를 마이그레이션할 것이 아니라면(CSALTER를 사용할 것이라면), 오라클은 안전을 위해 CLOB에 들어있지 않은 딕셔너리 Convertible 데이터들을 마이그레이션 전에 처리할 것을 권장한다
  • 한글 이름으로 된 테이블 이름 변경
  • 한글 이름으로 된 테이블 및 객체들은 exp후 drop시키고, 마이그레이션 후 다시 imp한다
기타 딕셔너리에서 Lossy나 Corrupted된 데이터가 발생한다면 이는 심각하며 반드시 전문가나 오라클 기술 지원 인력과 상의해야 할 문제이다.

exp/imp를 이용한 변경

전통적인 백업 방식을 이용하여 마이그레이션하는 방법으로 다른 버전의 데이터베이스들끼리의 데이터 마이그레이션 작업을 상당히 깔끔하게 할 수 있다는 장점이 있다. 하지만, exp가 잘되었다거나 imp가 잘 되었다는 것이 데이터가 안전하게 저장되었다는 것을 검증해 주지는 못한다. 예를 들어 KO16KSC5601 데이터베이스에서 exp한 파일에 잘못된 데이터(숖)이 있다고 하더라도 이것을 다시 UTF8 데이터베이스에 imp했을 때 그것을 발견하지 못한다. exp/imp에 대해서는 앞서 충분히 보여주었으므로 따로 예제를 들지 않겠다. NLS_LANG 변수값의 설정이 매우 중요하다는 사실은 확실히 기억하기 바란다.

CSALTER를 이용한 변경

실제 CSALTER를 수행하기 전에 반드시 Full Database Scan이 이루어져야 한다. 그렇지 않으면 다음과 같은 메시지를 만나게 될 것이다.

SQL> @@/home/oracle/oracle/rdbms/admin/csalter.plb normal

0 rows created.


Function created.


Function created.


Procedure created.

This script will update the content of the Oracle Data Dictionary.
Please ensure you have a full backup before initiating this procedure.
Would you like to proceed ?(Y/N)?y
..
Full database scan is required

csalter.plb는 SYS사용자만이 수행할 수 있고, 수행 시점에서 이 데이터베이스에 접속된 유일한 세션이어야 한다. 그리고 반드시 새로운 캐릭터셋은 기존 캐릭터셋의 Superset이어야 한다. 한글 지원 캐릭터셋과 US7ASCII 캐릭터셋들의 Subset-Superset 관계는 앞서 ALTER DATABASE 방식을 설명하는 중 소개된 테이블을 참조하기 바란다.

글을 마치며

캐릭터셋 변경은 필요하고도 위험하다. 캐릭터셋을 잘못 사용해 왔다는 사실을 알게 되는 시점부터 운영자는 고민에 빠진다. 캐릭터셋을 잘못 사용해 왔다는 것은 그 상단의 애플리케이션조차도 잘못되어 있을 가능성이 높다는 의미이다. 따라서, 데이터를 잘 보존하면서 캐릭터셋을 옮기는 것도 리스크가 크지만, 그 상단의 애플리케이션 또한 다시 디버깅하고 테스팅해야 한다는 사실 또한 운영자에게 두려움으로 다가올 수 있다.
  • 캐릭터셋 변경에 대한 방식을 충분히 습득하자
  • 캐릭터셋 변경에 따른 위험성을 충분히 이해하자
  • 캐릭터셋 변경에 대한 관계자들의 폭넓은 동의가 확보하자
  • 캐릭터셋 변경 전에 반드시 변경을 되돌릴 수 있는 장치를 마련하자
  • 캐릭터셋 변경 전에 충분히 테스트 DB로 테스트하자

[출처] http://www.oracle.com/technology/global/kr/pub/columns/oracle_nls_2.html



  • 오라클의 NLS 지원 특성
    1. 영역(Territory)별 지원
    2. 언어(Language)적 지원
  • 오라클 제품 처음부터 올바르게 설치하자
  • 올바른 캐릭터셋을 선택하자
    1. KO16KSC5601
    2. KO16MSWIN949
    3. UTF8/AL32UTF8
    4. National Characterset
    5. 캐릭터셋 선택의 원칙
  • 올바른 NLS 환경변수값 설정하기
    1. NLS_LANG
      1. 1) NLS_LANG 변수의 구성
      2. 2) NLS_LANG 변수값 설정의 기본 원칙
      3. 3) 데이터베이스의 캐릭터셋과 동일한 값으로 캐릭터셋을 설정하는 경우
  • KO16KSC5601에 서 지원되지 않는 글자들을 KO16KSC5601 데이터베이스에 입출력하기
  • 오라클 데이터베이스에서의 한글 정렬
    1. KO16KSC5601 데이터베이스
    2. UTF8/AL32UTF8 데이터베이스
      1. NLS_SORT=’KOREAN_M’ 이용
    3. KO16MSWIN949 데이터베이스
      1. 방법 1) NLS_SORT=’UNICODE_BINARY’ 이용
      2. 방법 2) NLS_SORT=’KOREAN_M’ 이용
    4. 인덱스를 이용하여 성능 향상시키기
  • 오라클 데이터베이스에서 한글 비교하기
    1. NLS_COMP
  • 글을 마치며


  • Oracle Forms를 이용하여 기업용 어플리케이션을 개발할 때 요구되는 화면의 유형은 크게 두 가지로 나눌 수 있다.
    하나는 입력용 화면이고 또 다른 유형은 조회용 화면이다. 입력용 화면은 데이터의 정합성 에 초점을 두어 개발이 이루어지며,
    조회용 화면은 다양한 조건으로 데이터를 조회할 수 있는 기능에 초점을 맞추고 있다.

    "왜 우리만 한글이 제대로 안 보이는 거야. 이거 비싼 돈 주고 도입했더니 완전 엉터리 아냐?
    아니면 오라클 개발자들이 꼬부랑말 쓰는 녀석들이라는데...영 아니올시다인가?"

    "그러니까 외국 제품들 쓰면 다 이 모양이라니깐.. 대한민국에 관심이나 있겠어?"

    어떻게든 되게 만들어야 합니다. 어김없이 사건 현장에는 혈투를 벌이는 병사가 구원을 요청한다. "한글, 한글이 왜 깨지냐고요?
    업그레이드하기 전까지는 잘 돌아갔는데 이게 무슨 경우입니까? 고객에게 어떤 답변을 해 주어야 합니까?"
    제 아무리 자식을 좋은 학교에 보내어도 노심초사 자식을 걱정하는 부모의 마음처럼, 천하를 호령하고 다닌다는 오라클 데이터베이스에
    데이터를 저장하고 있으면서도 고객 또한 자식같은 데이터를 잃을까 노심초사하기 마련이다.  오라클의 최전방의 병사들은
    이런 고객의 마음을 헤아리고자 백방으로 문제를 해결하려고 뛰어다니면서도 머릿속에 이런 생각이 든다.
    "아, 왜 세상엔 다양한 언어들이 있어서..."

    최전방 공격수가 아니라 다국적 소프트웨어로서의 오라클 제품의 완벽성을 위해 일하고 있는 본인의 입장에서는 처음부터
    병사들의 절규들을 이해할 수 있었던 것은 아니었다. 그도 그럴 것이 많은 문제들이 소프트웨어 자체의 결함보다는 잘못된
    설정과 이해에서 비롯된 것이기 때문이었다.  하지만, "소프트웨어의 글로벌화"라는 슬로건이 제대로 등장하기 시작한지는
    실질적으로 아직 10년도 되지 않았다. 게다가 다국어 지원의 개념이 직접 소프트웨어의 설계에 적용되고 개발, 출시되고
    사용되기까지는 그 10년 중 상당 부분이 소모되어야 했다. 그러므로 아직 시스템의 "한글화", "다국어화"라는 것이 100%
    이해되기에는 무리가 있는 것이다.

    "지금부터라도 제대로 "글로벌화"된 시스템을 이해하고 적용해 보는 거야!"

    오라클 데이터베이스가 속편하게 입력되는 데이터를 마치 바이너리 스트림을 저장하듯이 텍스트 데이터를 언어의 특성에 맞는지
    검사하거나 변환하는 작없없이 저장한다는 오히려 겉으로 드러나는 문제점이 더 적게 보이는 착시 현상이 발생할 지도 모른다.
    또한 오라클 데이터베이스가 현재 사용자에 대해, 어느 나라에 있는지 고려하지 않고 단순히 날짜나 숫자 데이터를 저장만 해주고,
    어떤 형식으로 날짜가 출력되어야 사용자가 이해할 수 있는지, 어떤 통화기호를 사용해야 사용자가 제대로 된 값을 이해할 수 있는지
    고려하지 않는다면 오라클 데이터베이스 입장에서도 개발의 부담이 훨씬 적었을 것이다. 그 모든 것은 상위 시스템 개발자의 책임이니까......

    "오라클 데이터베이스 한국어판 나왔습니까?"
    오라클의 제품들은 현재 세계 140개 이상의 다양한 로케일을 지원하고 있다. 물론 한국어도 포함한다. 단순히 한국어를 포함하는 게
    아니라 한국어는 "가장 중요한 10개 언어들 중 하나"의 위치를 차지하고 있다. 그런데도 "오라클 데이터베이스의 한국어판 출시 임박"
    이런 광고를 본 적이 없을 것이다. 한국어 뿐 아니라 저 100개가 넘는 모든 로케일에 대해 "~~판"이라는 것은 애시당초 존재하지 않는다.
    왜? 오라클 제품 자체가 "글로벌판"이기 때문이다. 출시 자체를 모든 로케일에 대한 지원이 끝난 이후에야 하게 되며, 출시된 제품에는
    이미 모든 로케일에 대한 지원이 포함되어 있는 것이다. 많은 제품들이 "영어 버전"의 출시 후 수 주, 혹은 수 달 후에 "한국어판"이라는
    것을 출시하는 것과는 다른 전략이다. 소프트웨어의 하부 설계 자체가 이미 다국어 지원을 고려하여 만들어졌기 때문에 가능한 것이다.

    SQL> alter session set NLS_CALENDAR = 'ROC Official';

    세션이 변경되었습니다.

    SQL> SELECT sysdate from dual;

    SYSDATE
    ------------------------------------------
    中華民國94年07月21日
    [오라클은 추가적으로 6개의 달력 시스템을 지원한다]

    위에서 보듯, 여러분이 설치한 오라클 데이터베이스는 일반적으로 사용하는 Gregorian 달력 이외에 6가지 달력을 더 지원하고 있다.
    이런 것이 오라클 소프트웨어만의 특징은 아닐 지라도 멋있지 않은가? "글로벌화"된 소프트웨어라는 것.

    "그래서 어렵다"
    이것은 오라클 제품의 강점이기도 하지만, 그래서 사람들에게 "어렵다"는 느낌을 심어준다. 왜냐하면 오라클 제품을 사용하는 사람은
    결국 입맛에 맞게 설정이 끝나 있는 "한국어판"을 사용하는 것이 아니라, "글로벌판"을 입맛에 맞게 설정하여 사용해야 하기 때문이다.
    하지만, 그건 단순한 이유일 뿐이다. 사실은 그 "입맛에 맞는 설정"이라는 것을 제대로 할 수 있도록 해 주는 "교본"이 없다는 것이
    문제이다. 그리하여 많은 DBA들조차 초기 설정을 제대로 하지 않은 채 시스템을 도입하게 되고, 무엇이든 해 낸다(자랑스럽기도 하고
    개발자로서 슬프기도 한)는 실력을 갖추고 있는 우리의 개발자들이 잘못된 설정을 가진 시스템을 기반으로 하여, 어떤 수를 써서라도
    문제가 발생하지 않도록 밤을 새는 것이다. 그리 어렵지 않은 "다국어 지원"을 상식화하면, 개발자들도 억울하게 고생하지 않고
    작업을 빨리 마친 후 집에 가서 재미있는 드라마도 볼 수 있는 것이다.

    "알아보세. 느껴보세. 기뻐해보세"
    누구나 잘 알고 있는 것 같으면서도, 누구나 잘못 사용할 수도 있는 오라클 데이터베이스의 견고한 NLS(National Language Support)
    아키텍처를 어떻게 하면 문제없이 잘 사용할 수 있는지 일단 한 번 그 세계로 빠져 보기로 하자.  약 보름 간격으로 네 번에 걸쳐
    오라클의 NLS 세계를 관광하고, 관광 다녀와서 이제는 개발자도 삼순이 같은 재미있는 드라마 보러 자신있게 퇴근하는 데 조금이라도
    도움이 될 만한 지식 혹은 상식을 가져 보기로 하자.

      연재 순서
      1회 : 오라클과 NLS의 찰떡궁합 들여다보기(1)
      2회 : 오라클과 NLS의 찰떡궁합 들여다보기(2)
      3회 : 오라클 GDK를 사용하여 깔끔한 다국어 개발 유틸리티를 만들자
      4회 : 한글화된 오라클 제품, 그 이면의 비밀.


    PartⅠ. 오라클의 NLS 지원 특성.

    1. 영역(Territory)별 지원

      영국과 미국은 "영어"를 모국어로 사용하는 나라들이지만, 이들 나라에서 사용되는 날짜 표기 방법은 각각 다르다. 영국에서는 "일/월/연도"로 표기하는 반면, 미국에서는 "월/일/연도"로 표기한다. 물론 사용하는 통화기호 또한 "파운드"와 "달러"로 각각 다르다. 이렇듯, 같은 언어를 사용하는 지역이라고 해도 서로 다른 지리적, 사회적 특성으로 말미암아 서로 차이점을 가지게 마련이다.  영역 정보들이야말로, 여러분들이 NLS에 대해 "캐릭터셋 지원"이나 "번역"이상의 그 무엇으로 인식하는 데 큰 도움을 줄  것이다. 한 영역은 다음 표와 같은 고유 정보들을 포함할 수 있다.

      • 달력 설정 방법 : 어떤 나라는 한 주의 첫 번 째 요일을 일요일로 생각하고(한국), 다른 나라들 중에서는 월요일(체코)로 생각할 수 있다. 또한 한 달의 첫 번 째 주를 생각할 때, 어떤 나라(체코)는 그 달의 날짜들이 해당 주의 과반수 이상을 차지하고 있을 때 그 주를 첫 번 째 주로 생각하고, 어떤 나라(한국)에서는 최초의 완전한 한 주를 그 달의 첫 번 째 달로 생각한다.
      • 날짜 포맷 : 같은 날짜를 표기하는 데 각 지역마다 고유의 방식이 있음. 각 지역마다 "짧은 형식"과 "긴 날짜 형식"을 지정할 수 있다. 2005년 8월 10일이라는 날짜 데이터를, 한국에서는 "05/08/10 오후 07:28:03"로, 체코에서는 "10.08.05 19:28:03"로 제공할 수 있다.
      • 통화 기호 : 각 지역마다 통화기호와 금액 표기 방식이 다르다.  또한 통화의 변경(EURO화 변경과 같이)으로 인해 두 개의 통화가 사용될 수 있고(DUAL CURRENCY), 국제적으로 통용되는 ISO 통화기호도 있다. 한국은 ₩, 체코는 Kč를 사용한다.
      • 숫자 그룹 : 소수점 기호나 숫자를 그룹핑하는 방법이 지역마다 다르다. 기타 측정방식(미터방식 등)이나 반올림 방식, 음수기호의 위치 등이 지역마다 다르다. 한국에서는 소수점기호는 dot, 그룹기호는 comma이지만, 체코에서는 소수점기호가 comma, 그룹기호는 dot이다.

    2. 언어(Language)적 지원

      언어별로 달리 지원을 하는 특성은 다음과 같은 것이 있다.

      • 캐릭터셋 : 각 언어가 저장될 수 있는 캐릭터셋을 대부분 지원한다. 한국어의 경우 KO16MSWIN949와 KO16KSC5601이 있다.
      • 정렬 방식 : 각 언어별로 정렬하는 규칙이 다르다. 기본적으로는 이진 바이너리 코드값을 이용해 데이터를 정렬할 수 있지만,
        때에 따라서는 각 언어가 가진 글자들의 고유한 특성에 맞게 정렬을 해 줄 필요성이 있다.  이런 정렬을 Linguistic Sorting이라고 한다. 한국어에 대해서는 KOREAN_M이라는 정렬 방식을 지원한다.
      • 날짜 표기에 사용되는 기호 : 날짜를 표시할 때 사용하는 month, day, day of week, year같은 정보를 그 나라에 맞게 번역하여 제공한다.
      • 에러메시지 및 UI 번역 : 사용자들의 불편을 최소화하기 위해 각 언어별로 번역된 에러 메시지와 사용자 인터페이스를
        제공한다.

    PartⅡ. 오라클 제품 처음부터 올바르게 설치하자.

      한국어 환경을 제대로 지원하려면 그림과 같이 반드시 "한국어""실행 언어"에 포함시켜야 한다. 사실 많은 DBA들이 이 부분을
      간과하고 있다. 아무 생각 없이 계속 "다음(N)" 버튼만 누르다가는 돌이킬 수 없는 결과를 얻게 될 수 있으므로 이 과정은 정말
      중요하다고 하겠다.

      언어 선택의 의미.

      "한국어"를 선택하지 않는다고 해서 한국어를 데이터베이스에 저장할 수 없다는 것은 아니다.
      먼저 이 "언어 선택"에서 한국어를 선택한다는 것의 의미를 정확히 알 필요가 있다. 위에 강조한 대로 한국어를 비록 선택하지
      않았다고 해서 데이터베이스에 한글 데이터를 넣을 수 없다는 것은 결코 아니다.

      "한국어 저장 여부는 오로지 캐릭터셋이 무엇인가에 달렸다"

      한국어를 제대로 저장하는가 여부는 오로지 다음에 언급할 캐릭터셋 설정에만 의존할 뿐, 실상"언어 선택"에서의 한국어 선택
      여부는 관계없다는 점을 잘 구분해야 한다 위의 그림에서 묻는 바와 같이 이 화면은 "실행 환경"에서 어떤 언어를 지원할 것인가를
      묻고 있다. 즉 여기에서 "한국어"를 선택하면 다음과 같은 리소스가 설치된다.

      • 번역된 메시지 : 오라클 데이터베이스와 함께 제공되는 애플리케이션 중, iSQL*plus나 자바나 ADF 기반의 웹 애플리케이션의 경우에는 "언어 선택"과 관계없이 번역된 작업 환경이 제공된다. 하지만, SQL*Plus와 같은 기존 애플리케이션은 오라클의 번역 메시지 리소스에 의존하며 이들 파일은 각 언어별로 따로 제공된다. 만일 "한국어"를 선택하지 않으면 한국어 메시지 파일은 설치되지 않게 된다.
      • 폰 트 : 오라클 ADF(UIX 혹은 CABO) 기반의 애플리케이션의 경우, "확인", "취소" 등의 버튼이 이미지로 제공되는 경우가 많다. 이런 이미지들은 번역된 메시지를 바탕으로 UIX 엔진에 의해 동적으로 생성된다. 이 이미지가 제대로 생성되기 위해서는 반드시 특정 폰트를 필요로 하게 되는데, 이 폰트는 "한국어"를 선택하지 않을 경우 설치되지 않는다. 이 특정 폰트는 한국어, 중국어(간체, 번체) 그리고 일본어에 대해 각각 제공되므로,  만일 한국어 이외에 이들 언어도 지원해야 할 경우 필수적으로 그 언어들을 선택해야 할 것이다.

      • 로케일 정보 : 번역 뿐만 아니라 오라클 데이터베이스가 다양한 로케일 정보를 가진 클라이언트들과 제대로 통신하기 위해서는 각 국가별, 언어별로 특색있는 로케일 정보를 지니고 있어야 한다. 이들은 날짜 형식("2050-04-14" "Jul 9, 2005" 등), 통화 코드($) 등의 정보를 포함하고 있다. 한국어에 관한 로케일 정보를 위해 반드시 "한국어"를 선택해야 한다.


      한글 Windows 혹은 LANG=ko로 설정한 유닉스 환경
      이 환경에서는 그림과 같이 OUI가 한글로 뜨게 된다.

      기타 유닉스 환경
      DBA 중에서는 "한글" 환경에서 오라클 제품을 설치하면 오동작하거나 설치되지 않는 경우가 많다고 믿는 사람들이 종종 있다. 하지만 이것은 틀린 이야기다. 오히려 한글 데이터나 한글 기반의 애플리케이션을 위해서는 한글 환경에서 오라클 제품을 설치하는 것이 실수를 줄이는 길이다.

      "저희는 LANG=ko로 하면 깨진 화면이 떠서요"

      하지만, 아직 리눅스 등 많은 OS에서 오라클이 제공하는 번역된 인스톨 환경을 사용할 수 없다. 오라클의 인스톨러인 OUI(Oracle Universal Installer)는 자바 기반의 애플리케이션이며, 한글 출력 가능 여부는 JRE(Java Runtime Engine)와 OS에 의존한다. Sun에서 제공하는 JRE의 Linux 버전은 현 시점까지는 오로지 일본어 폰트 정보(font.properties.ja)만 제공하고 있으며 한국어에 대해서는 제공하고 있지 않다. 따라서, 부득이하게 한글이 깨져서 나오게 되는 것이다. 이 경우에는 할 수 없이 LANG=C로 설정하고 영문 환경에서 오라클 제품을 설치해야 한다. 독자들은 이제 이런 상황에서 무엇을 조심해야 하는지를 깨달았을 것이다. 이 경우 "언 어 선택"에서는 오로지 "영어"만이 선택되어 있으므로, 반드시 "한국어"를 선택하고 넘어가야 할 것이다.

    PartⅢ. 올바른 캐릭터 셋을 선택하자.

      "올바른 캐릭터셋이라 함은 한글을 저장할 수 있는 캐릭터셋을 말한다."
      자, 설치에서 한숨을 돌렸다면, 이제 실제 데이터베이스 인스턴스가 생성될 때, 올바른 캐릭터셋을 선택하는 것이 중요하다.
      물론 이 사항은 오라클 많은 소프트웨어 제품들 중 데이터베이스를 설치할 때에만 해당되는 사항이다.
      캐릭터셋은 잘못 설치되었을 경우에는 그야말로 치명적이다. 이런 데이터베이스에 어떤 잘못된 방식으로든 한글 바이트 코드를
      저장하고 사용하게 된다면, 돌이킬 수 없는 결과를 낳게 된다.

      "정해진 캐릭터셋을 가지고 있지 않은 데이터베이스에 결코 한글 데이터를 저장할 수 없다."
      독자 중에는 이 말을 믿으려고 하지 않는 사람들이 있을 것이다. "설마? 내가 해 봤는데, 되던데요? 이런 생각을 가진 사람들이
      있을 것이라고 믿는다. 하지만, 여러분들이 저장한 것은 결코 한글이 아니다(한글을 저장하고 사용해 왔다고 믿고 싶을 테지만).
      여러분들이 저장해 온 것은 그저 한글을 가장한 이진 코드의 덩어리일 뿐이다. 데이터베이스가 여러분들이 던져주는 코드를
      올바른 텍스트로 인지하는 능력을 억제시킨 채, 강제로 데이터를 저장하며 그것을 가지고 한글을 제대로 저장했다고 우길 수는
      없는 것이다. 다 같이 믿자. 저 말은 부정할 수 없는 사실이며 결코 부정될 수 없다.

      "왜 유독 한국 사람들은 그동안 US7ASCII 캐릭터셋을 사랑해 왔나? 이제 헤어질 때가 왔다"
      현재 한글을 지원하는 캐릭터셋으로는 다음 네 가지가 있다. 오직 이 네 가지이다. 각각의 특색이 다르므로 유의해야 한다.

      • KO16KSC5601
      • KO16MSWIN949
      • UTF8
      • AL32UTF8

    1. KO16KSC5601

    이름에서 알 수 있는 바와 같이 이 캐릭터셋은 한글 완성형 코드와 일치한다. 완성형은 일반적으로 많이 사용되는 2350자의 한글을 25*94 매트릭스에 배열한 문자셋이며, 4888자의 한자와 히라카나, 카타카나, 그리고 영문 및 각종 기호들을 포함하고 있다. 유닉스 환경에서는 LANG=ko로 하여 DBCA(Database Configuration Assistant)를 실행할 경우, 자동으로 캐릭터셋을 KO16KSC5601로 지정한다. 물론 변경할 수도 있다.

    "KO16KSC5601 캐릭터셋을 사용하기 전에 그 특성을 제대로 알고 사용하자"

    햏햏

    모두에게 하게 되었소. 솔가 예약했던 커피이 배신을 때리는 바람에 그만 낙동강 오리알이 되었지요. 정말 하오.


    방각하

    불행하게도 여러분의 KO16KSC5601 데이터베이스는 이 글을 제대로 저장할 수 없다. 굵게 표시된 글자는 완성형에 포함되지 않은 글자들이다. 그래서 이 글을 저장한 후 다시 읽으려고 하면 다음과 같은 결과를 보게 될 뿐이다.

    ? ?

    모두에게 ?하게 되었소. 솔?가 예약했던 커피?이 배신을 때리는 바람에 그만 낙동강 오리알이 되었지요. 정말 ?하오.

    ?방각하

    비록 이런 글을 올리는 게 올바른 한국어 문화에 이바지하는 길이 아니라는 것을 잘 알고 있지만, 시스템을 개발하는 사람이 사용자에게 문화 계몽을 시킬 수는 없는 노릇이다. 또한 사람의 이름을 "솔믜"라고 짓지 말라고 요구할 수도 없다. 실제로 이쁜 이름이 아닌가?

    "캐릭터셋 지정 전에 반드시 사용할 시스템이 어느 정도 범위의 한글 지원을 원하는가를 확실히 검사할 필요가 있다"

    2. KO16MSWIN949

    Windows-949 캐릭터셋은 마이크로소프트사의 Windows Codepage 949번, 즉 한글 코드 페이지를 따른 코드셋이다.
    이는 완성형(KO16KSC5601)을 그대로 포함하고 있으며, 추가로 현대 한글 조합으로 표현할 수 있는 모든 가짓수에 해당하는 8822자의 한글을 추가해 포함하고 있다. 그러니까 "Windows-949 캐릭터셋은 KSC5601의 수퍼셋(Superset)"이 되며, 따라서 "KO16MSWIN949 또한 KO16KSC5601의 수퍼셋"이 된다.

    "다른 운영 체제에서도 사용할 수 있다!"
    운영 체제가 Windows 949 코드 페이지를 지원하지 않는다고 해서, KO16MSWIN949 캐릭터셋을 가진 데이터베이스 인스턴스를 생성할 수 없다는 것은 아니다. 데이터베이스 캐릭터셋과 운영체제의 캐릭터셋은 전혀 별개라고 인식해야 한다. 비록 Windows-949는 특정 업체의 문자셋이기는 하지만, 이를 기반으로 한 KO16MSWIN949 캐릭터셋은 한글 2350자의 한계를 가진 KO16KSC5601의 대안으로 용이하게 이용될 수 있다. 기억하자.
    Unix에서든 Linux에서든, KO16MSWIN949 캐릭터셋을 가진 데이터베이스 인스턴스를 생성할 수 있다.

    3. UTF8/AL32UTF8

    UTF8은 유니코드를 구현한 캐릭터셋 중에 가변길이 인코딩 방식을 택하고 있는 캐릭터셋이다.
    자세한 인코딩 방식은 여기에서 논할 필요가 없지만, 가변 길이를 위해 일종의 플래그 비트를 각 바이트마다
    포함시켜야 하다보니, 한 글자를 표한하는데 필요한 바이트의 길이가 최대 3바이트(AL32UTF8의 경우 6바이트)까지 늘어날 수 있다.

    유니코드는 잘 알려진 바와 같이 현대 한글 11172자를 모두 가나다 순으로 잘 정렬된 상태로 포함하고 있다.
    그래도 한글 한 자가 3바이트의 물리적 공간을 차지하므로, 오로지 모든 한글을 지원한다는 이유만으로 사용하는 것은 곤란하다. 하지만, 한글 이외에도 다른 언어들을 함께 데이터베이스에 저장해야 한다면 다른 선택의 여지가 없는 유일한 선택이 된다.

    한글을 지원하는 캐릭터셋 비교 테이블

    KO16KSC5601
    KO16MSWIN949
    UTF8
    AL32UTF8
    한글 지원상태
    한글 2350자 KO16KSC5601 + 확장 8822자(총 11172자)
    한글 11172자
    한글 11172자
    캐릭터셋/인코딩 버전
    한글완성형
    완성형코드포함
    확장된 8822자는 MS Windows Codepage 949에 따라 배열
    8.1.6 이전 : Unicode 2.1
    8.1.7 이후: Unicode 3.0
    9i Rel1: Unicode 3.0
    9i Rel2 : Unicode 3.1
    10g Rel1 : Unicode 3.2
    1/0g Rel2 : Unicode 4.0
    한글바이트
    2바이트
    2바이트
    3바이트
    3바이트
    지원버전
    7.x
    8.0.6 이상
    8.0 이후
    9i Release 1 이상
    Database Characterset으로 설정 가능 여부
    가능
    가능
    가능
    가능
    National Characterset으로 설정 가능 여부
    불가능
    불가능
    가능
    불가능
    한글정렬
    단순 바이너리 정렬로  구현 가능
    KOREAN_M 또는 UNICODE_BINARY 등 특수한 옵션 필요
    (한글 정렬에 관한 설명 참조)
    한글 정렬은 단순 바이너리 정렬로 가능. 한자 정렬은 KOREAN_M 옵션 필요

    장점
    - 특별한 장점이 없음. 완성형 코드만을 입출력하는 것이 확실할 경우에는 높은 성능
    - 2바이트로 모든 한글 저장/입출력 가능. 공간의 소모가 적으면서도 모든 한글을 입출력할 수 있다


    - 현대 한글 11172자가 정확한 순서로 배열되어 정렬이 효과적
    - 다른 언어들(중국어 태국어 등) 또한 같은 데이터베이스 인스턴스에 저장되어야 할 경우 UTF8 등의 유니코드 캐릭터셋 이외에 다른 대안이 있을 수 없음
    - 한글 지원은 UTF8과 동일
    단점
    - 한글을 2350자밖에 지원하지 못한다는 치명적인 단점이 있어 미래에는 사용이 자제되어야 할 캐릭터셋
    - 완성형과 호환을 하려다보니, 글자배열순서와 정렬 순서가 다르게 됨. 단순한 "ORDER BY" 절로는 제대로 한글 정렬을 할 수 없음
    한글 한 캐릭터가 3바이트를 소모하게 되어 공간의 소모가 크고, 유니코드 인코딩/디코딩에 성능을 소모해야 한다


    4. National Characterset

    네셔널 캐릭터셋은 유니코드를 지원하지 않는 캐릭터셋을 가진 데이터베이스에서 유니코드를 지원하기 위해 부가적으로 설정할 수 있는 캐릭터셋이다. 즉, 하나의 데이터베이스 인스턴스는 "캐릭터셋"과 "네셔널 캐릭터셋"을 가진다. 처음 시스템 구축 당시와는 달리, 한글 이외의 다른 언어를 급히 저장해야 할 필요성이 있는 경우 네셔널 캐릭터셋을 적절히 활용할 수 있다.

    네셔널 캐릭터셋을 가능한 캐릭터셋은 단 두 가지이다. UTF8과 AL16UTF16(기본값).
    네셔널 캐릭터셋을 사용하기 위해서는 특정 타입으로 테이블의 컬럼 또는 PL/SQL 변수를 선언해야 한다. CHAR와 VARCHAR2,CLOB에 대응되는 네셔널 캐릭터셋 기반의 타입으로는 NCHAR, NVARCHAR2,NCLOB이 있다.

    즉, KO16MSWIN949 데이터베이스에서 다음과 같이 테이블을 생성할 경우,

      CREATE TABLE test_table
      ( varchar_value VARCHAR2(2000),
      nvarchar_value NVARCHAR2(2000)
      );
    "varchar_value" 컬럼에는 KO16MSWIN949에 속하는 글자들만 저장할 수 있는 반면, nvarchar_value 컬럼에는 유니코드에 속한 모든 글자들을 저장할 수 있다. 약간의 부가적인 코드가 필요할 뿐, 실제 프로그래밍 방식은 거의 같다.

    5. 캐릭터셋 선택의 원칙

    많은 원칙이 필요없다. 다음 몇가지만 기억하자.

    • 한글 지원을 위해서는 반드시 위의 네 가지 캐릭터셋 중에 하나를 선택해야 한다
    • 한국에서만 사용하는 시스템이라면 KO16MSWIN949를 선택한다
    • 한국어 뿐 아니라 중국어, 일본어, 러시아어 등 다양한 언어로 된 데이터를 저장해야 한다면 UTF8, AL32UTF8을 선택한다. 인코딩 변환으로 한국어 기반의 캐릭터셋에 비해 속도의 저하가 있다고 알려져 있다.
    • 대부분이 한글이며, 일부 외국어가 필요하다면, 한국어 기반의 캐릭터셋(KO16MSWIN949)을 사용하되, National Characterset을 이용한 컬럼에 외국어를 저장한다.

    PartⅣ. 올바른 NLS 환경변수값 설정하기.

      "모로 가도 서울만 가면 된다"고 자동차에 대한 지식없이 어떻게 하다보니까 차를 뒤로 움직이게 되어, 후진으로만 목적지에 도달한다면 과연 목적지에 잘 도달했다고 칭찬받아야 할까? 그 답은 "예"일 수도, "아니오"일 수도 있지만, "모로 가도 한글만 나오면 된다"는 식으로 구축된 시스템에 대해서는 무조건 "아니오"가 답이다. 많은 시스템들이 제대로 운전에 대한 지식과 준비 없이 시스템을 목적지로 운전시키고 있다.

      오라클 데이터베이스는 무려 20개의 다양한 NLS 환경변수를 제공한다. 하지만 염려는 붙들어 매길...... 그렇다고 해서 20개 모두의 씀씀이를 다 알아야 한국어데이터를 제대로 다룰 수 있다는 의미는 아니다. "손가락만 까딱해도 된다"는 요즘 자동차들이 제공하는 수많은 기능들 중에 핵심적인 기능만 알아도 안전운전을 할 수 있듯이, 몇 가지 핵심적인 변수의 의미만 제대로 파악하고 제대로 사용한다면 그야말로 "안전한" 한국어 환경을 구축할 수 있는 것이다.

      1. NLS_LANG

      1) NLS_LANG 변수의 구성
      NLS_LANG 변수는 단순히 하나의 변수가 아니라 실질적으로 NLS 연산의 모든 것을 결정한다고 해도 틀리지 않은 세 가지 정보를 포함하고 있는 중요한 변수이다.

          NLS_LANG = [언어]_[영역].[캐릭터셋]


      정 의
      가능한 값
      언어
      현재 사용자가 사용하는 언어적 특성을 결정짓는 값 SQL> select parameter,value from V$NLS_VALID_VALUES where parameter like '%LANG%' ORDER BY value;
      ...

      KOREAN
      LATIN AMERICAN SPANISH
      ...
      TRADITIONAL CHINESE
      ..
      VIETNAMESE
      영역
      현재 사용자가 위치한 영역의 특성을 결정짓는 값 SQL> select parameter,value from V$NLS_VALID_VALUES where parameter like '%TERR%' ORDER BY value;
      ...

      KOREA
      ...
      SAUDI ARABIA
      ...
      YUGOSLAVIA
      캐릭 터셋
      현재 사용자의 시스템이 인식할 수 있는 캐릭터셋의 값 SQL> select parameter,value from V$NLS_VALID_VALUES where parameter like '%CHARACTERSET%' ORDER BY value;

      ..
      KO16KSC5601
      ..
      KO16MSWIN949
      ..
      WE8DEC

      ..
      ZHT16MSWIN950

      KOREAN_KOREA.KO16KSC5601
      KOREAN_KOREA.KO16MSWIN949
      AMERICAN_AMERICA.US7ASCII
      AMERICAN_AMERICA.WE8ISO8859P1
      JAPANESE_JAPAN.JA16SJIS
      .KO16MSWIN949
      .UTF8

      2) NLS_LANG 변수값 설정의 기본 원칙
      "제 데이터베이스가 UTF8인데, NLS_LANG도 .UTF8로 해야 하는 거 아닌가요?"
      땡, 틀렸다. NLS_LANG 변수가 데이터베이스 캐릭터셋과 값이 항상 같아야 한다면 무엇하러 설정하겠는가?

      "NLS_LANG 변수는 데이터베이스에게 사용자의 환경을 알려주는 인식표 역할을 한다"
      그렇다. NLS_LANG 변수의 값은 멀리 있는 데이터베이스의 환경이 아니라, 사용자 자신이 속해 있는 환경을 도리어 데이터베이스에 알려주는 역할을 하는 변수이다.

      NLS_LANG을 다음과 같이 설정해 보자.

      > set NLS_LANG=AMERICAN_AMERICA.KO16MSWIN949

      이는 사용자 자신이 미국의 영어를 쓰고, 아메리카 영역 내에 있으며, 가진 컴퓨터가 사용하는 캐릭터셋은 KO16MSWIN949라는 의미이다.

      > sqlplus scott/tiger
      SQL> select x from y;
      select x from y
      *
      ERROR at line 1:
      ORA-00942: table or view does not exist


      SQL> select hiredate from emp;

      HIREDATE
      ------------
      17-DEC-80
      ...

      18 rows selected.

      이와 같이 모든 메시지나 날짜 형식을 미국에 맞게 표현해 준다.

      SQL> exit

      이제 NLS_LANG을 다음과 같이 변경해 보자.

      > set NLS_LANG=KOREAN_KOREA.KO16MSWIN949

      이는 사용자 자신이 한국어를 쓰고, 한국 영역 내에 있으며, 가진 컴퓨터가 사용하는 캐릭터셋은 KO16MSWIN949라는 의미이다.

      SQL> select hiredate from emp;

      HIREDATE
      --------

      80/12/17
      ..

      18 개의 행이 선택되었습니다.


      이제 한국의 날짜 형식과 함께 한국어로 메시지를 보여주게 된다.


      이에 따라, Windows 운영체제에서 한국어 환경을 사용하는 사용자들은 다음과 같이 NLS_LANG 값을 설정할 수 있다.
      KOREAN_KOREA.KO16MSWIN949

      그리고, 유닉스 운영체제에서 한국어를 입출력한다면 다음과 같이 NLS_LANG을 설정할 수 있다.
      KOREAN_KOREA.KO16KO16KSC5601

      데이터베이스가 UTF8이든 KO16MSWIN949이든 상관없이 한글을 지원하는 데이터베이스와 통신한다면 반드시 위와 같이 NLS_LANG값을 설정해야 한다.


      3) 데이터베이스의 캐릭터셋과 동일한 값으로 캐릭터셋을 설정하는 경우
      데이터베이스의 데이터는 사용자로 전달될 때, NLS_LANG에 설정된 캐릭터셋에 기반해 적절히 변환되어 전달된다.
      즉 내부적으로는 한글이 UTF8로 저장되어 있다 할 지라도, NLS_LANG의 값에 따라 UTF8로 인코딩된 문자열을 Windows 949 코드 페이지로 변환하여 사용자에게 전달하게 되는 것이다.

      NLS_LANG 값에 있는 캐릭터셋을 UTF8로 설정하는 경우가 꼭 없는 것은 아니다. 만일 데이터베이스의 캐릭터셋이 UTF8인 상태에서, 질의를 요청한 사용자의 NLS_LANG의 값도 .UTF8이라면, 데이터베이스에서는 단순히 UTF8 문자열을 사용자에게 전달하게 되며 이를 어떻게 사용할 지는 사용자의 몫에 달려 있다.

      NLS_LANG 값을 데이터베이스 캐릭터셋에 맞추는 경우는 대략 다음과 같은 경우가 있다.

      • 데이터베이스로부터 데이터를 export받을 때
      • export 받은 데이터베이스와 같은 캐릭터셋을 가진 데이터베이스로 export된 파일을 import할 때
      • 기타 다국어 지원 애플리케이션에서 목적에 따라 사용할 수 있다.

      [주요 NLS 변수 요약]

      변수명
      정의
      기본값 설정
      설정방법
      NLS_TERRITORY
      영역 설정
      NLS_LANG 변수값에 의해 자동 설정
      초기화변수
      ALTER SESSION SET NLS_TERRITORY = 'KOREA'
      NLS_LANGUAGE
      언어 설정
      NLS_LANG 변수값에 의해 자동 설정 초기화변수
      ALTER SESSION SET NLS_TERRITORY = 'KOREAN'
      NLS_LANG
      언어,영역, 캐릭터셋
      설정
      AMERICAN_AMERICA.US7ASCII
      OS 환경변수
      NLS_COMP
      SQL에서의 비교 방식(<,>,=)
      설정
      BINARY값으로 비교
      초기화변수,OS환경변수
      ALTER SESSION SET NLS_COMP = ''
      NLS_SORT
      문자열의 정렬방법 설정
      NLS_LANGUAGE값에 따라 결정
      초기화변수,OS환경변수
      ALTER SESSION SET NLS_SORT = 'KOREAN_M'
      NLS_TERRITORY 변수에 따라 그 값이 결정되는 변수:
      • NLS_CREDIT(대차대조표 '대변'항목의 금액표기를 위한 기호. 보통 '공백'문자)
      • NLS_CURRENCY
      • NLS_DATE_FORMAT
      • NLS_DEBIT(대차대조표 '차변'항목의 금액표기를 위한 기호. 보통 '마이너스'문자)
      • NLS_ISO_CURRENCY
      • NLS_LIST_SEPARATOR(숫자를 가로로 나열할 때 각 숫자를 구분하는 기호로, 우리나라의 경우 콤마이다)
      • NLS_MOMETARY_CHARACTERS(금액 표기시 금액을 읽기 쉽게 나누는 문자로 우리나라에서는 3자리마다 ","를 추가한다)
      • NLS_NUMERIC_CHARACTERS(소수점기호와 숫자 그룹핑을 위한 문자 설정. 우리나라에서는 '.,'이다(dot와 comma)
      • NLS_TIMESTAMP_FORMAT
      • NLS_TIMESTAMP_TZ_FORMAT
      • NLS_DUAL_CURRENCY(유로화 변경 기간동안의 혼란을 막기 위해 만들어진 매개변수. 9i Release 2과 그 이후로는 EU의 유로화 변경이 완료된 상태로 NLS_CURRENCY와 값이 동일하다. 다만 미래에 다른 지역에서도 통화기호의 변경이 일어나면 사용될 수 있다)

      NLS_LANGUAGE 변수에 따라 그 값이 결정되는 변수:
      • NLS_DATE_LANGUAGE
      • NLS_SORT

      *초기화변수 : init.ora또는 spinit.ora에 설정되어 데이터베이스 구동시에 자동 설정될 수 있는 변수를 의미함
      *OS환경변수 : OS에서 초기화 방식(setenv, export 등)으로 값을 할당,변경할 수 있는 변수
    PartⅤ. KO16KSC5601에 서 지원되지 않는 글자들을 KO16KSC5601 데이터베이스에 입출력하기.

    이제는 KO16KSC5601 캐릭터셋은 “ㅤㅅㅛㅍ”, “ㅤㅃㅙㄼ”과 같은 글자를 지원하지 않는다는 것을 알게 되었을 것이다.
    그저 한글 하면 “KSC5601”을 떠 올리는 것이 보통이니 이런 사실을 아는 것만으로 획기적인 사고의 전환이 가능하다
    .

    “우리 시스템은 일반 사용자의 다양한 의견을 게시판을 통해 수렴하고 있다. 어떤 한글이 입력될 지 모르는 상황이다. KO16KSC5601”은 한글을 2350자밖에 지원하지 않으므로 사용하지 않아야겠다."

    이런 매끄러운 사고가 가능한 것이다. 그러면 어떻게 KO16KSC5601에서 지원되지 않는 글자들을 KO16KSC5601” 데이터베이스에 입출력할 수 있는가? 결론은 이렇다.

    “그런 일은 있어서도 안 되고, 가능하지도 않다”
    실망스러운가? 하지만 엄연한 사실이며, 여러분들은 이 말을 아주 심각하게 받아들여야 한다. 하지만 여전히 여러분들보다
    생각이 덜 하는 사람들, 심각하게 받아들이지 않는 사람들이 있다.

    “KO16KSC5601”에 얼마든지 그런 글자들을 삽입할 수 있던데요?
    그래서 그런 사람들에게 직접 예를 보여줄 수 있다. 한글 윈도우 상에서 다음과 같은 질의를 보여주자.
    한글 Windows 운영체제이므로 암시적으로 NLS_LANG의 값은 KOREAN_KOREA.KO16MSWIN949라고 생각하면 된다.

    DOS> sqlplus scott/tiger@KSC5601DB

    SQL> create table charset_test(charcol VARCHAR2(100), ncharcol NVARCHAR2(100));

    SQL> insert into charset_test(charcol) values('ㅤㅅㅛㅍ');

     

    1 개의 행이 만들어졌습니다.

     

    SQL> select charcol from charset_test;

     

    CHARCOL

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


    “ㅤㅅㅛㅍ”이라는 글자는 INSERT 문장 실행시 에러가 발생되지는 않았지만 결국 제대로 된 값이 삽입되지 않았다.
    여기서 다시 “삽입할 수 있다”파는 다음과 같은 주장을 펼칠 수 있다.

    “NLS_LANG =.KO16KSC5601로 설정하면 결국 캐릭터셋과 NLS_LANG 값이 일치하여 제대로 삽입할 수 있을 것이다.”

    그래서 이렇게 시도를 할 수 있다. 어떤 결과가 나오는지 살펴보자.

    DOS> set NLS_LANG=.KO16KSC5601

    DOS> sqlplus scott/tiger@KSC5601DB

    SQL> insert into charset_test(charcol) values('ㅤㅅㅛㅍ');

    ERROR:

    ORA-01756: quoted string not properly terminated


    이번에는 오히려 INSERT 문장 자체가 실패한다. 어떻게 이런 결과가 나올까? 앞서 NLS_LANG에 대해 배운 바로는
    분명히 NLS_LANG 변수와 데이터베이스의 캐릭터셋이 일치할 경우 데이터의 변환 없이 데이터가 그대로 삽입되게 된다.
    그런데 삽입 문장 자체가 실패하다니? 하지만 놀랄 필요없다. 그저 이것은 당연한 결과이다. 개발자 여러분은 최대한
    단순해질 필요가 있다. 개발자들은 가끔 너무 잘 하려고 하는 경향이 있으며, 안 되는 것은 없다고 생각한다.
    하지만 모든 꼼수는 결국 가까운 미래에 자기 자신에게 부메랑이 되어 격무의 부담으로 돌아온다는 사실을 꼭 기억하자.

    “지원되지 않는 글자를 데이터베이스에 삽입하지 말자”

    인정하건대 글자에 따라, 프로그래밍 방식에 따라 지원되지 않는 글자들로 연산이 가능한 때가 있다.
    단순히 NLS_LANG과 데이터베이스 캐릭터셋이 일치하는 바람에 들어가지 못할 데이터가 들어가게 되는 경우가 있다.
    예를 들어 위의 “ㅤ숖”자 대신에 역시 완성형에서 지원되지 않는 글자인 “똠”을 테스트하면 엉뚱하게 들어가게 된다.
    그렇지만 이것은 단순히 “우연의 일치”일 뿐이다. 1바이트와 2바이트가 섞여 있는 캐릭터셋에서 한 글자의 끝을 인식하는 과정에서 우연히도 “ㅤ똠”이 그 과정을 무사히 통과했을 뿐이다. 그렇지 않으면 대부분 “ㅤ숖”과 같이 엉뚱하게 “문 자열이 제대로 끝나지 않았다”는 에러를 만나게 된다. 확인할 겸 몇 글자 더 시도해 보기로 하자.

    DOS> set NLS_LANG=.KO16KSC5601

    DOS> sqlplus scott/tiger@KSC5601DB

    SQL> insert into charset_test(charcol) values('숖');

    ERROR:

    ORA-01756: quoted string not properly terminated

     

     

    SQL> insert into charset_test(charcol) values('똠');

     

    1 row created.

     

    SQL> insert into charset_test(charcol) values('믜');

    ERROR:

    ORA-01756: quoted string not properly terminated

     

     

    SQL> insert into charset_test(charcol) values('뾃');

     

    1 row created.

     

    SQL> insert into charset_test(charcol) values('햏');

     

    1 row created.

     

    SQL> select charcol from charset_test;

     

    CHARCOL

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

     

    햏/p>


    일부 OCI나 MS 기반의 애플리케이션에서 NLS_LANG과 캐릭터셋이 일치하는 바람에 지원되지 않는 글자들을 삽입하고 조회할 수 있기도 한다. 지금 얼마나 많은 오라클 데이터베이스 인스턴스에서 이런 현상이 벌어지고 있는지 상상하기조차 힘들다. 하지만 미래에는 그런 일이 일어나지 않아야겠다. 왜냐하면 그런 잘못된 구성으로 인해 발생하는 힘든 일은 모두 개발자가 감당하고 있기 때문이다.

    PartⅥ. 오라클 데이터베이스에서의 한글 정렬.

    1. KO16KSC5601 데이터베이스

    KO16KSC5601에서는 한글 2350자의 바이너리 정렬 순서가 한글의 언어적 정렬 방식과 동일하다. 따라서, 단순한 ORDER BY 명령어만으로 정렬의 효과를 거둘 수 있다. 그리고, 한자의 경우 한글 뒤에 한자의 음에 맞게 정렬이 된다. 예에 나와 있는 한자들은 "가구","류","애"로 모두 잘 정렬되어 있는 것을 볼 수 있다. 하지만 명심하라, 단지 한글 2350자들과 한자 4888자의 정렬일 뿐이다. 나머지 글자들에 대해서는 입출력도 불가능하거니와, 입출력을 무슨 수를 써서 했다고 하더라도 정렬이 제대로 될 리가 없다.

    SQL> SELECT text FROM nlstech_sample_sort_ksc5601 ORDER BY text;

     

    TEXT

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

    가나다라마바사

    라디오를켜라

    可口

     

    // 한글, 한자의 정렬이 올바르다

    실제로 잘 만들어진 프레임워크의 내부를 보면 좋다고 알려진 패턴을 구현한 것인 경우가 많고 구조는 잘 정의된 패턴에 기반하고 있는 경우가 일반적이다. 프레임워크는 디자인 패턴들을 실제로 구현한 결과이고 프레임워크를 문서화할 때는 패턴을 통해서 기술하는 것이 효과적이다.

    2. UTF8/AL32UTF8 데이터베이스
    UTF8 데이터베이스의 경우, 한글만을 고려하면 별다른 정렬 옵션이 필요없다. 왜냐하면 한글 11172자의 정렬 순서와 바이트 코드 정렬 순서가 일치하기 때문이다. 한글만의 정렬에 관해서는 정확성과 성능을 모두 만족한다고 볼 수 있다.

    데이터베이스에서 예제 실행:

    SQL> SELECT * FROM nlstech_sample_sort_ko ORDER BY text;

    TEXT

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

    가나다라마바사

    똠각하

    라디오를켜라

    먄해

    햏햏

     

    // 정렬 결과가 올바르다


    하지만, 한자까지 고려한다면 이것도 역시 완벽한 방법은 아니다. 다음 예제를 보자.

    SQL> SELECT text FROM nlstech_sample_sort_unicode_b ORDER BY text;

     

    TEXT

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

    可口

    가나다라마바사

    똠방각하

    라디오를켜라

    먄해

    햏햏


    한자의 경우도 단순히 유니코드 내의 바이트 코드값에 의존하게 되면 별다른 의미를 찾을 수 없는 배열만을 얻게 된다. 이 경우에는 특정 NLS_SORT 매개변수 값을 지정함으로써 좀 더 의미있는 결과를 얻을 수 있다.

    NLS_SORT=’KOREAN_M’ 이용

    NLS_SORT 값을 설정할 수 있는 방법에 대해서는 이미 앞에서 설명한 바가 있다.

    9i부터 지원되는 ‘KOREAN_M’ 정렬 방식을 이용하면 한자에 관련되어 몇 가지 원칙이 적용된다.
    • 한글은 단순히 유니코드 바이트 정렬에 의존한다
    • 모든 한글은 한자에 우선한다
    • 한자는 발음 순서대로 정렬된다

    한 마디로 KO16KSC5601에서 사용되던 정렬 방식으로 모든 한글과 한자를 정렬하겠다는 방법이다.

    KOREAN_M 사용:

    SQL> SELECT text FROM nlstech_sample_sort_unicode_b ORDER BY NLSSORT(text,'NLS_SORT=KOREAN_M');
     

    TEXT

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

    가나다라마바사

    똠방각하

    라디오를켜라

    먄해

    햏햏

    可口

    // 한자가 발음 순서대로 정렬되어 있다(가 애)

    3. KO16MSWIN949 데이터베이스

    KO16MSWIN949는 KO16KSC5601에서 지원되지 않는 8822자의 한글을 추가적으로 지원한다는 점에서 KO16KSC5601의 대안으로 자주 이용되는 캐릭터셋이다. 하지만, 기존 KO16KSC5601의 수퍼셋으로 군림하려다 보니 총 11172자의 한글의 바이트 코드가 한글의 언어적 정렬 순서와 불일치할 수 밖에 없다.

    KO16MSWIN949 데이터베이스에서 실행:

     

    SQL> SELECT * FROM nlstech_sample_sort_unicode_b ORDER BY text;

     

    TEXT

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

    똠방각하

    먄해

    가나다라마바사

    라디오를켜라

    햏햏

     

    // 정렬 결과가 엉뚱하다


    이를 극복하기 위해서는 단순한 바이트코드 정렬방식이 아니라 다른 정렬방식을 적용해야 한다.

    방법 1) NLS_SORT=’UNICODE_BINARY’ 이용

    현재 사용자 세션의 기본 정렬 방식을 변경하여 해결:

     

    SQL> ALTER SESSION set NLS_SORT = 'UNICODE_BINARY';

     

    세션이 변경되었습니다.

     

    SQL> SELECT * FROM nlstech_sample_sort_unicode_b ORDER BY text;

     

    TEXT

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

    가나다라마바사

    똠방각하

    라디오를켜라

    먄해

    햏햏



    질의 자체에 NLS_SORT 값을 지정해 주는 방법:

     

    SQL> SELECT text FROM nlstech_sample_sort_ko ORDER BY NLSSORT(text,'NLS_SORT=UNICODE_BINARY');

     

    TEXT

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

    가나다라마바사

    똠각하

    라디오를켜라

    먄해

    햏햏



    방법 2) NLS_SORT=’KOREAN_M’ 이용

    한자 데이터까지 고려하면 ‘KOREAN_M’을 사용하는 것이 좋다.

    UNICODE_BINARY 사용:

     

    SQL> SELECT text FROM nlstech_sample_sort_korean_m ORDER BY NLSSORT(text,'NLS_SORT=UNICODE_BINARY');

     

    TEXT

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

    可口

    가나다라마바사

    똠방각하

    라디오를켜라

    먄해

    햏햏

     

    // 한자의 정렬 또한 단순히 유니코드 바이트 코드에 의존한다.



    KOREAN_M 사용:

    SQL> SELECT text FROM nlstech_sample_sort_korean_m ORDER BY NLSSORT(text,'NLS_SORT=KOREAN_M');

     

    TEXT

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

    가나다라마바사

    똠방각하

    라디오를켜라

    먄해

    햏햏

    可口

     

    // 한자가 발음 순서대로 정렬되어 있다(가 애)

    4. 인덱스를 이용하여 성능 향상시키기.

      미리 NLSSORT를 이용한 인덱스를 생성하면 비록 디스크 공간이 더 필요하기는 하지만, 편리하고 성능도 좋아진다.
      인덱스는 각 NLS_SORT 값별로 여러 개를 생성할 수 있다. 어느 인덱스를 사용할 것인지는 NLS_SORT 변수의 값에 달려 있다.

      KO16MSWIN949 데이터베이스에서 실행:

       

      SQL> CREATE INDEX nlstech_sample_sort_ub ON nlstech_sample_sort_ms949 (NLSSORT(text, 'NLS_SORT = UNICODE_BINARY'));

       

      인덱스가 생성되었습니다.

       

      SQL> CREATE INDEX nlstech_sample_sort_km ON nlstech_sample_sort_ms949 (NLSSORT(text, 'NLS_SORT = KOREAN_M'));

       

      인덱스가 생성되었습니다.

       

      SQL> ALTER SESSION set NLS_SORT = 'UNICODE_BINARY';

       

      세션이 변경되었습니다.

       

      // UNICODE_BINARY 인덱스가 사용된다

      SQL> SELECT text FROM nlstech_sample_sort_ms949 ORDER BY text;

       

      TEXT

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

      可口

      가나다라마바사

      똠방각하

      라디오를켜라

      먄해

      햏햏

       

      8 개의 행이 선택되었습니다.

       

      SQL>

      SQL> ALTER SESSION set NLS_SORT = 'KOREAN_M';

       

      세션이 변경되었습니다.

       

      // KOREAN_M 인덱스가 사용된다

      SQL> SELECT text FROM nlstech_sample_sort_ms949 ORDER BY text;

       

      TEXT

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

      가나다라마바사

      똠방각하

      라디오를켜라

      먄해

      햏햏

      可口

       

      8 개의 행이 선택되었습니다.



      NLS_SORT의 값으로 설정할 수 있는 것은 버전 별로 다를 수 있다. 이는 각 버전마다 존재하는 Globalization Support Guide(8.1.7 이전에는 Nationa Language Support Guide)를 참조하면 부록에서 발견할 수 있다. 하지만 본인이 운영하는 데이터베이스라면 대략 다음과 같은 질의로 확인할 수 있다.

      SQL> select value from V$NLS_VALID_VALUES where parameter = 'SORT' ORDER BY value
      ..
      BINARY
      ...
      KOREAN_M
      ...
      UNICODE_BINARY
      ..

    PartⅦ. 오라클 데이터베이스에서 한글 비교하기.

    앞선 팁에서 짐작할 수 있듯이, 정렬이 원하는 대로 되지 않는다면, 비교 또한 제대로 될 리가 없다. 두꺼운 책의 마지막에 있는 인덱스(찾아보기)나 사전처럼, 한글 키워드를 ‘가나다’ 그룹으로 만들기 위해서는 정렬과 비교가 필수적인 기능이다.

    "오라클의 기본 비교 방식은 BINARY"

    정렬도 그렇지만, 비교할 경우에도 역시 오라클 데이터베이스는 기본적으로 BINARY 방식을 사용한다. 같은 글자라 해도 이진 코드는 각 캐릭터셋마다 다르므로 어떤 캐릭터셋에서는 “>”의 결과가 나왔던 비교문도 다른 캐릭터셋에서는 “<”의 결과가 나올 수도 있다. 우리는 이미 어떤 정렬 방식을 지정하면 제대로 정렬이 되는지 앞선 팁에서 익혔으므로, 이제 여기서는 오라클이 비교를 수행할 경우에도 내부적으로 같은 방식을 사용할 것을 지시하기만 하면 된다.

    1.NLS_COMP.

    NLS_COMP 변수는 오라클이 비교 연산들을 수행할 시 어떤 방식을 사용할 지를 지정하는 변수이다. 복잡한 NLS_SORT와는 달리 이 변수의 값으로는 오로지 BINARY 또는 ANSI 두 값만이 허용된다. BINARY는 기본으로 설정되어 있는 값으로 비교 연산은 이진 바이트 코드의 비교로 이루어진다는 것을 의미한다. 하지만, ANSI라고 설정하면, 같은 비교 연산자의 수행 방식이 이진 비교에서 언어적 비교(Linguistic Comparison)으로 전환된다. 즉, 우리가 NLS_SORT 변수에 설정한 정렬 방식이 비교 연산자(>, <, BETWEEN 등)들에게 약발이 먹힌다는 의미이다.

    NLS_SORT와는 달리 이 값은 NLSSORT 함수의 내부 패러미터가 아니므로, SQL 질의문 내에 사용될 수는 없다.
    데이터베이스 초기화 패러미터(init.ora)로 지정되거나 환경 변수, 세션 변수로 지정되어야 한다.

    ALTER SESSION SET NLS_COMP=ANSI;
    또는
    ALTER SESSION SET NLS_COMP=BINARY;


    그렇다면 다음을 살펴보자.

    KO16MSWIN949 데이터베이스에서:

    SQL> SELECT text FROM nlstech_sample_comp_ms949;

    TEXT

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

    루비

    다대기

    두상이크다

    병원

    도망

    나름대로

    도도하다

    똠방각하

    거위

    도시남녀

    너도밤나무

     

    TEXT

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

    구씨가족

    라면

     

    13 개의 행이 선택되었습니다.


    정렬되지 않은 이 문자열들의 집합에서 ‘ㄷ’ 에 속한 문자열들만을 추출하기를 원한다고 치자.
    그렇다면 먼저 다음과 같이 시도해 볼 수 있다.

    SQL> SELECT text FROM nlstech_sample_comp_ms949 WHERE text >= '다' AND text < '라';

     

    TEXT

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

    다대기

    두상이크다

    도망

    도도하다

    도시남녀


    제대로 된 것 같지만, 똠방각하가 누락되었다. 그 이유는 짐작할 수 있을 것이다.
    KO16KSC5601에 포함되어 있지 않은 이라는 글자는 아쉽게도 이진 정렬에서는 제대로 된 결과를 보여주지 못한다.
    그래서 이번에는 다음과 같이 시도해 보기로 한다.

    SQL> ALTER SESSION SET NLS_SORT='KOREAN_M';

    세션이 변경되었습니다.

    SQL> SELECT text FROM nlstech_sample_comp_ms949 WHERE text >= '다' AND text < '라';

     

    TEXT

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

    다대기

    두상이크다

    도망

    도도하다

    도시남녀


    애석하게도 결과는 똑같다. 분명히 NLS_SORT 값을 ‘KOREAN_M’으로 설정하여 정렬 시 유니코드 이진 값을 기준으로 정렬할 것을 명시했지만, 비교 연산자의 수행 시, 이 원칙이 지켜지지 않았다. 그래서 이번에는 비교 연산을 수행할 때, NLS_SORT에서 명시한 언어별 정렬 방식을 사용할 것을 명령해 보자.

    SQL> ALTER SESSION SET NLS_SORT='KOREAN_M';

     

    세션이 변경되었습니다.

     

    SQL> ALTER SESSION SET NLS_COMP=ANSI;

     

    세션이 변경되었습니다.

     

    SQL> SELECT text FROM nlstech_sample_comp_ms949 WHERE text >= '다' AND text < '라';

     

    TEXT

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

    다대기

    두상이크다

    도망

    도도하다

    똠방각하

    도시남녀

     

    6 개의 행이 선택되었습니다.


    이제야 비로소 제대로 된 결과가 나오는 것을 알 수 있다.
    PartⅧ. 글을 마치며.

    이상으로 오라클이 어떤 방식으로 NLS를 지원하는가를 한국어를 중심으로 알아보았다. 쉬운 내용이지만, 그만큼 많은 사람들이 간과하기도 쉬운 것으로, 아무리 "나는 한국에 존재하는 시스템만을 위한 소프트웨어 개발자로소이다"라고 해도 NLS에 대한 개념을 가진 상태에서 개발을 진행하는 것은 매우 중요하다. 오라클이라는 소프트웨어는 기본적인 설계부터 NLS를 고려해 만들어졌다. 시스템을 구축할 때 첫 출발을 잘 한다면, 그 어떤 소프트웨어보다도 강력하게 지원되는 NLS 기능을 마음껏 누릴 수 있을 것이다. 다음 회에는 조금 더 실용적인 사례를 살펴보기로 하겠다.



    [출처] http://www.oracle.com/technology/global/kr/pub/columns/oracle_nls_1.html




    + Recent posts