Избранное | Русский | Войти

Определение классов данных

JDO можно использовать для хранения простых Java-объектов (иногда называемых "старыми добрыми простыми Java-объектами" или "POJO") в хранилище данных. Каждый объект, который сделан постоянным с помощью PersistenceManager, становится объектом хранилища данных. Аннотации используются, чтобы определить способ хранения и воссоздания экземпляров классов данных для JDO.

Примечание. В ранних версиях JDO вместо аннотаций Java использовались XML-файлы .jdo. Они работают и в JDO 2.3. Однако в этой документации описаны только аннотации Java для классов данных.

Аннотации классов и полей

Все объекты, сохраненные JDO, становятся объектами хранилища данных App Engine. Тип объекта получается из простого названия класса (внутренние классы используют путь $ без названия пакета). Каждое постоянное поле класса представляет собой свойство объекта, причем название свойства совпадает с названием поля (с сохранением регистра).

Чтобы объявить класс Java в качестве сохраняемого и получаемого из хранилища данных с помощью JDO, используйте аннотацию @PersistenceCapable. Например:

import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
    // ...
}

Поля класса данных, которые нужно сохранить в хранилище данных, необходимо объявить в качестве постоянных. Чтобы объявить поле в качестве постоянного, используйте аннотацию @Persistent:

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

// ...
    @Persistent
    private Date hireDate;

Чтобы объявить поле в качестве непостоянного (оно не будет сохранено в хранилище данных и не будет восстановлено при получении объекта), используйте аннотацию @NotPersistent.

Совет. Если не указана ни аннотация @Persistent, ни аннотация @NotPersistent, по умолчанию JDO определяет поля некоторых типов в качестве постоянных, а других типов в качестве непостоянных. Полное описание такого поведения приведено в документации DataNucleus. Поскольку в соответствии со спецификацией JDO не все основные типы значений хранилища данных App Engine являются постоянными по умолчанию, для внесения ясности рекомендуется явно добавлять аннотацию @Persistent или @NotPersistent.

У поля может быть один из следующих типов, подробно рассмотренных далее:

  • один из основных типов, поддерживаемых хранилищем данных;
  • коллекция (например, java.util.List<...>) или массив значений основного типа хранилища данных;
  • экземпляр или коллекция экземпляров класса @PersistenceCapable;
  • экземпляр или коллекция экземпляров сериализуемого класса;
  • встроенный класс, сохраняемый как свойства элемента.

У класса данных одно поле должно быть отведено под хранение первичного ключа соответствующего объекта хранилища данных. Можно выбрать один из четырех типов полей ключей, каждое из которых использует свой тип значения и аннотации. (Дополнительную информацию можно найти в статье Создание данных: ключи.) Простейшее поле ключа представляет собой длинное целочисленное значение, при первом сохранении объекта в хранилище данных автоматически заполняемое JDO значением, уникальным относительно других экземпляров класса. Для ключей в виде длинного целого используются аннотации @PrimaryKey и @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY).

Совет. Сделайте все свои постоянные поля private или protected (или защищенными в пакете) и предоставьте к ним общий доступ с помощью методов средств доступа. При использовании прямого доступа к постоянному полю из другого класса может ускользнуть из внимания улучшение класса JDO. Вместо этого можно создать другие классы @PersistenceAware. Дополнительную информацию можно найти в документации по DataNucleus.

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

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

Вот пример класса данных:

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

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @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 Long getId() {
        return id;
    }

    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 String getHireDate() {
        return hireDate;
    } 
    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    } 
}

Основные типы значений

Хранилище данных поддерживает следующие основные типы значений:

Тип Класс Java Порядок сортировки Примечания
короткая текстовая строка, < 500 байт java.lang.String Unicode При значении длиннее 500 байт возникает исключение JDOFatalUserException.
короткая байтовая строка, < 500 байт com.google.appengine.api.datastore.ShortBlob побайтовый При значении длиннее 500 байт возникает исключение JDOFatalUserException.
логическое значение boolean или java.lang.Boolean false < true
целое short, java.lang.Short, int, java.lang.Integer, long, java.lang.Long Числовой Хранится в виде длинного целого, а затем преобразуется в тип поля. Переполнение значениями вне диапазона.
число с плавающей точкой float, java.lang.Float, double, java.lang.Double Числовой Хранится в виде числа с плавающей точкой двойной точности, а затем преобразуется в тип поля. Переполнение значениями вне диапазона.
дата-время java.util.Date Хронологический
аккаунт Google com.google.appengine.api.users.User По адресу электронной почты (Unicode)
длинная текстовая строка com.google.appengine.api.datastore.Text (не сортируется) Не индексируется.
длинная байтовая строка com.google.appengine.api.datastore.Blob (не сортируется) Не индексируется.
ключ объекта com.google.appengine.api.datastore.Key или ссылочный объект (в качестве дочернего) По элементам пути (типу, идентификатору или названию, типу, идентификатору или названию и т.д.)
URL com.google.appengine.api.datastore.Link Unicode

