JDO의 Datastore 쿼리

이 문서에서는 App Engine Datastore 쿼리에 대한 자바 데이터 객체(JDO) 사용을 중점적으로 설명합니다. 쿼리에 대한 전반적인 내용은 기본 Datastore 쿼리 페이지를 참조하세요.

쿼리는 지정된 일련의 조건을 충족하는 항목을 Datastore에서 검색합니다. 쿼리는 지정된 종류의 항목을 대상으로 작동하고, 항목의 속성 값, 키, 상위 항목에 필터를 지정하고, 항목 0개 이상을 결과로 반환할 수 있습니다. 또한 정렬 순서를 지정하여 결과를 속성 값에 따라 순차적으로 배열할 수 있습니다. 결과에는 필터와 정렬 순서에 명시된 모든 속성에 대한 값이 하나(null 가능) 이상 있으며 해당 속성 값이 지정된 모든 필터 기준을 충족하는 모든 항목이 포함됩니다. 쿼리는 전체 항목 또는 예상 항목을 반환하거나 항목 만 반환할 수 있습니다.

일반적인 쿼리에는 다음이 포함됩니다.

  • 쿼리가 적용되는 항목 종류
  • 항목의 속성 값, 키, 상위 요소를 기반으로 하는 0개 이상의 필터
  • 결과를 순차적으로 배열하는 0개 이상의 정렬 순서
쿼리가 실행되면 해당 종류의 모든 항목 중 지정된 필터를 모두 충족하는 항목이 지정된 정렬 순서에 따라 검색됩니다. 쿼리는 읽기 전용으로 실행됩니다.

참고: 메모리를 절약하고 성능을 개선하려면 가능할 때마다 쿼리가 반환하는 결과 수를 제한해야 합니다.

참고: 색인 기준 쿼리 메커니즘은 다양한 쿼리를 지원하고 대부분의 애플리케이션에 적합하지만 다른 데이터베이스 기술에서 일반적으로 사용되는 몇몇 종류의 쿼리는 지원하지 않습니다. 특히 조인 및 집계 쿼리는 Datastore 쿼리 엔진에서 지원되지 않습니다. Datastore 쿼리의 제한사항은 Datastore 쿼리 페이지를 참조하세요.

JDOQL를 사용하는 쿼리

JDO에는 일련의 기준을 충족하는 객체를 검색하기 위한 쿼리 언어가 있습니다. JDOQL이라고 하는 이 언어는 JDO 데이터 클래스와 필드를 직접 참조하며 쿼리 매개변수와 결과의 유형 확인을 포함합니다. JDOQL은 SQL과 비슷하지만 App Engine Datastore와 같은 객체 지향 데이터베이스에 더 적합합니다. App Engine의 JDO API 구현은 SQL 쿼리를 직접 지원하지 않습니다.

JDO Query 인터페이스는 여러 호출 스타일을 지원합니다. 예를 들어 JDOQL 문자열 구문을 사용하여 전체 쿼리를 문자열로 지정하거나 Query 객체에서 메서드를 호출하여 쿼리 일부 또는 전체를 지정할 수 있습니다. 다음 예시에서는 필터에 사용된 값에 매개변수 대체를 사용하여 하나의 필터와 하나의 정렬 순서로 호출하는 메서드 스타일을 보여줍니다. Query 객체의 execute() 메서드에 전달되는 인수 값은 지정된 순서에 따라 대체되어 쿼리에 사용됩니다.

import java.util.List;
import javax.jdo.Query;

// ...

Query q = pm.newQuery(Person.class);
q.setFilter("lastName == lastNameParam");
q.setOrdering("height desc");
q.declareParameters("String lastNameParam");

try {
  List<Person> results = (List<Person>) q.execute("Smith");
  if (!results.isEmpty()) {
    for (Person p : results) {
      // Process result p
    }
  } else {
    // Handle "no results" case
  }
} finally {
  q.closeAll();
}

다음은 문자열 구문을 사용한 동일한 쿼리입니다.

Query q = pm.newQuery("select from Person " +
                      "where lastName == lastNameParam " +
                      "parameters String lastNameParam " +
                      "order by height desc");

List<Person> results = (List<Person>) q.execute("Smith");

쿼리를 정의할 때 이런 스타일을 혼합하여 사용할 수 있습니다. 예를 들면 다음과 같습니다.

Query q = pm.newQuery(Person.class,
                      "lastName == lastNameParam order by height desc");
q.declareParameters("String lastNameParam");

List<Person> results = (List<Person>) q.execute("Smith");

매개변수에 다른 값을 대체하고 execute() 메서드를 여러 번 호출하여 단일 Query 인스턴스를 재사용할 수 있습니다. 각 호출은 쿼리를 수행하며 결과를 컬렉션으로 반환합니다.

