JDO로 데이터 클래스 정의

JDO를 사용하여 일반 자바 데이터 객체('POJO(Plain Old Java Object)'라고도 함)를 데이터 저장소에 저장할 수 있습니다. PersistenceManager로 영구화되는 각 객체는 데이터 저장소에서 항목 한 개가 됩니다. 주석을 사용하여 데이터 클래스의 인스턴스를 저장하고 다시 만드는 방법을 JDO에 지시합니다.

참고: 이전 버전의 JDO는 자바 주석 대신 .jdo XML 파일을 사용합니다. JDO 2.3에서도 마찬가지입니다. 본 문서는 데이터 클래스와 함께 자바 주석을 사용하는 경우만 설명합니다.

클래스 및 필드 주석

JDO에서 저장되는 각 객체는 App Engine Datastore에서 항목 한 개가 됩니다. 이 항목 종류는 클래스의 단순 이름에서 파생됩니다(내부 클래스는 패키지 이름 없이 $ 경로를 사용함). 클래스의 각 영구 필드는 항목 속성을 나타내고 속성 이름은 필드 이름과 같습니다(대소문자가 유지됨).

JDO를 통해 Datastore에서 저장 및 검색할 수 있도록 자바 클래스를 선언하려면 클래스에 @PersistenceCapable 주석을 지정합니다. 예를 들면 다음과 같습니다.

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable
public class Employee {
    // ...
}

데이터 저장소에 저장되는 데이터 클래스 필드는 영구 필드로 선언되어야 합니다. 영구 필드로 선언하려면 @Persistent 주석을 지정합니다.

import java.util.Date;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    private Date hireDate;

비영구 필드로 선언하려면(Datastore에 저장되지 않고 객체 검색 시 복원되지 않음) @NotPersistent 주석을 지정합니다.

팁: JDO는 @Persistent 또는 @NotPersistent 주석이 모두 지정되지 않고 다른 모든 유형의 필드가 기본적으로 영구적이지 않으면 JDO는 특정 유형의 필드가 기본적으로 영구화되도록 지정합니다. 이 동작에 대한 자세한 설명은 DataNucleus 문서를 참조하세요. App Engine Datastore 핵심 값 유형 중 JDO 규격에 따라 기본적으로 영구적이 아닌 값도 있으므로 명확성을 위해 필드를 명시적으로 @Persistent 또는 @NotPersistent로 주석 처리하는 것이 좋습니다.

필드 유형은 다음 중 하나일 수 있습니다. 아래에서 자세한 내용을 설명합니다.

  • 데이터 저장소에서 지원하는 핵심 유형 중 하나
  • 컬렉션(예: java.util.List<...>) 또는 핵심 Datastore 유형의 값 배열
  • @PersistenceCapable 클래스의 인스턴스 또는 인스턴스 컬렉션
  • 직렬화 가능 클래스의 인스턴스 또는 인스턴스 컬렉션
  • 항목에 속성으로 저장되는 포함된 클래스

데이터 클래스에는 해당 데이터 저장소 항목의 기본 키를 저장하는 전용 필드가 한 개만 있어야 합니다(없어도 안 됨). 각각 다른 값 유형과 주석을 사용하는 네 가지 종류의 키 필드 중에서 한 필드를 선택할 수 있습니다. (자세한 내용은 데이터 만들기: 키를 참조하세요.) 가장 유연한 유형의 키 필드는 객체가 Datastore에 처음 저장될 때 JDO에서 다른 모든 클래스 인스턴스에 걸쳐 고유한 값으로 자동 입력되는 Key 객체입니다. Key 유형의 기본 키에는 @PrimaryKey 주석과 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) 주석이 필요합니다.

팁: 모든 영구 필드를 private 또는 protected(또는 package protected)로 만들고 접근자 메서드를 통해 공개 액세스 권한만 제공하세요. 다른 클래스에서 영구 필드에 직접 액세스하면 JDO 클래스 고급 기능을 우회할 수 있습니다. 또는 다른 @PersistenceAware 클래스를 만들 수 있습니다. 자세한 내용은 DataNucleus 문서를 참조하세요.

