Java

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

griffy 2009. 5. 15. 09:11

자바 환경에서 XQuery 사용하기 (하)
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의 일부분





Bob Dylan
USA
Columbia
10.90
1985



Bonnie Tyler
UK
CBS Records
9.90
1988




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/YEAR/text()}
";
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/YEAR/text()}
"

이 중괄호는 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/YEAR/text()}
";
System.out.println(tester.query(queryString));
} catch (Exception e) {
e.printStackTrace(System.err);
System.err.println(e.getMessage());
}

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

XQuery 실행

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

XQConnection에서 XQExpression을 생성한다.
XQExpression 객체의 bindXXX() 메서드를 사용해 쿼리와 변수를 결합한다.
쿼리를 실행하고 결과를 XQSequence 객체에 저장한다.
까다로운 부분은 변수를 결합하는 단계뿐이다. 이 예에서, 변수 docName은 프로그램에 전달되는 파일 이름과 결합해야 한다. 문자열 변수를 결합하기 위해 bindString 메서드를 사용하는데, 이 메서드는 세 개의 인자가 필요하다.

XQuery에서 사용할 변수 이름을 인자로 하는 javax.xml.namespace.QName 인스턴스(이 클래스는 JAXP 패키지에 있다)
변수에 결합될 값
변수에 결합될 자료형(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/YEAR/text()}
";
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
198219851987
1987
1987
19971999

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

실험!

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

결론

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

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