JDOQL 문자열 구문은 문자열 및 숫자 값의 리터럴 사양을 지원합니다. 다른 모든 값 유형은 매개변수 대체를 사용해야 합니다. 쿼리 문자열 내의 리터럴을 작은따옴표(') 또는 큰따옴표(")로 묶을 수 있습니다. 문자열 리터럴 사용의 예시는 다음과 같습니다.

Query q = pm.newQuery(Person.class,
                      "lastName == 'Smith' order by height desc");

필터

속성 필터는 다음을 지정합니다.

  • 속성 이름
  • 비교 연산자
  • 속성 값
예를 들면 다음과 같습니다.

Filter propertyFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);
Query q = pm.newQuery(Person.class);
q.setFilter("height <= maxHeight");

속성 값은 애플리케이션에서 제공해야 합니다. 속성 값은 다른 속성을 참조하거나 그와 관련하여 계산될 수 없습니다. 비교 연산자로 설명된 방식에 따라 필터에 지정된 값과 해당 값이 비교되는 지정된 이름의 속성이 있으면 항목이 필터를 만족합니다.

비교 연산자는 다음 중 하나일 수 있습니다.

연산자 의미
== 같음
< 미만
<= 이하
> 초과
>= 이상
!= 같지 않음

기본 쿼리 페이지의 설명대로 단일 쿼리는 속성 두 개 이상에 불일치 필터(<, <=, >, >=, !=)를 사용할 수 없습니다. 단, 값 범위의 쿼리와 같이 동일한 속성에는 불일치 필터를 여러 개 사용할 수 있습니다 다음 구문에서는 SQL의 IN 필터에 해당하는 contains() 필터를 사용할 수 있습니다.

// Query for all persons with lastName equal to Smith or Jones
Query q = pm.newQuery(Person.class, ":p.contains(lastName)");
q.execute(Arrays.asList("Smith", "Jones"));

같지 않음(!=) 연산자는 실제로는 쿼리 두 개를 수행합니다. 즉, 다른 모든 필터는 변경되지 않고 같지 않음 필터가 보다 작음(<) 필터로 대체되는 쿼리 하나와 보다 큼(>) 필터로 대체되는 쿼리 하나가 수행됩니다. 그런 다음 결과를 순서대로 병합합니다. 하나의 쿼리에 불일치 필터가 두 개 이상 있을 수 없으며, 이 필터가 있는 쿼리에는 다른 불일치 필터가 존재할 수 없습니다.

contains() 연산자도 여러 쿼리를 수행합니다. 지정한 목록의 각 항목마다 하나의 쿼리를 수행하며, 다른 모든 필터는 변경되지 않고 contains() 필터만 같음(==) 필터로 대체됩니다. 결과는 목록에 있는 항목의 순서대로 병합됩니다. 한 쿼리에 contains() 필터가 한 개 넘게 있으면 contains() 목록의 가능한 값 조합마다 하나씩 여러 개의 쿼리로 수행됩니다.

같지 않음(!=) 또는 contains() 연산자가 포함된 단일 쿼리에서는 하위 쿼리가 최대 30개로 제한됩니다.

JDO/JPA 프레임워크에서 !=contains() 쿼리가 여러 쿼리로 변환되는 방법에 대한 자세한 내용은 Queries with != and IN filters를 참조하세요.

JDOQL 문자열 구문에서는 여러 필터를 &&(논리적 'and') 및 ||(논리적 'or') 연산자로 구분할 수 있습니다.

q.setFilter("lastName == 'Smith' && height < maxHeight");

부정(논리적 'not')은 사용할 수 없습니다. || 연산자로 구분되는 필터의 속성 이름이 모두 동일한 경우 이러한 필터를 contains() 필터 하나에 결합할 수 있는 경우에만 이 연산자를 사용할 수 있습니다.

// Legal: all filters separated by || are on the same property
Query q = pm.newQuery(Person.class,
                      "(lastName == 'Smith' || lastName == 'Jones')" +
                      " && firstName == 'Harold'");

// Not legal: filters separated by || are on different properties
Query q = pm.newQuery(Person.class,
                      "lastName == 'Smith' || firstName == 'Harold'");

정렬 순서

쿼리 정렬 순서는 다음을 지정합니다.

  • 속성 이름
  • 정렬 방향(오름차순 또는 내림차순)

예를 들면 다음과 같습니다.

// Order alphabetically by last name:
Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING);

// Order by height, tallest to shortest:
Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING);

예를 들면 다음과 같습니다.

// Order alphabetically by last name:
Query q = pm.newQuery(Person.class);
q.setOrdering("lastName asc");

