XQuery

Development/XML 2013. 2. 20. 16:27


자바 환경에서 XQuery 사용하기

자바 애플리케이션에서 XQuery로 문서 검색하기

Brett McLaughlin, 필자 겸 편집자, O'Reilly Media Inc.

요약:  XML 데이터 형식은 검색이 어렵지만, 최근에 나온 XQuery API를 사용하면 쉽고 유연하게 XML을 검색할 수 있습니다. XQJ(XQuery API for Java)는 SAX, DOM, JDOM, JAXP 등을 사용해 XML 문서를 다뤄 왔던 자바(Java™) 프로그래머들을 위한 새로운 도구입니다. 이제 자바 프로그래머들은 시스템 호출이나 썬 표준 패키지에 있는 무겁고 다루기 힘든 API를 사용하지 않고도 XQurey의 강력한 기능을 활용할 수 있습니다.


SQL 데이터베이스, XML 데이터, 그리고 쿼리, ...!

프로그래밍 세계, 특히 자바 프로그래밍이 팽창하면서 표준화된 방법도 같이 늘어나고 있다. 다시 말하면, 썬이 승인하는 API가 점점 더 많아지고 있다. 이렇게 표준화된 방법이 많아짐에 따라, 개발자들이 자신들의 핵심 기술을 제외하고도 배워야 할 새로운 기술들도 늘어나고 있다.

그 중에서 좀 더 흥미롭고 가치 있으며 숙지해야 할 API들은 데이터 관리와 관련된 것이다. 애플리케이션의 사용성은 그 겉모습이 얼마나 멋진가에 상관없이, 궁극적으로는 데이터를 관리하는 능력에 따라 결정된다. 그리고 API 수가 끊임없이 증가하는 반면에 공통적으로 많이 사용되는 데이터 형식은 점차로 줄어든다. 일부 데이터 관리자들이 객체 지향 데이터베이스 관리시스템(object-oriented database management system: OODBMS)이나 XML 기반 데이터베이스(XML-driven database)를 사용하지만, 관계형 데이터베이스(relational database: RDBMS)는 힘든 시기를 거쳐 잘 견뎌왔고, 데이터 관리자는 대부분 여전히 관계형 데이터베이스를 선택한다. 따라서 자바 개발자는 SQL 데이터베이스와 연동하기 위해 JDBC(데이터베이스와의 연결을 위해)나 JDO(자바 데이터 객체: Java Data Objects)를 사용한다.

SQL 데이터베이스 vs 다른 데이터베이스들

이 글에서 사용하는 쿼리나 데이터베이스라는 용어는 SQL 데이터베이스, 즉 관계형 데이터베이스를 가정한 것이다. 그러나 훌륭한 XML 데이터베이스나 객체 데이터베이스들도 있다.

XML 데이터베이스에 관심이 있다면 DB2® Express-C를 받아 사용해볼 수 있다(링크는 참고자료를 보라). 가장 눈에 띄는 점은, XML 데이터베이스에서는XML 문서와 관계형 자료 간의 변환이 필요 없다는 것이다. XML 데이터베이스는 XML 데이터를 저장하기 때문에, XQuery가 데이터베이스를 위한 실질적인 쿼리 언어가 된다는 이 글의 주제와도 무관하지 않다.

데이터베이스에 들어 있지 않은 데이터는 거의 XML 데이터 형식으로 표준화되었다. XML은 구질구질하지만, 견고하며, XML을 다루는 API도 다양하다. 파싱을 하든, 데이터 바인딩을 하든, 또는 변형(transforming)을 하든, XML을 사용하지 않는 애플리케이션은 제한적이고, 조금은 시대에 뒤떨어져 보인다.

겉보기에는 아무 연관 없는 두 가지 사실, 즉 데이터를 SQL 데이터베이스에 저장하고 데이터베이스 밖의 모든 데이터는 XML로 관리하려는 성향은 독특한 문제를 만들었다. SQL 데이터베이스는 쿼리(query)가 쉽지만, XML 문서는 그렇지 못하다. 사용자들은 데이터를 쉽게 검색하기를 원한다. 데이터베이스 안에 있는 데이터는 검색이 잘 되지만, XML 안에 있는 데이터는 검색하기 어렵다. 분명한 점은, XML 형식의 데이터를 단지 검색을 쉽게 하기 위해 데이터베이스에 저장하는 것은 잘못된 접근 방식이라는 것이다. 이 때문에 XQuery가 등장했고, XQJ(XQuery API for Java)가 만들어졌다.

XQuery: 세 가지 기술

자주 쓰는 약어

  • API: application programming interface
  • DOM: Document Object Model
  • GUI: Graphical User Interface
  • IDE: Integrated development environment
  • JAXP: Java API for XML Processing
  • SAX: Simple API for XML
  • W3C: World Wide Web Consortium
  • XML: Extensible Markup Language

간단히 말하면 XQuery는 XML 문서를 검색하는 데 쓰는 언어다. 마치 SQL이 SELECT와 FROM에 특별한 의미를 부여하듯이 — 어떤 특별한 문맥에서 — XQuery는 슬래시(/), 골뱅이(@)와 몇 가지 키워드에 의미를 정의한다.

XQuery는 세 개의 핵심 요소로 이루어져 있다.

  1. XPath 스펙: XML 문서에서 0개, 한 개, 또는 여러 개의 노드를 선택하는 방법
  2. 특정 XML 문서를 선택하기 위한 부가적인 구문, 그리고 XPath의 결과 노드들에 검색 조건 추가
  3. XQJ(XQuery for Java)와 같이 특정 프로그래밍 언어에서 XQuery 표현들을 평가하는 API

XQuery를 제대로 익히려면 위 세 가지 구성요소를 모두 알아야 한다. 자바 프로그래머라면, XPath를 배우고, 추가 XPath 구문을 배우고, XQuery 표현식을 평가하기 위한 자바 기반 API를 사용해야 한다.

다행스럽게도 XPath와 XQuery구문은 상당히 직관적이다. 유닉스(UNIX®) 셸, 맥 OS X 터미널, DOS 창에서 디렉터리 구조를 사용해 본 적이 있다면 쉽게 이해될 것이다. 거기에 <, >, = 같은 기본적인 연산자를 알고 있다면 XPath 전문가가 되는 길에서 절반은 온 셈이다.

XPath Basics

XQuery는 XPath라는 또 다른 XML 스펙에 전적으로 의존한다. XPath는 XML 문서의 일부를 나타내는 경로(path)를 생성하는 방법을 정의한다. 예를 들어, /play/act/scene play 루트 요소(root element) 안에 있는 act 요소를 찾고, 그 안에 속해 있는 모든 scene 요소를 가리킨다.

XPath의 경로는 상대적이다

기본적인 XPath는 요소 이름과 슬래시(/)를 사용한다. 그리고 기본 값으로, XPath는 XML 문서의 현재 위치에서 시작한다. 예를 들면, DOM을 사용하고 있고, speech 요소로 가서, speaker 경로를 찾으면 현재 위치인 speech 요소 안에 있는 모든 speaker 요소를 가리킨다. 즉, XPath는 문서 안에서 현재 위치로부터 상대적으로 평가한다.

현재 노드에서 다른 노드로 이동하기

문서의 루트로 이동하려면, 경로 앞에 슬래시(/)를 붙이면 된다. 따라서 /play는 현재 어느 위치에 있든 play라는 루트 요소를 찾는다. ../를 붙이면 현재 요소의 부모 요소를 찾는다. 이러한 것들은 디렉터리 구조와 비슷하다. 예를 들어, ../../personae/title은 현재 요소에서 두 단계 위로 올라간 후 personae 요소를 찾고, 그 안에 있는 title 요소를 찾는다.