Примечание. Хранилище данных поддерживает несколько дополнительных основных типов значений, еще не реализованных в API хранилища данных Java, хотя уже реализованных в API Python. Если тип значения свойства совпадает с одним из указанных и у загруженного объекта есть поле для этого свойства, JDO будет вести себя как и при отсутствии свойства: полю будет присвоено значение null, или если поле не может принимать такое значение, возникнет исключение NullPointerException. Неподдерживаемые типы включают следующие: Category, Email, IM, PhoneNumber, PostalAddress, Rating, GeoPt.

Чтобы представить свойство, содержащее одно значение основного типа, объявите поле типа Java и используйте аннотацию @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(identityType = IdentityType.APPLICATION)
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(identityType = IdentityType.APPLICATION)
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private ContactInfo myContactInfo;

    // ... accessors ...
}

В этом примере если приложение создает экземпляр класса Employee, заполняет поле myContactInfo новым экземпляром класса ContactInfo, затем сохраняет экземпляр класса Employee с pm.makePersistent(...), значит хранилище данных создает два объекта. Один типа "ContactInfo", представляющий экземпляр класса ContactInfo. Другой типа "Employee". В качестве родителя группы объектов ключ объекта ContactInfo использует ключ объекта Employee.

Встроенные классы

Встроенные классы позволяют моделировать значение поля с помощью класса, при этом не требуется создание нового объекта хранилища данных и формирование отношения. Поля значения объекта напрямую сохраняются в соответствующем объекте хранилища данных.

В качестве встроенного в другой класс объекта можно использовать любой класс данных @PersistenceCapable. Поля класса @Persistent встроены в объект. Класс с аннотацией @EmbeddedOnly может использоваться только в качестве встроенного. Для встроенного класса не требуется поле первичного ключа, поскольку он не сохраняется в качестве отдельного объекта.

Вот пример встроенного класса. В этом примере встроенный класс превращен в использующий его внутренний класс данных. Это может пригодиться, однако не является обязательным условием превращения класса во встроенный.

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

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class EmployeeContacts {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    Long id;
    
    @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.Set<...>
  • java.util.SortedSet<...>
  • java.util.Stack<...>
  • java.util.TreeSet<...>
  • java.util.Vector<...>

Если поле объявлено в виде List, значения объектов, возвращаемых хранилищем данных, будут типа ArrayList. Если поле объявлено в виде Set, хранилище данных вернет HashSet, если в виде SortedSet – TreeSet.

Например, поле типа List<String> сохраняется в виде нуля или более строковых значений для свойства, по одному для каждого элемента в List.

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

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

Коллекция дочерних объектов (классов @PersistenceCapable) представляет несколько объектов с отношением "один ко многим". См. статью Отношения.

В фильтрах запросов и порядках сортировки свойства хранилища данных с несколькими значениями ведут себя особым образом. Дополнительную информацию можно найти в статье Запросы и индексы: порядки сортировки и свойства с несколькими значениями.

Поля и свойства объектов

В хранилище данных App Engine разграничиваются объекты без свойства и объекты со свойством, принимающим значение null. JDO не поддерживает это разграничение: у каждого поля объекта есть значение, которое может быть и null. Если полю данного типа, которое может принимать значение null (т.е. типа, отличного от встроенного, такого как int или boolean), при сохранении присваивается значение null, свойство результирующего объекта будет также иметь значение null.

Если объект хранилища данных загружается в другой объект, для поля которого у него нет свойства, при этом это поле может принимать значение null и иметь только одно значение, то полю присваивается значение null. При сохранении объекта обратно в хранилище данных значение этого свойства null. Если полю нельзя присвоить значение null, при загрузке объекта без соответствующего свойства возникает исключение. Этого не произойдет, если объект создан с помощью класса JDO, использованного для воссоздания экземпляра. Однако это может произойти при изменении класса JDO или в случае использования при создании низкоуровневого API вместо JDO.

Если тип поля является основным типом или сериализуемым классом и нет значений для свойства объекта, в хранилище данных будет представлена пустая коллекция путем присвоения свойству одного нулевого значения. Если поле является массивом, ему присваивается массив из нуля элементов. Если объект загружен и для свойства нет значения, полю присваивается пустая коллекция соответствующего типа. Технически, хранилище данных различает пустые коллекции и коллекции, содержащие одно нулевое значение.

Если у объекта есть свойство, для которого нет соответствующего поля, к этому свойству нельзя получить доступ. Если объект снова сохраняется в хранилище данных, лишнее свойство удаляется.

Если у объекта есть свойство, тип значения которого отличается от типа соответствующего поля, JDO пытается привести значение к типу поля. Если привести значение к типу поля не удается, в JDO возникает исключение ClassCastException. В случае чисел (длинных целых и чисел с плавающей точкой двойной точности) значение не приводится, а преобразуется. Если значение числового свойства больше допустимого для типа поля, при преобразовании происходит переполнение, при этом исключение не возникает.