Java

자바 환경에서 XQuery 사용하기 (상)

griffy 2009. 5. 15. 09:10

자바 환경에서 XQuery 사용하기 (상)

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

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는 세 개의 핵심 요소로 이루어져 있다.

XPath 스펙: XML 문서에서 0개, 한 개, 또는 여러 개의 노드를 선택하는 방법
특정 XML 문서를 선택하기 위한 부가적인 구문, 그리고 XPath의 결과 노드들에 검색 조건 추가
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은 다음과 같이 세 개의 경로를 포함한다.

/cds는 "cds"라는 이름의 루트 요소를 반환한다(하나의 요소 노드를 가진 노드 집합).
cd(앞의 노드 집합에 상대적으로)는 앞의 노드 집합 안에 있는 모든 cd 요소를 반환한다.
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 목록 문서의 구조





Track title






이제, 열 개 또는 그 이상의 트랙을 가진 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을 사용한다.
각 절에 대해 조금 더 자세히 알아보자.