속성 선택하기

XPath는 요소 외에도 많은 것을 검색할 수 있다. 예를 들어, /cds/cd@title은 cds라는 이름의 루트 요소 안에 있는 cd 요소에서 title이라는 이름의 모든 속성(attribute)을 반환한다.

@ 구문은 속성의 이 아닌 속성 자체를 반환한다는 점을 주의해야 한다. @isbn은 isbn 이라는 속성의 값이 아닌, isbn 이라는 이름의 모든 속성을 검색한다. XPath에서 속성이라는 용어는 속성의 이름과 값을 의미한다(더 자세한 내용은 노드 선택하기를 참조하라).

텍스트 선택하기

텍스트는 요소 안에 내포된 요소다

요소와 그 요소 안에 있는 텍스트를 이해하는 일반적인 방법은 요소가 "텍스트를 포함"한다고 생각하는 것이다. 그러나 "title 요소의 값은 'You Can't Count On Me.'다" 같은 표현은 잘못된 것이다. 텍스트는 요소 안에 내포된 또 다른 요소일 뿐이다. 텍스트가 요소에 속한 것으로 봐도 무방하지만, 속성이 텍스트 값을 갖는 것과는 다르다.

이 사실을 이해하는 더 좋은 방법은 텍스트는 요소에 내포된 요소라는 사실 그대로다. 텍스트는 텍스트를 둘러싼 요소의 값이 아니며, 요소는 텍스트의 부모다.

요소와 속성을 검색하는 것처럼, 요소 안에 있는 텍스트도 검색할 수 있다. 예를 들어, /cds/cd/title 같이 XPath가 요소의 이름으로 끝나면 그 요소 안에 있는 텍스트를 포함하지 않는다. 요소 안에 있는 텍스트를 검색하려면,text() 구문을 사용하면 된다. CD들의 모든 title 을 검색하고 싶다면/cds/cd/title/text()를 사용하면 된다. 이러한 경로는 요소를 반환하는 것이 아니라, 요소 안에 있는 텍스트를 반환한다.

노드 선택하기

효과적으로 XPath를 사용하려면, XPath의 평가 결과가 항상 노드 집합(node set)이라는 점을 명심해야 한다. 그 집합은 비어있을 수도 있고, 하나 이상의 노드들을 포함할 수도 있다. 어쨌든 XPath의 결과는 항상 집합이다. 경로는 요소나 속성이나 텍스트를 반환한다는 일반적인 생각을 포함하고 있지만 그게 전부는 아니다.

DOM을 사용한다면 노드에 대해 생각해본 적이 있을 것이다. DOM에서는, XML 문서 안에 있는 모든 것, 즉 요소, 속성, 텍스트는 노드다. 요소는 요소 노드(element node), 속성은 속성 노드(attribute node)다. 심지어 요소 안에 있는 텍스트도 텍스트 노드(text node)다. 따라서../../personae/title title 요소를 검색하는 경로지만, 사실 노드 집합을 반환한다. 그 집합은 비어있거나(검색 조건에 맞은 요소가 없거나) 여러 개의 노드를 포함할 수 있다. 이 경우에는 반환된 노드 집합에 포함된 모든 노드가 "title"이라는 이름의 요소일 것이다.

경로가 점점 복잡해지면서, 잠재적으로 더 넓은 집합 — 속성과 요소, 또는 텍스트와 요소를 동시에 포함하는 — 을 선택할 수 있겠지만, 결론은 노드 집합을 선택하는 것이다. XQuery를 잘 사용하려면 이 점을 명심해야 한다. XPath를 사용하면 노드 집합을 선택할 수 있다. 그리고 XQuery와 함께 사용하면, 검색 조건으로 그러한 노드들의 일부분을 선택하거나 여러 노드 집합을 합쳐 검색 조건에 적용할 수 있다. 노드 집합은 여러 가지 형태(요소, 텍스트, 속성)라는 것을 염두에 둔다면, 경로를 좀 더 나은 방법으로 사용할 수 있고, 그 경로들은 원하는 것을 그대로 반환할 것이다.

XPath에 검색 조건 사용하기

지금까지 노드(요소 또는 속성)와 부모 노드(텍스트나 주어진 노드의 모든 자식 노드를 선택할 경우)의 이름으로 노드 집합을 선택하는 방법을 알아 보았다. 이것만으로도 XPath는 꽤 강력하지만, XPath는 검색을 위한 몇 가지 술어(predicate)를 더 제공한다.

술어의 기초와 구문

마이크로소프트 인터넷 익스플로러(Microsoft Internet Explorer)와 여타 브라우저의 오류

XPath 스펙에는 [1]이 집합에서 첫 번째 노드를 참조한다고 명시되어 있다. 즉, XPath의 노드 집합의 색인은 1부터 시작한다. 그러나 인터넷 익스플로러의 대부분의 버전에서는 색인이 0부터 시작하도록 XPath를 잘못 구현하고 있어서, [1]은 배열의 두 번째 항목을 참조하며, 첫 번째 항목을 참조하려면[0]을 사용해야 한다. 브라우저에서 직접 시험해볼 필요가 있지만, 어쨌든 첫 번째 항목을 참조하기 위해 [0]이 아니라[1]을 사용하는 것이 정확한 동작이다.

술어는 노드 집합에 적용될 수 있는 표현식(expression)이며, 대괄호([, ])로 표기한다. 술어는 그 술어의 왼쪽에 있는 경로가 가리키는 노드 집합에 적용된다. 예를 들어, /cds/cd라는 경로는 루트 요소인 cds 안에 있는 모든 cd 요소를 선택한다. 그런데 첫 번째 CD를 원한다면 /cds/cd[1]와 같이 술어를 사용하면 된다. 이 경로는 /cds/cd 경로가 선택한 노드들 중에서 첫 번째 노드를 반환한다.

술어는 모든 노드 집합에 적용할 수 있다

술어는 그 술어의 바로 왼쪽에 있는 노드 집합에 적용된다는 것을 기억하라. 그렇다고 해서 술어가 오직 완전한 XPath의 끝 부분에만 사용할 수 있다는 뜻은 아니다. XPath가 경로들의 집합이라고 생각한다면, 각각의 경로는 각각의 노드 집합을 반환할 것이다. 예를 들어, /cds/cd/title은 다음과 같이 세 개의 경로를 포함한다.

  1. /cds는 "cds"라는 이름의 루트 요소를 반환한다(하나의 요소 노드를 가진 노드 집합).
  2. cd(앞의 노드 집합에 상대적으로)는 앞의 노드 집합 안에 있는 모든 cd 요소를 반환한다.
  3. title(cd와 마찬가지로, 앞의 노드 집합에 상대적으로)은 앞의 노드 집합 안에 있는 모든 title 요소를 반환한다.

술어는 반드시 노드 집합에 적용되어야 하며, 어느 노드 집합에도 적용될 수 있다. 예를 들어, /cds[1]/cd[2]/title[1]은 올바르게 사용된 경로인데, /cds로 검색된 첫 번째 노드 집합을 선택하고, /cds[1]/cd로 두 번째 노드 집합을 선택한다. 그리고 여기서/cds[1]/cd[2]/title로 선택된 노드들 중에서 첫 번째 노드를 선택한다.