import com.google.appengine.api.datastore.Key;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

다음은 데이터 클래스의 예입니다.

import com.google.appengine.api.datastore.Key;

import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String firstName;

    @Persistent
    private String lastName;

    @Persistent
    private Date hireDate;

    public Employee(String firstName, String lastName, Date hireDate) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.hireDate = hireDate;
    }

    // Accessors for the fields. JDO doesn't use these, but your application does.

    public Key getKey() {
        return key;
    }

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Date getHireDate() {
        return hireDate;
    }
    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    }
}

핵심 값 유형

핵심 유형의 단일 값을 포함하는 속성을 나타내려면 자바 유형의 필드를 선언하고 @Persistent 주석을 사용합니다.

import java.util.Date;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    private Date hireDate;

직렬화 가능 객체

필드 값에는 Blob 유형의 단일 속성 값에 직렬화된 인스턴스 값을 저장하는 직렬화 가능 클래스 인스턴스를 포함할 수 있습니다. 값을 직렬화하도록 JDO에 지시하려면 필드에 @Persistent(serialized=true) 주석을 사용합니다. Blob 값은 색인을 생성하지 않으며 쿼리 필터 또는 정렬 순서에 사용될 수 없습니다.

다음은 파일 콘텐츠, 파일 이름, MIME 유형을 포함하여 파일을 나타내는 간단한 직렬화 가능 클래스의 예입니다. 이는 JDO 데이터 클래스가 아니므로 영구 주석이 없습니다.

import java.io.Serializable;

public class DownloadableFile implements Serializable {
    private byte[] content;
    private String filename;
    private String mimeType;

    // ... accessors ...
}

직렬화 가능 클래스 인스턴스를 속성에서 Blob 값으로 저장하려면 클래스 유형의 필드를 선언하고 @Persistent(serialized = "true") 주석을 사용합니다.

import javax.jdo.annotations.Persistent;
import DownloadableFile;

// ...
    @Persistent(serialized = "true")
    private DownloadableFile file;

하위 객체와 관계

@PersistenceCapable 클래스 인스턴스 필드 값은 두 객체 사이에 소유된 일대일 관계를 만듭니다. 해당 참조 컬렉션 필드는 소유된 일대다 관계를 만듭니다.

중요: 소유된 관계는 트랜잭션, 항목 그룹, 연속 삭제에 영향을 미칩니다. 자세한 내용은 트랜잭션관계를 참조하세요.

다음은 Employee 객체와 ContactInfo 객체 사이에 소유된 일대일 관계의 간단한 예입니다.

ContactInfo.java

import com.google.appengine.api.datastore.Key;
// ... imports ...

@PersistenceCapable
public class ContactInfo {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String streetAddress;

    @Persistent
    private String city;

    @Persistent
    private String stateOrProvince;

    @Persistent
    private String zipCode;

    // ... accessors ...
}

Employee.java

import ContactInfo;
// ... imports ...

@PersistenceCapable
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private ContactInfo myContactInfo;

    // ... accessors ...
}

이 예시에서 앱이 Employee 인스턴스를 만들고 myContactInfo 필드에 새 ContactInfo 인스턴스를 입력한 후 pm.makePersistent(...)가 포함된 Employee 인스턴스를 저장하면 Datastore는 항목 두 개를 만듭니다. 하나는 ContactInfo 인스턴스를 나타내는 "ContactInfo" 종류입니다. 그리고 다른 하나는 "Employee" 종류입니다. ContactInfo 항목 키에는 항목 그룹 상위 항목으로 Employee 항목 키가 있습니다.

포함된 클래스

포함된 클래스를 사용하면 새 데이터 저장소 항목 생성 및 관계 형성 없이 클래스를 사용하여 필드 값을 모델링할 수 있습니다. 객체 값의 필드는 포함하는 객체의 데이터 저장소 항목에 직접 저장됩니다.

