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' 종류의 어떤 항목도 포함되지 않습니다.
이제 더 흥미로운 내용을 살펴보겠습니다. Employee
와 Intern
외에, 퇴사한 직원을 설명하는 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' 종류의 항목이 생성됩니다.
선언된 관계 필드의 유형이 해당 필드에 할당되는 객체의 런타임 유형과 일치하면 관계와 상속이 혼합되어도 문제가 없습니다. 자세한 내용은 다형성 관계 섹션을 참조하세요.