주의: 이 경로의 각 부분은 특별한 의미가 없다. 예를 들어, /로 투트 요소를 선택한 다음, [1] 술어를 적용하면 항상 집합 내의 첫 번째이자 유일한 노드를 반환한다. 술어가 요소를 반환하지 않는 유일한 경우는 경로에 지정한 루트 요소가 문서의 실제 루트 요소와 달라 노드 집합이 비어있을 경우뿐이다. 그러나 설명을 위해서건 기술적인 관점이건 XPath 자체만 사용해도 되고, 술어를 사용해도 된다.

숫자 인덱스 외에 유용하게 사용할 수 있는 술어

숫자만으로 참조하도록 하는 API는 극히 드물지만, 그렇게 하면 항상 원하는 항목이 정확히 어디에 있는지 알 수 있다. XPath에는 그 외에도 많은 술어를 제공한다. 일단, last() 함수를 사용하면 얼마나 많은 항목이 있든 그 중에서 마지막 항목을 선택할 수 있다. 예를 들어,/cds/cd[last]는 문서에서 마지막 cd를 선택한다.

position() 함수를 사용하면 어떤 특정 위치보다 앞에 있거나 뒤에 있는 항목을 선택할 수 있다. position() 함수는 주어진 노드들 속에서 위치값을 반환한다. 예를 들어, 처음 다섯 개의 CD들을 검색하기 원한다면 /cds/cd[position()<6] 패스를 사용하면 된다. 이 패스는position() 값이 6보다 작은 모든 노드를 선택한다.

데이터를 기준으로 노드 선택하기

마지막으로 — 지금 보고 있는 XPath에 대한 짧은 소개 글에서 — 노드의 자식 요소나 노드의 속성을 기준으로 노드를 선택할 수 있다. XPath에서는 순차적인 위치가 상위 경로의 노드들을 기준으로 하듯이, 노드들의 술어는 그 술어가 적용된 노드들을 기준으로 한다. 술어에 연산자 <, >를 덧붙여 사용하면 검색된 노드들 중에서 단순히 위치가 아닌 데이터를 기준으로 선택할 수 있다.

속성 이름이 "style"이고 값이 "folk"인 CD들을 검색해보자. 먼저 모든 CD를 검색하는 표현식을 사용하고, 그러고 나서 그 CD들의 style 속성을 "folk"로 비교한다. XPath로 표현한다면 /cds/cd[@style='folk']가 될 것이다. 지금까지 설명한 것에 따르면 이것은 상당히 이해하기 쉽다. 먼저, /cds/cd로 모든 cd 노드를 선택한다. 그리고 @style 술어로 각 노드의 속성 이름이 "style"인 것을 분류한다. 앞에서 말했듯이 이름 앞에 @가 오는 것은 속성을 가리킨다. 그리고 속성은 이미 선택된 노드들(이 경우는 모든 cd 요소)에 상대적이라고 가정한다. 그러고 나서 이 속성 값들을 "folk"와 비교한다. 그리고 일치하는 속성을 가진 노드들이 반환된다. 그 외 다른 것들은 결과 값에서 빠진다.

같은 과정이 선택된 노드들 안에 있는 요소에도 적용된다. Listing 1과 같은 구조를 가진 문서가 있다고 가정하자.


Listing 1. 예제의 CD 목록 문서의 구조
                
<cds>
 <cd style="some-style">
  <title>CD title</title>
  <track-listing>
    <track>Track title</track>
    <!-- More track elements... -->
  </track-listing>
 </cd>
 <!-- More CDs... -->
</cds>

이제, 열 개 또는 그 이상의 트랙을 가진 CD를 검색해보자. 먼저, 모든 cd 요소를 선택하는데, 지금까지 여러 번 언급한 /cds/cd 경로가 필요하다. 그리고 술어를 사용하면 이미 선택한 노드들 중에서 특정 노드의 개수를 얻을 수 있다. 이 예에서는, track-listing 안에 있는 모든track 요소 노드를 선택하면 된다. 마지막으로 XPath에서 count() 함수를 사용하여 노드들의 수, 이 예에서는 10과 비교하면 된다. 이 모든 과정을 하나로 만든다면 다음과 같은 경로와 술어가 된다: /cds/cd[count(track-listing/track) >= 10].

XPath 술어의 모순점

주의 깊게 읽었다면, XPath가 요소 텍스트와 속성을 다루는 방법에 있어서 약간의 모순점을 발견했을 것이다. 앞에서 속성 노드는 속성과 그 속성 값 모두를 의미하고, 이것은 하나의 정보로 취급된다고 설명했다. 그러나 술어에서 @type과 같은 표현식은 type 속성 노드 전체를 나타내는 것이 아니라 그 속성의 값을 나타낸다. 그래서 @type='reggae'처럼 값을 다른 것과 비교할 수 있다.

같은 방식으로, 다음과 같은 술어로 텍스트 요소를 나타낼 수 있다: /cds/cd[title='Revolver']. 여기서 cd 안에 있는 type 요소의 값은 "Revolver" 값과 비교된다. 속성 노드의 경우처럼 요소에 대한 표준 규칙들에 위배된다. 일반적으로 요소 노드는 텍스트 노드의 부모다. 그러나 지금 말한 것처럼 술어는 요소 안에 있는 텍스트를 나타내지 않는 경우도 있다.

규칙에 대한 이런 미세한 변형은 요소와 속성을 정확히 이해하고 있다면 큰 문제가 되지 않는다. 단지 속성과 그 속성의 값을 하나의 노드로 취급할 경우와 속성의 값을 나타낼 경우만 구분하면 된다. 비슷한 개념으로, 요소가 다른 텍스트 노드를 포함하는 경우와 요소의 텍스트를 다른 값과 비교할 경우를 구분하면 된다.

XQuery와 함께 사용하기

XPath는 놀라울 정도로 강력하지만 제약 사항도 조금 있다. 무엇보다도 XPath는 정적 데이터에 아주 적합해 술어와 XPath를 사용하면 요소, 속성, 텍스트를 특정 데이터와 비교하는 특정 문서를 위한 XPath 쿼리를 만들 수 있다. 또한, XPath는 어떤 제어 구조(if/else 문)도 갖고 있지 않으며, 단순한 비교 이상의 복잡한 처리를 할 수 없다.