@PersistenceCapable 데이터 클래스를 다른 데이터 클래스에 포함된 객체로 사용할 수 있습니다. 클래스의 @Persistent 필드가 객체에 포함됩니다. @EmbeddedOnly 주석을 포함하도록 클래스를 지정하면 이 클래스는 포함된 클래스로만 사용할 수 있습니다. 포함된 클래스는 별도의 항목으로 저장되지 않으므로, 기본 키 필드가 필요하지 않습니다.

다음은 포함된 클래스의 예입니다. 이 예는 클래스를 사용하는 데이터 클래스의 내부 클래스로 포함된 클래스를 만듭니다. 이 방법은 유용하지만 클래스를 포함 가능으로 만들 필요가 없습니다.

import javax.jdo.annotations.Embedded;
import javax.jdo.annotations.EmbeddedOnly;
// ... imports ...

@PersistenceCapable
public class EmployeeContacts {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    Key key;
    @PersistenceCapable
    @EmbeddedOnly
    public static class ContactInfo {
        @Persistent
        private String streetAddress;

        @Persistent
        private String city;

        @Persistent
        private String stateOrProvince;

        @Persistent
        private String zipCode;

        // ... accessors ...
    }

    @Persistent
    @Embedded
    private ContactInfo homeContactInfo;
}

포함된 클래스 필드는 항목에서 속성으로 저장되며, 각 필드 이름과 해당 속성 이름이 사용됩니다. 유형이 포함된 클래스인 객체에 필드가 한 개 이상 있으면 서로 충돌하지 않도록 한 필드의 이름을 변경해야 합니다. @Embedded 주석에 대한 인수를 사용하여 새 필드 이름을 지정합니다. 예를 들면 다음과 같습니다.

    @Persistent
    @Embedded
    private ContactInfo homeContactInfo;

    @Persistent
    @Embedded(members = {
        @Persistent(name="streetAddress", columns=@Column(name="workStreetAddress")),
        @Persistent(name="city", columns=@Column(name="workCity")),
        @Persistent(name="stateOrProvince", columns=@Column(name="workStateOrProvince")),
        @Persistent(name="zipCode", columns=@Column(name="workZipCode")),
    })
    private ContactInfo workContactInfo;

마찬가지로, 포함된 필드 이름을 변경하지 않는 한 객체의 필드에 포함된 클래스의 필드와 충돌하는 이름을 사용하지 않아야 합니다.

포함된 클래스의 영구적 속성은 다른 필드와 동일한 항목에 저장되므로, JDOQL 쿼리 필터와 정렬 순서에서 포함된 클래스의 영구적 필드를 사용할 수 있습니다. 외부 필드 이름, 점(.), 포함된 필드 이름을 사용하여 포함된 필드를 참조할 수 있습니다. 포함된 필드의 속성 이름이 @Column 주석을 사용하여 변경되어도 작동합니다.

    select from EmployeeContacts where workContactInfo.zipCode == "98105"

컬렉션

데이터 저장소 속성에는 값이 두 개 이상 있을 수 있습니다. JDO에서 속성은 컬렉션 유형의 단일 필드로 표시되며, 여기서 컬렉션은 핵심 값 유형 또는 직렬화 가능 클래스 중 하나입니다. 다음 컬렉션 유형이 지원됩니다.

  • java.util.ArrayList<...>
  • java.util.HashSet<...>
  • java.util.LinkedHashSet<...>
  • java.util.LinkedList<...>
  • java.util.List<...>
  • java.util.Map<...>
  • java.util.Set<...>
  • java.util.SortedSet<...>
  • java.util.Stack<...>
  • java.util.TreeSet<...>
  • java.util.Vector<...>

필드가 List로 선언되면 데이터 저장소에서 반환되는 객체는 ArrayList 값을 갖습니다. 필드가 Set으로 선언되면 데이터 저장소는 HashSet을 반환합니다. 필드가 SortedSet으로 선언되면 데이터 저장소는 TreeSet을 반환합니다.

예를 들어, 유형이 List<String>인 필드는 List의 각 값에 대해 하나씩, 속성 값이 0 이상인 문자열 값으로 저장됩니다.