// Order by height, tallest to shortest:
Query q = pm.newQuery(Person.class);
q.setOrdering("height desc");

쿼리에 정렬 순서가 여러 개 있으면 지정한 순서대로 적용됩니다. 다음 예시에서는 먼저 성을 기준으로 오름차순으로 정렬한 다음 키를 기준으로 내림차순으로 정렬합니다.

Query q =
    new Query("Person")
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("height", SortDirection.DESCENDING);
Query q = pm.newQuery(Person.class);
q.setOrdering("lastName asc, height desc");

정렬 순서가 지정되지 않으면 결과는 Datastore에서 검색된 순서대로 반환됩니다.

참고: Datastore에서 쿼리를 실행하는 방식 때문에, 쿼리에서 하나의 속성에 불일치 필터를 지정하고 다른 속성에 정렬 순서를 지정하면 불일치 필터에 사용되는 속성이 다른 속성보다 먼저 정렬되어야 합니다.

범위

쿼리는 애플리케이션에 반환되는 결과의 범위를 지정할 수 있습니다. 범위는 전체 결과 집합 중 어느 결과가 맨 처음과 마지막에 반환되어야 하는지를 나타냅니다. 결과는 숫자 색인으로 식별되며, 이 때 0은 집합의 첫 번째 결과를 나타냅니다. 예를 들어 범위가 5,, 10이면 6번째 결과부터 10번째 결과가 반환됩니다.

q.setRange(5, 10);

참고: 범위를 사용하면 Datastore에서 시작 오프셋 이전의 모든 결과를 검색한 다음 삭제해야 하므로 성능에 영향을 줄 수 있습니다. 예를 들어 범위가 5,, 10인 쿼리는 Datastore에서 결과 10개를 검색한 후 처음 5개를 삭제하고 나머지 5개를 애플리케이션에 반환합니다.

키 기반 쿼리

항목 키는 쿼리 필터 또는 정렬 순서의 대상이 될 수 있습니다. Datastore는 이러한 쿼리에 항목의 상위 경로, 종류, 애플리케이션에서 할당한 키 이름 문자열 또는 시스템에서 할당한 숫자 ID를 포함한 전체 키 값을 고려합니다. 키는 시스템의 모든 항목에서 고유하므로 키 쿼리를 사용하면 Datastore 콘텐츠의 일괄 덤프와 같이 지정된 종류의 항목을 일괄적으로 쉽게 검색할 수 있습니다. 이는 JDOQL 범위와는 달리 항목의 수에 상관없이 효율적으로 작동합니다.

비균등 비교 시 다음 순서의 기준에 따라 키가 정렬됩니다.

  1. 상위 경로
  2. 항목 종류
  3. 식별자(키 이름 또는 숫자 ID)

마찬가지로 상위 경로의 요소도 종류(문자열)에 이어 키 이름 또는 숫자 ID로 비교됩니다. 종류와 키 이름은 문자열이며 바이트 값으로 정렬되고, 숫자 ID는 정수이며 숫자로 정렬됩니다. 상위 요소 및 종류가 동일한 항목에 키 이름 문자열과 숫자 ID를 함께 사용하면 숫자 ID가 있는 항목이 키 이름이 있는 항목보다 앞에 옵니다.

JDO에서는 객체의 기본 키 필드를 사용하여 쿼리의 항목 키를 참조합니다. 쿼리 필터로 키를 사용하려면 매개변수 유형 Key declareParameters() 메서드에 지정합니다. 다음은 PersonFood 간의 미소유 일대일 관계를 가정하여 지정된 가장 좋아하는 음식에 해당하는 모든 Person 항목을 검색합니다.

Food chocolate = /*...*/;

Query q = pm.newQuery(Person.class);
q.setFilter("favoriteFood == favoriteFoodParam");
q.declareParameters(Key.class.getName() + " favoriteFoodParam");

List<Person> chocolateLovers = (List<Person>) q.execute(chocolate.getKey());

키 전용 쿼리는 항목 자체가 아니라 결과 항목의 키만 반환하므로, 전체 항목을 검색할 때보다 지연 시간과 비용이 낮습니다.

Query q = new Query("Person").setKeysOnly();
Query q = pm.newQuery("select id from " + Person.class.getName());
List<String> ids = (List<String>) q.execute();

실제로 필요한 것보다 많은 항목을 가져올 수 있는 일반 쿼리를 실행하는 대신 키 전용 쿼리를 먼저 실행한 후 결과에서 항목의 하위 집합을 가져오는 것이 더 경제적인 경우가 종종 있습니다.

익스텐트