엄밀하게 말하면, 이러한 제약사항은 프로그래머가 아닌 대부분의 사람에게는 큰 문제가 되지 않는다. 그러나 자바(또는 C#, 파이썬) 프로그래머들이 XPath를 많이 사용한다면, 그들은 모든 프로그래밍 언어의 능력과 함께 XPath가 단독으로 제공하는 기능을 넘어서 XML 문서를 검색할 수 있는 더 좋은 방법들에 대한 아이디어를 빠르게 내놓을 것이다. 그것이 바로 XQuery가 지향하는 것이다.

문서 선택하기

XQuery에서 자주 쓰이진 않지만, 가장 중요한 기능은 XPath를 적용할 문서를 지정하는 것이다. 예를 들어, /cds/cd[title='Revolver']같은 XPath 경로를 적용할 문서를 XQuery의 doc() 함수를 사용해 지정할 수 있다. catalog.xml을 검색한다면, XQuery 표현식인doc("catalog.xml")/cds/cd[title='Revolver']를 사용하면 된다.

이 작은 기능 덕분에 프로그램으로 문서를 선택하거나(아마도 사용자가 입력한 값에 따라) 또는 여러 문서를 반복문에 적용해(아마도 네트워크에 있는 모든 iTunes 카탈로그) 각각의 문장에 쉽게 적용하는 코드를 작성할 수 있다.

XQuery와 FLWOR

물론, XQuery는 단순히 실행 중에 문서를 선택하는 것 이상의 기능을 제공한다. 이른바 FLWOR을 수행할 때 XQuery는 더 강력한 기능을 제공한다. FLWOR은 "for, let, where, order by, return"의 약자다. XQuery에는 좀 더 정확한 결과를 얻기 위해 사용할 수 있는 구문이 많다.

flower가 아니다

누가 왜 FLOWR 대신 FLWOR라는 약어를 선택했는지는 명확하지 않지만, FLOWR라고 했다면 "flower"와 같이 발음했을 것이다. 많은 표현식에서, 구문들이 for, where, order by,return 순서로 나오기 때문이라는 것이 가장 그럴듯한 이유지만, XQuery 표현식의 앞이나 중간에 나오는 let을 빼먹었다.

XML 스펙 표준화 단체(W3C)에서 FLWOR이라는 약어를 선택했지만, 쓸 일도 없을 것이고, XML 전문가들 앞에서 쓰면 우스꽝스러워 보일지도 모른다.

SQL에 익숙한 사람들이라면 좀 더 쉽게 접근할 수 있다. WHERE나 ORDER BY는 SQL 쿼리에서는 널리 쓰이는 부분이다. 그리고 프로그래머들은 for에 익숙하다. 다음은 FLWOR에서 각 절의 역할에 대한 간단한 설명이다.

  • for: 노드 집합을 선택하고 그것을 순회(iterate)할 수 있다. for는 다양한 방법으로 노드 집합의 현재 값을 변수를 할당하므로 그 변수를 조작하면 된다.
  • let: 변수에 값을 할당한다. (곧 볼) 다른 FLWOR 절보다는 자주 사용하지 않는다.
  • where: 노드 집합에 XPath보다 더 강력한 검색 조건을 적용할 수 있다. 대부분의 경우, where 절에서 XPath보다 더 많은 일을 하지 않으며, 단지 XPath 내에 있는 술어의 위치를 옮겨 놓은 것이다.
  • order by: order by 절은 데이터를 변경하거나 걸러내지 않고, 결과 값들을 정렬하고, XPath에서 사용하는 위치 값이 아닌 다른 어떤 것을 기준으로 값들을 분류한다.
  • return: 노드 집합에서 연산을 한 후, 결과로 노드 집합이 아닌 어떤 것을 반환할 때 사용한다. 어떤 값을 선택하고, 정렬하고, 걸러내고, 그리고 결과 값으로 자식 요소만 반환할 때, return을 사용한다.

각 절에 대해 조금 더 자세히 알아보자.

for 문 사용하기

for 문은 자바와 C# 같은 프로그램 언어에서 사용하는 방법과 거의 비슷하게 쓴다. for 문의 형식은 다음과 같다.

for $variable-name in XPath
...

변수명은 어떤 식별자(예를 들면 x)라도 상관없다. 일반적으로 변수명은 용도(firstName, title)에 따라 정해지지만, 이 변수는 반복 카운터로만 사용할 것이므로, 문자 한 개만 사용하는 것도 나쁘지 않다.

XPath는 어떤 것이라도 사용할 수 있다. /cds/cd는 완벽한 예다. 예를 들어 다음과 같이 사용할 수 있다.

for $cd in doc("catalog.xml")/cds/cd
...

이것이 전부다. 변수 &cd는 XPath 경로 /cds/cd에 의해 반환되는 각각의 노드 값을 갖는다. 위 예에서, ...이라고 표시한 것은 XQuery 표현식의 나머지 부분들인데, 이에 대해서는 뒤에 알아 보겠다.

프로그래머들의 이해를 돕기 위해 예를 들면, 위 문장은 아래 문장과 다를 바 없다.

for (int i = 0; i<cdArray.length; i++) {
  CD cd = cdArray[i];
  // Process CD
}

리스트로 표현해도 비슷하다.

for (Iterator i = cdList.iterator(); i.hasNext(); ) {
  CD cd = (CD)i.next();
  // Process each CD
}

let을 사용하여 변수 할당하기

let 절은 변수를 할당하는 데 쓴다. 이미 XQuery에서는 식별자 앞에 달러($) 표시를 붙임으로써 변수가 정의됨을 알았을 것이다. 대부분의 경우에는 앞에서 설명한 것처럼 XQuery에서는 let 절로 명확하게 변수를 생성하기보다는 for 절에서 묵시적으로 변수를 생성할 것이다.

그러나 명시적으로 변수를 생성하려면 아래와 같이 하면 된다.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
...

예제를 보면 검색 대상 문서가 변수에 할당되었다. XQuery는 변수에 할당을 위해 :=를 사용하는데, 파스칼(Pascal) 언어를 사용해 본 적이 없다면 이 기호가 조금 생소할 것이다. 좀 더 실질적인 상황에서는, XML 문서 목록을 순회하는 함수에서 $docName에 그 문서들의 이름을 차례로 할당한다. 더 큰 목록에 포함된  문서에서 cd 요소를 선택하고, 동일한 방식으로 처리할 수 있다.

return을 사용하여 쿼리 완성하기

FLWOR에서 order by는 잠시 건너 뛰고, 지금은 먼저 쿼리를 완성해보자. 지금까지 설명한 내용은 다음과 같다.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
...

다음 단계는 반환이다. 이 쿼리는 모든 cd 요소를 선택했고, 그것이 검색하려던 노드 집합이긴 하지만, 그대로 반환하면 사용하기 힘들다. 이러한 요소들을 그대로 반환하기보다는 title 요소에 저장된 각 CD의 제목처럼 좀 더 구체적인 데이터를 반환하려고 한다. 이때 return이 필요하다.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
return $cd/title/text()

설명하자면, 모든 cd 요소를 선택했고, 각 노드는 $cd에 차례로 할당된다. 마지막으로 return 절은 요소 그 자체를 반환하는 대신, 검색하려던 데이터를 가지고 있는 "title"이라는 이름의 자식 요소를 반환한다.

아래와 같은 단순한 실수를 하지 않도록 주의하라.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
return /cds/cd/title/text()
            

위 예제는 세 가지 중대한 문제를 갖고 있다.

  • 첫째, XQuery의 목적을 헛되게 한다. 아무런 쿼리도 수행하지 않고, XPath의 위치만 반환한다.
  • 둘째, CD들을 검색할 문서를 가리키는 doc($docName)에 상관없이 데이터를 반환한다.
  • 마지막이 가장 중대한 문제인데, for 절로 반환된 노드 집합에 적용한 필터나 순서를 무시한다.

다음 작업을 하려고 한다면 이 점은 매우 중요하다. for 절에서 정의하고, return 절에서 다시 사용할 수 있는 변수가 있다는 점을 명심해라. 이 단순하면서도 중요한 법칙을 잘 지키면, 쿼리가 의도한 대로 수행됨을 확신할 수 있다.

where 절로 검색 조건 지정하기

where 절을 사용하면 XQuery의 검색 조건을 더욱 세밀하게 지정할 수 있다. XQuery의 where 절은 SQL에서와 같이 동작한다. 결과 집합을 정제하려면 검색에 where 절을 사용하면 된다. 간단한 예제를 보자.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
where $cd@type = 'reggae'
return $cd/title/text()

위의 예제는 title이 reggae인 모든 CD를 반환한다. where 절이 꽤 직관적이어서 따로 설명할 것도 없지만, and와 함께 사용하면 좀 더 복잡한 조건을 적용할 수 있다.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
where $cd@type = 'reggae'
  and count($cd/track-listing/track) > 10
return $cd/title/text()

위 예제는 열 개 이상의 track을 가진 모든 reggae CD를 반환한다.

where 절의 또 하나의 중요한 용도는 조인(join)을 수행하는 것이다. Listing 2와 같은 XML 파일이 있다고 가정하자.


Listing 2. CD 목록 문서의 확장된 구조
                
<cds>
 <cd style="some-style">
  <title>CD title</title>
  <artist id="289" />
  <track-listing>
    <track>Track title</track>
    <!-- More track elements... -->
  </track-listing>
 </cd>
 <!-- More CDs... -->

 <artists>
  <artist id="289">
   <firstName>Bob</firstName>
   <lastName>Marley</lastName>
  </artist>

  <!-- More artist elements -->
 </artists>
</cds>

위 예제는 Listing 1의 XML 구조를 확장한 것인데, id 속성으로 식별할 수 있는 artist 요소가 추가되었고, 각각의 cd 요소에 포함된 한 개 이상이 artist 요소에 의해 각 CD와 조인되어 있다.

XQuery를 사용하면, CD와 그 CD의 artist를 조인할 수 있다. 아래의 예를 보자.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
                where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
return $cd/title/text()

여기에서 눈 여겨 볼 것은 두 가지다. 첫 번째로, for 절은 한 개 이상의 변수를 정의할 수 있다. 단지 CD들을 식별하는 대신, 여기서는$artist도 정의해 artist 요소를 다루기 쉽다.

다음으로, where 절은 CD와 그 CD의 artist를 조인한다: where $cd/artist/$id = $artist/$id. 이렇게 하면, 각각의 CD와 그 CD의 artist들이 연결되고, SQL의 join과 같은 결과를 가져온다는 것을 기억하라. 다음으로, 검색 조건을 더 정의했다: $artist/lastName = 'Marley'. 이렇게 하면, "Marley"라는 lastName을 가진 모든 artist를 검색한다. 조인을 한 다음, return 절로 CD의 title들을 반환한다. 그 결과, lastName이 "Marley"인 artist를 포함하는 모든 CD의 title을 얻는다.

이 예제를 통해 XQuery를 어디에 써먹을지 알 수 있을 것이다. (아마도 많은 문서가 고급 검색을 염두에 둔 구조를 갖고 있지 않겠지만) XML 문서에 대해 SQL과 비슷한 복잡한 조인과 선택을 수행할 수 있다.

노드 순서 지정하기

where 절은 SQL의 WHERE와 거의 비슷하게 동작하지만, order by 절은 SQL의 ORDER BY와 똑같이 동작한다. XPath로 참조할 수 있는 어떤 것으로든 결과를 정렬할 수 있다.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
order by $cd/release/@date
return $cd/title/text()

위 예에서는, 반환된 CD의 title들을 각 CD의 자식 요소인 release date 속성에 따라 정렬한다. 기본적으로 정렬은 오름차순(ascending)이지만, 원한다면 아래와 같이 명시적으로 지정할 수 있다.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
order by $cd/release/@date ascending
return $cd/title/text()

물론, 내림차순(descending)으로 정렬할 수도 있다. 아래 예는 최근에 발매된 CD를 가장 먼저 반환한다.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
order by $cd/release/@date descending
return $cd/title/text()

주의: date 속성을 날짜 타입(date type)으로 인식하는 스키마(schema)나 XQuery 처리기를 사용하지 않는다면, 위 문장은 오류가 발생한다. 더 안 좋은 것은 date를 문자열로 인식해 알파벳 순서로 정렬한다는 사실이다. 거의 모든 현대적인 처리기는 날짜를 인식하기 때문에 별로 신경 쓸 필요는 없다.

한 번에 여러 개의 정렬 조건을 적용할 수 있다. 예를 들어, 위의 예는 "Marley"라는 lastName의 artist를 포함하는 모든 CD를 반환하면서, 발매일 순으로 정렬한다. 아래 표현식은 artist의 이름, 발매일 순으로 정렬한다.

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
order by $artist/firstName, $cd/release/@date
return $cd/title/text()

모든 결과의 lastName은 같으므로 $artist/firstName에 앞서 $artist/lastName으로 정렬할 필요가 없다는 점을 주목하라.

자바 기술에 접목하기

자바 환경에서 XQuery를 사용하는 방법에 대한 글을 많이 접했겠지만, XQJ(XQuery API for Java)를 선택하는 프로그래머는 대부분 XPath와 XQuery를 대충 보고 넘어간다. 이제 여기에서 조금 더 배운 기초를 바탕으로 자바 프로그램에 XQuery를 사용해 보자.

XQuery는 벤더 중립적인 스펙이다

XQJ는 썬의 후원 아래, 자바 커뮤니티 프로세스의 일부인 JSR 225(링크는 참고자료를 보라)로 개발되고 있다. 스펙에는 몇몇 다른 벤더(썬, 노키아부터 BEA, 오라클, 인텔에 이르기까지)뿐 아니라 소수의 핵심적인 개인들(서블릿, JDOM, XML로 유명한 Jason Hunter 같은)도 참여하는데, 그 나름대로 특정 데이터베이스 벤더나 XML 제품 제조사에 종속되는 것을 막기 위해 노력하고 있다.

XQJ는 벤더 종속적인 구현체다

안타깝지만, XQJ에 대한 썬 표준 구현체는 아직 없다. 전문가 그룹에 있는 벤더 대부분은 자사 제품으로 XQJ를 구현하는 회사에서 일하고 있는데, 이는 특정 벤더에 국한된 문제를 다뤄야 한다는 것을 의미한다. 물론, 오랫동안 XML을 다뤄왔던 이들에겐 2000년대 초반에 있었던 XML 파서와 XSL 처리기 전쟁과 별 다를 바가 없다. 적당한 때가 되면, XQJ는 표준화될 것이고, 썬은 자체적으로 XQJ를 구현하거나, XML 파서와 XSL 처리기가 JAXP가 동작하는 것처럼 XQJ 구현을 위한 추상화된 API를 발표할 것이다.

XQJ 구현체 얻기

XQJ를 시작하는 쉬운 방법은 DataDirect에서 무료 시험판을 다운로드하는 것이다. 양식을 작성하는 게 귀찮겠지만, 이 글을 따라가다 보면 충분한 시간을 벌 수 있다. DataDirect XQuery 다운로드 사이트를 방문하자(참고자료). XML Documents Only 옵션을 선택하고, 접근을 원하는 데이터베이스를 입력해야 한다. 이 옵션들을 선택하면 datadirectxquery.jar 파일을 받을 수 있는 곳에 대한 안내가 들어있는 이메일을 받는다.

JAR 파일 풀기

설치 과정은 조금 혼란스럽다. 먼저, jar 명령을 사용하여 다운로드한 datadirectxquery.jar의 압축을 풀어야 한다. 그러나 그 전에 먼저, 설치할 디렉터리를 만들고, 그 디렉터리에 JAR 파일을 푼 다음, jar 명령을 실행하자.

[bdm0509:~/Desktop] mkdir xqj
[bdm0509:~/Desktop] cd xqj
[bdm0509:~/Desktop/xqj] jar xvf ../datadirectxquery.jar 
 inflated: XQueryInstaller.jar
 inflated: ddxqj.jar
 inflated: ExtensionTool.jar
 inflated: Readme.txt
 inflated: 3rdPartySoftware.txt
 inflated: Fixes.txt
 inflated: installer.properties

설치 프로그램 실행하기

이제 조금 전에 만든 디렉터리를 열고 XQueryInstaller.jar 파일을 더블클릭하자. 자바가 설치되어 있다면, GUI 설치 프로그램이 실행될 것이다.

trial 또는 licensed version 중에 선택하는 단계에서, trial을 선택하자. 다음으로, 설치할 디렉터리를 선택해야 한다. 여기에서 입력한 디렉터리에 쓰기 권한이 있는지 확인하자. 이 예에서는 /usr/local/java/xqj를 선택했는데, 먼저 /usr/local/java 디렉터리에 쓰기 권한이 있는지 확인해야 한다. 설치 과정에서 마지막 하위 디렉터리(이 예에서는 xqj)가 만들어지고, 그 안에 DataDirectory XQuery 파일들이 설치된다. 마지막으로Finish를 클릭하고 설치가 마무리된다.

모두 마쳤으면, 새 디렉터리의 내용을 확인하자. 아래에 보이는 목록과 비슷해야 한다.

[bdm0509:/usr/local/java] cd xqj
[bdm0509:/usr/local/java/xqj] ls
3rdPartySoftware.txt    examples                lib
Fixes.txt               help                    planExplain
Readme.txt              javadoc                 src

XQJ JAR를 클래스패스에 추가하기

이제, 클래스패스에 lib 디렉터리와 ddxq.jar에 있는 XQJ 클래스들과 XQuery JAR를 추가해야 한다. 이것은 다운로드했던 원래의 JAR 파일이아니라, 앞에서 압축을 푼 JAR 파일에 의해 설치된 것이다(DataDirect는 ZIP이나 tar.gz 파일을 다운로드하도록 해서 혼란을 줄이고 있다). 클래스패스는 수동으로도 설정할 수도 있고, 셸 스크립트나 .profile 파일을 사용할 수도 있고, 또는 IDE에서 JAR 파일을 클래스패스에 추가할 수도 있다. 어찌 됐건 ddxq.jar가 클래스패스에 들어있으면 된다.

데이터베이스 연결 설정

DataDirect에서 다운로드한 제품은 관계형 데이터베이스에 대해 XQueiry를 실행할 수도 있고, 데이터베이스에 연결할 수도 있다. 다운로드 양식을 작성할 때 어떤 데이터베이스를 사용할 것인지를 꼭 선택해야 한다. 그렇게 해야 데이터베이스 설정이 가능하도록 조정된 파일을 다운로드할 수 있다. 이 부분은 이 글의 범위에서 약간 벗어나지만, DataDirect 라이브러리를 데이터베이스 연결에 사용하고, 디스크에 있는 XML 문서를 XQuery로 만드는 데 관심이 있다면, 참고자료에 있는 관련 링크를 확인해 보면 된다. 설치 디렉터리 아래의 lib 디렉터리에는 다른 JAR 파일이 많이 있지만, 단순한 파일 쿼리에는 필요가 없다. 나중에라도 DataDirect를 데이터베이스 연결에 사용하면, 그 JAR 파일들을 살펴보면 된다.

자바에서 XQuery 실행하기

지금까지 XPath와 XQuery에 대해 알아보고, 클래스패스에 XQJ를 추가했으니, 자바 코드를 작성하여 쿼리를 실행할 준비가 끝났다. 다음은 모든 프로그램 작성에서 따라야 하는 두 가지 기본 과정이다.

  1. XQuery 데이터 소스를 만들고 접근하기
  2. XQuery 실행하기

둘 다 단순한 과정이고, 다른 XQuery 구현체로 변경하지 않는다면, 첫 번째 과정은 모든 프로그램에서 똑같다. 사실, 데이터 소스 설정과 연결을 다루는 코드는 유틸리티 클래스로 만들면 된다(연습 삼아 해보자).

XQuery 데이터 소스 사용하기

데이터 소스와 데이터베이스

데이터 소스는 DataDirect처럼 데이터베이스 연결성과 함께 정적인 XML 문서에 대한 쿼리와 함께 만드는 벤더에게는 핵심이다. 연결 객체가 일단 만들어지고 나면, 그 연결에 대해 쿼리를 실행할 수 있고, 벤더는 정적인 XML 문서나 다른 여러 가지 관계형 데이터베이스에 대해 그 쿼리를 실행한다. 데이터베이스에 대해 XQuery를 실행하려면, DataDirect의 문서나 사용 중인 데이터베이스 벤더의 문서를 확인할 필요가 있다.

JDBC를 많이 사용해봤거나 n-티어 데이터베이스 애플리케이션을 작성해 봤다면 데이터 소스에 대한 개념은 친숙할 것이다. 이 글에서 말하는 데이터 소스는 어떻게 연결이 생성되고, 그리고 연결이 이디에 연결되는지에 대한 상세 정보를 추상화한 연결 객체다. 따라서 데이터 소스가 MySQL 데이터베이스에 네트워크로 연결된 것이나 정적 XML 문서에 파일 기반으로 연결되어 있는 것을 나타낼지도 모른다. 일단 데이터 소스를 가졌으면 연결 시맨틱에 상관 없이 그것을 다룰 수 있다.

로컬 디스크에 있는 XML 문서(이 글에서 다루는)에 쿼리를 수행한다면, 연결 설정은 간단하다. Listing 3은 새로운 데이터 소스를 만들고, 쿼리를 할 수 있는 기본적인 자바 프로그램이다.


Listing 3. 쿼리를 위한 데이터 소스 설정하기
                
package ibm.dw.xqj;

import com.ddtek.xquery3.XQConnection;
import com.ddtek.xquery3.XQException;
import com.ddtek.xquery3.xqj.DDXQDataSource;

public class XQueryTester {

  // Filename for XML document to query
  private String filename;

  // Data Source for querying
  private DDXQDataSource dataSource;

  // Connection for querying
  private XQConnection conn;

  public XQueryTester(String filename) {
    this.filename = filename;
  }

  public void init() throws XQException {
    dataSource = new DDXQDataSource();
    conn = dataSource.getConnection();
  }

  public static void main(String[] args) {
    if (args.length != 1) {
      System.err.println("Usage: java ibm.dw.xqj.XQueryTester [XML filename]");
      System.exit(-1);
    }

    try {
      String xmlFilename = args[0];
      XQueryTester tester = new XQueryTester(xmlFilename);
      tester.init();
    } catch (Exception e) {
      e.printStackTrace(System.err);
      System.err.println(e.getMessage());
    }
  }
}

위 프로그램은 실제보다 길고 복잡해 보인다. 테스트 프로그램은 생성자와 init() 메서드 등으로 잘 모듈화되어 있으므로 수정 없이 쉽게 재사용할 수 있다.

이 프로그램은 파일 이름을 받아(명령행으로 받아서 클래스 생성자에 전달되는), 아래 코드를 실행한다.

dataSource = new DDXQDataSource();
conn = dataSource.getConnection();

먼저, 새로운 데이터 소스가 만들어진다. 이 객체의 타입은 com.ddtek.xquery3.xqj.DDXQDataSource다. 데이터베이스에 연결하지 않기 때문에 인자가 없는 생성자를 사용하고, 다른 환경 설정도 필요 없다. 이렇게 만들어진 데이터 소스는 새로운com.ddtek.xquery3.XQConnection 객체를 얻는 데 사용된다. 이 객체는 새로운 XQuery 표현식을 생성하고 실행하는 중요한 역할을 한다. 이제 쿼리를 실행하는 프로그램을 만들어보자.

실제 XML 문서 쿼리하기

실제로 쿼리를 수행하려면 XML 파일이 필요하다. 다운로드에서 zip으로 압축해 놓은 예제 CD 카탈로그 파일을 찾을 수 있을 것이다. 이 XML 문서는 W3C에서 예제로 제공하는 것이다. 200줄이 넘는 문서를 여기서 다 보여줄 필요는 없겠지만, 쿼리를 수행하기에 좋은 문서다.

Listing 4는 문서의 구조를 알 수 있는 일부분이다.


Listing 4. cd_catalog.xml의 일부분
                
<?xml version="1.0" encoding="ISO-8859-1"?>
<CATALOG>
        <CD>
                <TITLE>Empire Burlesque</TITLE>
                <ARTIST>Bob Dylan</ARTIST>
                <COUNTRY>USA</COUNTRY>
                <COMPANY>Columbia</COMPANY>
                <PRICE>10.90</PRICE>
                <YEAR>1985</YEAR>
        </CD>
        <CD>
                <TITLE>Hide your heart</TITLE>
                <ARTIST>Bonnie Tyler</ARTIST>
                <COUNTRY>UK</COUNTRY>
                <COMPANY>CBS Records</COMPANY>
                <PRICE>9.90</PRICE>
                <YEAR>1988</YEAR>
        </CD>
  <!-- more CD listings ... -->
</CATALOG>

XQuery 만들기

다음으로 쿼리를 만들어야 한다. 단순한 자바 String을 사용하면 된다. 새로운 변수를 만들고, 아래 예제와 같이 XQueryTester 클래스의main() 메서드에 쿼리할 문자열을 만들자.

    try {
      String xmlFilename = args[0];
      XQueryTester tester = new XQueryTester(xmlFilename);
      tester.init();

      final String sep = System.getProperty("line.separator");
      String queryString =
        "      for $cd in doc($docName)/CATALOG/CD " +
        "    where $cd/YEAR > 1980 " +
        "      and $cd/COUNTRY = 'USA' " +
        " order by $cd/YEAR " +
        "   return " +
        "<cd><title>{$cd/TITLE/text()}</title>" +
        " <year>{$cd/YEAR/text()}</year></cd>";
      System.out.println(tester.query(queryString));
    } catch (Exception e) {
      e.printStackTrace(System.err);
      System.err.println(e.getMessage());
    }

쿼리는 1981년부터 US label에서 녹음된 모든 CD를 선택해 발매일 순서로 정렬한다. 그리고 반환하는 각 CD의 제목과 발매일을 포함한 XML을 반환한다. 따로 설명할 만한 것이 별로 없다.

  • 변수 docName은 검색 대상 문서를 나타낸다. 명령행으로 지정한 문서를 사용한다.
  • 결과 집합에서 각 노드에 해당하는 하나의 값을 반환하는 대신 XML 문자열을 반환한다. 이 XML은 좀 더 큰 XML 문서 같은 다른 곳에 전달되어 온라인에서 표시하거나 XSL 처리기에 전달된다.
  • 원본 문서에서 사용했던 XML 요소 이름들인 CD, TITLE, YEAR 등은 결과 집합에서는 사용되지 않으며, cd, title, year 같은 새로운 요소 이름을 사용한다.

별로 어려운 부분도 없고, 단지 XQuery를 사용하여 XML로부터 데이터를 선택해 반환하는 데이터를 반환하는 유연한 방법을 보여주는 예다.

특이한 부분은, 결과 집합으로 XML 문자열을 반환할 때, 변수를 중괄호({ })로 감싸야 한다는 점이다.

return <cd><title>{$cd/TITLE/text()}</title>" +
        " <year>{$cd/YEAR/text()}</year></cd>"

이 중괄호는 XQuery 처리기에게 문자열 그대로 사용하지 말고, 괄호 속에 있는 값을 변수 이름으로 인식해서, 해당 변수의 값으로 대치하도록 한다.

외부 변수 선언

XQuery는 문서를 나타내는 데 $docName 같은 변수를 사용한다. 그러나 명시적으로 변수를 선언해야 할 필요가 있고, 외부, 여기서는 자바 프로그램에서 그 변수를 정의했음을 알려야 한다. XQuery에서는 declare 구문을 사용하는데, 그 형식은 다음과 같다.

declare variable [variable-name] as [variable-type] external;

이 예에서는, [variable-name] $docName이며, [variable-type]은 XML 스키마의 데이터형이다. 대부분 문자열은 xs:string을, 정수는xs:int를 사용하면 된다. 다른 여러 가지 데이터형이 있지만, 별로 쓸 일이 없다.

외부 변수 선언을 포함하도록 XQueryTester 클래스의 쿼리를 수정하자.

    try {
      String xmlFilename = args[0];
      XQueryTester tester = new XQueryTester(xmlFilename);
      tester.init();

      final String sep = System.getProperty("line.separator");
      String queryString =
        "declare variable $docName as xs:string external;" + sep +
        "      for $cd in doc($docName)/CATALOG/CD " +
        "    where $cd/YEAR > 1980 " +
        "      and $cd/COUNTRY = 'USA' " +
        " order by $cd/YEAR " +
        "   return " +
        "<cd><title>{$cd/TITLE/text()}</title>" +
        " <year>{$cd/YEAR/text()}</year></cd>";
      System.out.println(tester.query(queryString));
    } catch (Exception e) {
      e.printStackTrace(System.err);
      System.err.println(e.getMessage());
    }

이제 남은 것은 이 쿼리를 실행할 함수를 만드는 것이다.

XQuery 실행

쿼리를 수행하기 위해서 다음 세 단계를 수행해야 한다.

  1. XQConnection에서 XQExpression을 생성한다.
  2. XQExpression 객체의 bindXXX() 메서드를 사용해 쿼리와 변수를 결합한다.
  3. 쿼리를 실행하고 결과를 XQSequence 객체에 저장한다.

까다로운 부분은 변수를 결합하는 단계뿐이다. 이 예에서, 변수 docName은 프로그램에 전달되는 파일 이름과 결합해야 한다. 문자열 변수를 결합하기 위해 bindString 메서드를 사용하는데, 이 메서드는 세 개의 인자가 필요하다.

  1. XQuery에서 사용할 변수 이름을 인자로 하는 javax.xml.namespace.QName 인스턴스(이 클래스는 JAXP 패키지에 있다)
  2. 변수에 결합될 값
  3. 변수에 결합될 자료형(atomic type)

위의 내용을 정리하면, 다음과 같은 메서드가 된다.

public String query(String queryString) throws XQException {
  XQExpression expression = conn.createExpression();
  expression.bindString(new QName("docName"), filename,
    conn.createAtomicType(XQItemType.XQBASETYPE_STRING));
  XQSequence results = expression.executeQuery(queryString);
  return results.getSequenceAsString(new Properties());
}

첫 번째 줄의 XQExpression expression = conn.createExpression();은 새로운 표현식 객체를 만든다.expression.bindString(new QName("docName"), filename, conn.createAtomicType(XQItemType.XQBASETYPE_STRING));은 검색 대상 문서의 파일 이름과 docName 변수를 결합한다.QName 객체에 대해서는 너무 걱정할 필요가 없다. 그냥 XQuery에 있는 변수 이름($ 문자를 제외하고)을 전달하면 된다. 두 번째 인자는 파일 이름이고, 세 번째 인자는 변수의 자료형을 나타내는 상수 값이다. 이 예에서는 쿼리의 변수가 xs:string으로 선언되어 있으므로XQItemType.XQBASETYPE_STRING을 사용했다.

직관적으로 이해가 되지 않는다면, API 문서에서 더 자세한 설명을 볼 수 있다(링크는 참고자료를 보라). bindInt(), bindFloat() 등의 다른bindXX() 메서드들도 같은 방식으로 동작한다.

마지막으로 쿼리를 실행하고 결과 집합을 넘겨 받는다. 이 결과 집합은 순회(iterate)할 수 있다. query() 메서드를 사용하면 문자열로 변환할 수도 있는데, 이렇게 하면 호출하는 프로그램이 DataDirect XQuery API에 대해 알 필요가 없다.

Listing 5는 완성된 XQueryTester의 코드다.


Listing 5. 완성된 XQueryTester 클래스
                
package ibm.dw.xqj;

import javax.xml.namespace.QName;
import java.util.Properties;

import com.ddtek.xquery3.XQConnection;
import com.ddtek.xquery3.XQException;
import com.ddtek.xquery3.XQExpression;
import com.ddtek.xquery3.XQItemType;
import com.ddtek.xquery3.XQSequence;
import com.ddtek.xquery3.xqj.DDXQDataSource;

public class XQueryTester {

  // Filename for XML document to query
  private String filename;

  // Data Source for querying
  private DDXQDataSource dataSource;

  // Connection for querying
  private XQConnection conn;

  public XQueryTester(String filename) {
    this.filename = filename;
  }

  public void init() throws XQException {
    dataSource = new DDXQDataSource();
    conn = dataSource.getConnection();
  }

  public String query(String queryString) throws XQException {
    XQExpression expression = conn.createExpression();
    expression.bindString(new QName("docName"), filename,
      conn.createAtomicType(XQItemType.XQBASETYPE_STRING));
    XQSequence results = expression.executeQuery(queryString);
    return results.getSequenceAsString(new Properties());
  }

  public static void main(String[] args) {
    if (args.length != 1) {
      System.err.println("Usage: java ibm.dw.xqj.XQueryTester [XML filename]");
      System.exit(-1);
    }

    try {
      String xmlFilename = args[0];
      XQueryTester tester = new XQueryTester(xmlFilename);
      tester.init();

      final String sep = System.getProperty("line.separator");
      String queryString =
        "declare variable $docName as xs:string external;" + sep +
        "      for $cd in doc($docName)/CATALOG/CD " +
        "    where $cd/YEAR > 1980 " +
        "      and $cd/COUNTRY = 'USA' " +
        " order by $cd/YEAR " +
        "   return " +
        "<cd><title>{$cd/TITLE/text()}</title>" +
        " <year>{$cd/YEAR/text()}</year></cd>";
      System.out.println(tester.query(queryString));
    } catch (Exception e) {
      e.printStackTrace(System.err);
      System.err.println(e.getMessage());
    }
  }
}

XQueryTester 실행하기

이 프로그램을 컴파일하고 실행하자.

[bdm0509:~/Documents/developerworks/java_xquery] 
   java ibm.dw.xqj.XQueryTester cd_catalog.xml 
<cd><title>Greatest Hits</title><year>1982</year></cd><cd><title>
Empire Burlesque</title><year>1985</year></cd><cd><title>When a man loves a woman
</title><year>1987</year></cd><cd><title>The dock of the bay</title><year>
1987</year></cd><cd><title>Unchain my heart</title><year>1987</year></cd>
<cd><title>Big Willie style</title><year>1997</year></cd><cd><title>
1999 Grammy Nominees</title><year>1999</year></cd>

주의: 온라인에서 보일 것으로 고려하여 줄바꿈을 삽입했지만, 실제 결과는 줄바꿈 없이 한 줄에 모두 나타날 것이다.

실험!

위 프로그램은 쿼리를 처리하여 지정한 XML 문서에 적용하도록 되어 있다. 이제 쿼리 문자열을 변형해 가면서 실행해 볼 수 있고, 그렇게 하면서 XQuery와 자바로 쿼리를 수행하는 과정에 대해 좀 더 이해할 수 있을 것이다. 모든 CD를 선택해보자. 또, 반환되는 결과 형식을 정형화된 텍스트로 바꿔보자. 최근 것부터 가장 오래된 것 순으로 모든 CD 목록을 볼 수도 있고, 가격이 10달러 이하인 모든 CD를 찾아볼 수도 있다. 한번 이렇게 프로그램을 작성해 두면, 쿼리를 조정하고, 시험하고, 프로그램에서 사용할 XML 문서를 변형하는 일을 더 쉽게 할 수 있다.

결론

XQuery의 기초를 얘기하지 않고 XQJ에 대해 설명하기란 쉬운 일이 아니다. 또한, XPath를 깊이 있게 설명하지 않고는 XQuery에 대해 설명할 수도 없다. 이 모든 것을 하나로 연결하는 것은 단지  가지를 — 쿼리를 수행하는 자바 프로그램, 또는 쿼리를 시작할 문서 내의 위치 — 더 쉽게 하기 위해서다. 자바에서 XQuery 사용을 고려할 때, 가장 좋은 방법은 개별적인 단순한 부분을 모아 멋지고 강력한 프로그램을 만드는 것이다.

여러 다른 기술에 친숙해지려면 두 개의 컴포넌트는 변형하지 말고, 세 번째 것을 변형하여 실행하면 된다. 즉, 자바 프로그램은 수정하지 말고, 쿼리와 검색 대상 문서만 바꿔가면서 실행해보자. 그리고 기본적인 쿼리도 수행해보고, 선언된 변수가 더 많은 복잡한 쿼리도 시험해보자. 아마 return 문과 검색의 시작 위치에 XPath를 적용하고 싶어질 것이다. 각 컴포넌트에 능숙해질수록, 쿼리들은 더 복잡해지고, 자바 코드도 더욱 견고해진다.


다운로드 하십시오

설명이름크기다운로드 방식
CD 카탈로그와 자바 소스 코드x-xjavaxquery.zip3KBHTTP

다운로드 방식에 대한 정보


참고자료

교육

제품 및 기술 얻기

  • DataDirect의 XQuery for Java 구현: XQuery와 자바를 시작하기 위한 라이브러리를 다운로드할 수 있다.

  • DB2 Express-C 9.5: XML 데이터베이스를 다운로드할 수 있다. 

  • Java and XML, Third Edition(Brett McLaughlin과 Justin Edelson, O'Reilly Media, Inc.): XML과 XSL의 넓은 범위에 걸쳐 XML의 시작부터 끝까지 설명되어 있고, XML과 관련된 많은 스펙도 포함하고 있다.

  • IBM 시험판 소프트웨어: developerWorks에서 시험판 소프트웨어를 직접 다운로드해 프로젝트를 만들 수 있다. 

토론

필자소개

Photo of Brett McLaughlin

Brett McLaughlin은 베스트셀러 논픽션 작가다. 컴퓨터 프로그래밍, 홈씨어터, 분석과 디자인에 관한 그의 책은 10만 부 이상 판매되었다. 그는 10년째 기술 서적을 집필하고, 편집하고, 기획하고 있으며, 워드프로세서 앞에 있으면, 기타 뒤에 있거나 집 주위에서 두 아들을 쫓아 다니거나 부인과 함께 못말리는 패밀리(Arrested Development) 재방송을 보면서 웃는 것 같은 편안함을 느낀다. 그의 최근 책, Head First Object Oriented Analysis and Design은 2007년 졸트 상 기술 서적 부문을 수상했다. 그의 책 Java and XML은 자바 언어에서 XML 기술 사용하기에 대한 고전이다.



출처 - http://www.ibm.com/developerworks/kr/library/x-xjavaxquery/index.html





'Development > XML' 카테고리의 다른 글

xml - XSLT  (0) 2013.08.10
jaxen - XPath and Default Namespace handling  (0) 2013.02.20
XSL(Extensible Stylesheet Language)  (0) 2013.02.19
XPath  (0) 2013.02.19
XML - XML 스키마(XSD) 및 xsi 접두어 의미  (0) 2012.10.02
Posted by linuxism
,