import java.util.List;
// ... imports ...

// ...
    @Persistent
    List<String> favoriteFoods;

하위 객체(@PersistenceCapable 클래스)의 컬렉션은 일대다 관계를 가진 여러 항목을 만듭니다. 관계를 참조하세요.

값이 두 개 이상인 데이터 저장소 속성에는 특수한 쿼리 필터 및 정렬 순서 동작이 있습니다. 자세한 내용은 데이터 저장소 쿼리 페이지를 참조하세요.

객체 필드 및 항목 속성

App Engine Datastore는 제공된 속성이 없는 항목과 속성에 대한 null 값이 있는 항목을 구분합니다. JDO는 이러한 구분을 지원하지 않습니다. 즉, 객체의 모든 필드에 보통 null 값이 있습니다. null 허용 값 유형(int 또는 boolean과 같은 기본 제공 이외의 유형)을 가진 필드가 null로 설정되면 객체 저장 시 결과 항목은 null 값을 포함한 속성 집합을 갖습니다.

Datastore 항목이 객체에 로드되고 객체 필드 중 하나에 대한 속성이 없으며 필드 유형이 null 허용 단일 값 유형이면 필드는 null로 설정됩니다. 객체가 Datastore에 다시 저장되면 null 속성은 Datastore에서 null 값으로 설정됩니다. 필드가 Null 허용 값 유형이 아닌 경우, 해당 속성 없이 항목을 로드하면 예외가 발생합니다. 항목이 인스턴스를 다시 만드는 데 사용된 동일한 JDO 클래스로부터 만들어진 경우에는 이러한 일이 발생하지 않습니다. 하지만 JDO 클래스가 변경되거나 항목이 JDO 대신 낮은 수준의 API를 사용하여 만들어진 경우에는 발생할 수 있습니다.

필드 유형이 핵심 데이터 유형의 컬렉션이거나 직렬화 가능 클래스이고 항목에 속성 값이 없으면 빈 컬렉션은 속성을 단일 Null 값으로 설정하는 식으로 데이터 저장소에 표시됩니다. 필드 유형이 배열 유형이면 0 요소의 배열이 할당됩니다. 객체가 로드되고 속성 값이 없으면 필드에 해당 유형의 빈 컬렉션이 할당됩니다. 내부적으로 데이터 저장소는 빈 컬렉션과 Null 값 한 개를 포함한 컬렉션을 구분합니다.

항목에 객체와 관련된 필드가 없는 속성이 있으면 해당 속성은 객체에서 액세스할 수 없습니다. 객체가 다시 데이터 저장소에 저장되면 추가 속성은 삭제됩니다.

항목에 객체와 관련된 필드가 아닌 다른 유형 값을 가진 속성이 있으면 JDO는 값을 필드 유형으로 변환하려고 시도합니다. 값을 필드 유형으로 변환할 수 없으면 JDO는 ClassCastException을 발생시킵니다. 숫자의 경우(긴 정수 및 이중 폭 부동 소수점) 값이 변환되지 않고 전환됩니다. 숫자 속성 값이 필드 유형보다 크면 예외 발생 없이 전환이 오버플로됩니다.

클래스 정의의 속성 위에 다음 줄을 추가하여

    @Extension(vendorName="datanucleus", key="gae.unindexed", value="true")

색인이 생성되지 않은 속성을 선언할 수 있습니다. 속성이 색인을 생성하지 않는다는 의미에 대한 자세한 내용은 주 문서의 색인이 생성되지 않은 속성 섹션을 참조하세요.

상속

상속을 사용하는 데이터 클래스를 만드는 것은 자연스러운 일이며, JDO는 이를 지원합니다. App Engine에서 JDO 상속이 어떻게 작동하는지 알아보기 전에 이 주제에 대한 DataNucleus 문서를 먼저 읽어보는 것이 좋습니다. 다 읽어보셨나요? 좋습니다. App Engine에서 JDO 상속은 DataNucleus 문서의 설명과 동일하게 작동하지만 일부 추가적인 제한사항이 있습니다. 구체적인 예를 통해 이러한 제한사항에 대해 알아보겠습니다.