JDO 익스텐트는 특정 클래스의 Datastore에 있는 모든 객체를 나타냅니다. 원하는 클래스를 Persistence Manager의 getExtent() 메서드에 전달하여 JDO 익스텐트를 만듭니다. Extent 인터페이스는 Iterable 인터페이스를 확장하여 결과에 액세스하고 필요한 경우 일괄적으로 검색합니다. 결과에 액세스했으면 익스텐트의 closeAll() 메서드를 호출합니다.

다음 예시는 Datastore의 모든 Person 객체에서 반복됩니다.

import java.util.Iterator;
import javax.jdo.Extent;

// ...

Extent<Person> extent = pm.getExtent(Person.class, false);
for (Person p : extent) {
  // ...
}
extent.closeAll();

쿼리별 항목 삭제

쿼리 필터와 일치하는 모든 항목을 삭제하기 위해 쿼리를 실행하는 경우 JDO의 '쿼리별 삭제' 기능을 사용하면 코딩 작업을 하는 수고를 덜 수 있습니다. 다음 예시에서는 지정된 키보다 큰 사람을 모두 삭제합니다.

Query q = pm.newQuery(Person.class);
q.setFilter("height > maxHeightParam");
q.declareParameters("int maxHeightParam");
q.deletePersistentAll(maxHeight);

여기서 유일한 차이점은 q.execute() 대신 q.deletePersistentAll()을 호출한다는 점입니다. 앞에서 설명한 대로 필터, 정렬 순서, 색인에 대한 모든 규칙과 제한사항은 결과 집합을 선택하든 삭제하든 관계없이 쿼리에 적용됩니다. 그러나 pm.deletePersistent()를 사용하여 Person 항목을 삭제한 경우처럼 쿼리에서 삭제한 항목에 종속된 하위 요소도 삭제됩니다. 종속된 하위 요소에 대한 자세한 내용은 JDO의 항목 관계 페이지를 참조하세요.

쿼리 커서

JDO에서는 확장 프로그램과 JDOCursorHelper 클래스를 사용하여 JDO 쿼리에 커서를 사용할 수 있습니다. 커서는 결과를 목록으로 가져오거나 반복자를 사용할 때 작동합니다. 커서를 가져오려면 결과 목록 또는 반복자를 정적 메서드 JDOCursorHelper.getCursor()에 전달합니다.

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jdo.Query;
import com.google.appengine.api.datastore.Cursor;
import org.datanucleus.store.appengine.query.JDOCursorHelper;

Query q = pm.newQuery(Person.class);
q.setRange(0, 20);

List<Person> results = (List<Person>) q.execute();
// Use the first 20 results

Cursor cursor = JDOCursorHelper.getCursor(results);
String cursorString = cursor.toWebSafeString();
// Store the cursorString

// ...

// Query q = the same query that produced the cursor
// String cursorString = the string from storage
Cursor cursor = Cursor.fromWebSafeString(cursorString);
Map<String, Object> extensionMap = new HashMap<String, Object>();
extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, cursor);
q.setExtensions(extensionMap);
q.setRange(0, 20);

List<Person> results = (List<Person>) q.execute();
// Use the next 20 results

쿼리 커서에 대한 자세한 내용은 Datastore 쿼리 페이지를 참조하세요.

데이터 저장소 읽기 정책 및 호출 기한

구성을 사용하여 PersistenceManager 인스턴스에서 수행한 모든 호출의 읽기 정책(strong consistency와 eventual consistency 차이)과 Datastore API 호출 기한을 설정할 수 있습니다. 개별 Query 객체에 이러한 옵션을 재정의할 수도 있습니다. (단, 키를 사용하여 항목을 가져올 때는 이 옵션의 구성을 재정의할 방법이 없습니다.)

Datastore 쿼리에 eventual consistency가 선택되면 쿼리가 결과를 수집하는 데 사용하는 색인에 액세스할 때도 eventual consistency가 적용됩니다. 쿼리가 쿼리 기준에 맞지 않는 항목을 반환할 때도 있지만 이는 strong consistency를 가진 읽기 정책에서도 마찬가지입니다. (쿼리가 상위 필터를 사용하는 경우에는 트랜잭션을 사용하여 결과 집합의 일관성을 보장할 수 있습니다.)

단일 쿼리의 읽기 정책을 재정의하려면 addExtension() 메서드를 호출합니다.

Query q = pm.newQuery(Person.class);
q.addExtension("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");

가능한 값은 "EVENTUAL""STRONG"이며, jdoconfig.xml 구성 파일에 달리 설정되어 있지 않은 경우 기본값은 "STRONG"입니다.

단일 쿼리의 Datastore API 호출 기한을 재정의하려면 setDatastoreReadTimeoutMillis() 메서드를 호출합니다.

q.setDatastoreReadTimeoutMillis(3000);

값은 시간 간격(밀리초)입니다.