'new-table' 상속 전략을 사용하면 여러 '테이블'로 단일 데이터 객체의 데이터를 분할할 수 있습니다. 하지만 App Engine Datastore는 조인을 지원하지 않으므로, 이 상속 전략을 사용하여 데이터 객체를 조작하려면 각 상속 수준의 원격 절차 호출이 필요합니다. 이는 매우 비효율적일 수 있으므로 상속 계층구조의 루트에 없는 데이터 클래스에서는 'new-table' 상속 전략이 지원되지 않습니다.

두 번째, 'superclass-table' 상속 전략을 사용하면 상위 클래스의 '테이블'에 데이터 객체의 데이터를 저장할 수 있습니다. 이 전략에는 내재된 비효율성은 없지만 현재 지원되지 않습니다. 이 내용은 향후 출시 버전에서 다시 설명할 예정입니다.

이제 'subclass-table'과 'complete-table' 전략은 DataNucleus 문서의 설명과 동일하게 작동하며, 상속 계층구조의 루트에 있는 모든 데이터 객체에서도 'new-table'을 사용할 수 있습니다. 예를 살펴보겠습니다.

Worker.java

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Inheritance;
import javax.jdo.annotations.InheritanceStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable
@Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)
public abstract class Worker {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String department;
}

Employee.java

// ... imports ...

@PersistenceCapable
public class Employee extends Worker {
    @Persistent
    private int salary;
}

Intern.java

import java.util.Date;
// ... imports ...

@PersistenceCapable
public class Intern extends Worker {
    @Persistent
    private Date internshipEndDate;
}

이 예시에서는 strategy> 속성을 InheritanceStrategy.SUBCLASS_TABLE로 설정하여 Worker 클래스 선언에 @Inheritance 주석을 추가했습니다. 이는 서브클래스의 Datastore 항목에 Worker의 모든 영구 필드를 저장하도록 JDO에 지시합니다. Employee 인스턴스를 포함한 makePersistent()를 호출한 결과로 만들어진 Datastore 항목에는 'department' 및 'salary'라는 속성 두 개가 있습니다. Intern 인스턴스를 포함한 makePersistent()를 호출한 결과로 만들어진 Datastore 항목에는 'department'와 'internshipEndDate'라는 속성 두 개가 있습니다. 데이터 저장소에는 'Worker' 종류의 어떤 항목도 포함되지 않습니다.

이제 더 흥미로운 내용을 살펴보겠습니다. EmployeeIntern 외에, 퇴사한 직원을 설명하는 Employee의 전문 분야도 필요하다고 가정해보겠습니다.

FormerEmployee.java

import java.util.Date;
// ... imports ...

@PersistenceCapable
@Inheritance(customStrategy = "complete-table")
public class FormerEmployee extends Employee {
    @Persistent
    private Date lastDay;
}

이 예시에서는 custom-strategy> 속성을 'complete-table'로 설정하여 FormerEmployee 클래스 선언에 @Inheritance 주석을 추가했습니다. 이는 JDO에 FormerEmployee의 모든 영구 필드와 슈퍼클래스를 FormerEmployee 인스턴스에 해당하는 Datastore 항목에 저장하도록 지시합니다. FormerEmployee 인스턴스를 포함한 makePersistent()를 호출한 결과로 만들어진 Datastore 항목에는 'department', 'salary', 'lastDay'라는 속성 세 개가 있습니다. 'Employee' 종류의 항목은 FormerEmployee에 해당하지 않습니다. 그러나 런타임 유형이 Employee인 객체를 포함한 makePersistent()를 호출하면 'Employee' 종류의 항목이 생성됩니다.

선언된 관계 필드의 유형이 해당 필드에 할당되는 객체의 런타임 유형과 일치하면 관계와 상속이 혼합되어도 문제가 없습니다. 자세한 내용은 다형성 관계 섹션을 참조하세요.