Java Platform, Enterprise Edition (Java EE) 8
Учебник по Java EE

Назад Вперёд Содержание

Сущности

Сущность — это легковесный объект персистентной области. Как правило, сущность представляет таблицу в реляционной базе данных, и каждый объект сущности соответствует строке в этой таблице. Основным программным артефактом сущности является класс сущности, хотя сущности могут использовать вспомогательные классы.

Персистентное состояние сущности проявляется через персистентные поля и персистентные свойства. Эти поля или свойства используют аннотации объектно-реляционного отображения для отображения сущностей и отношений сущностей с реляционными данными в хранилище данных.

Требования к классам сущностей

Класс сущности должен соответствовать следующим требованиям.

  • Класс должен быть аннотирован javax.persistence.Entity.

  • Класс должен иметь открытый или защищённый (protected) конструктор без аргументов. Класс может иметь другие конструкторы.

  • Класс не должен быть объявлен final. Методы и персистентные переменные объекта не должны быть объявлены final.

  • Если объект сущности передаётся по значению как отдельный объект, например, через удалённый бизнес-интерфейс сессионного компонента, класс должен реализовать интерфейс Serializable.

  • Сущности могут расширять классы сущностей и не-сущностей, а классы не-сущностей могут расширять классы сущностей.

  • Персистентные переменные должны быть иметь приватный, защищённый (protected) или пакетный доступ, и доступ к ним можно получить только через методы класса сущности. Клиенты должны получить доступ к состоянию объекта с помощью методов доступа или бизнес-методов.

Персистентные поля и свойства в классах сущностей

Доступ к персистентному состоянию объекта можно получить через переменные или свойства объекта. Поля или свойства должны иметь следующие типы Java:

  • Java примитивы

  • java.lang.String

  • Другие сериализуемые типы, в том числе:

    • Обёртки (wrapper) примитивов Java

    • java.math.BigInteger

    • java.math.BigDecimal

    • java.util.Date

    • java.util.Calendar

    • java.sql.Date

    • java.sql.Time

    • java.sql.TimeStamp

    • Пользовательские сериализуемые типы

    • byte[]

    • Byte[]

    • char[]

    • Character[]

  • Перечислимые типы

  • Другие объекты и/или коллекции объектов

  • Встраиваемые классы

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

Персистентные поля

Если класс сущности использует персистентные поля, среда выполнения персистентности напрямую обращается к переменным объекта класса сущности. Все поля, не аннотированные javax.persistence.Transient или не помеченные как Java transient, будут сохранены в хранилище данных. Аннотации объектно-реляционного отображения должны применяться к переменным объекта.

Персистентные свойства

Если объект использует персистентные свойства, он должен следовать соглашениям о методах компонентов JavaBeans. Свойства стиля JavaBeans используют get- и set- методы, которые обычно именуются в соответствии с именами переменных объекта класса сущности. Для каждого персистентного свойства типа Type объекта существует метод получения getProperty и метод установки setProperty. Если свойство логического типа, можно использовать isProperty вместо getProperty. Например, если сущность Customer использует персистентные свойства и имеет приватную переменную firstName, класс определяет методы getFirstName и setFirstName для чтения и установки состояния переменной firstName.

Сигнатуры метода для однозначных персистентных свойств следующие:

Type getProperty()
void setProperty(Type type)

Аннотации объектно-реляционного отображения для персистентных свойств должны быть применимы к get-методам. Аннотации отображения нельзя применять к полям или свойствам, аннотированным @Transient или помеченным transient.

Использование коллекций в полях и свойствах сущностей

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

  • java.util.Collection

  • java.util.Set

  • java.util.List

  • java.util.Map

Если класс сущности использует персистентные поля, тип в сигнатурах предыдущего метода должен быть одним из этих типов коллекции. Универсальные варианты этих типов коллекций также могут быть использованы. Например, если у него есть персистентное свойство, содержащее набор телефонных номеров, сущность Customer будет иметь следующие методы:

Set<PhoneNumber> getPhoneNumbers() { ... }
void setPhoneNumbers(Set<PhoneNumber>) { ... }

Если поле или свойство объекта состоит из коллекции базовых типов или встраиваемых классов, используйте аннотацию javax.persistence.ElementCollection для поля или свойства.

Атрибуты targetClass и fetch аннотации @ElementCollection. Атрибут targetClass указывает имя класса базового или встраиваемого класса и является необязательным, если поле или свойство определяются с использованием обобщений (generics) Java. Необязательный атрибут fetch используется, чтобы указать, следует использовать для коллекции отложенное (lazy) или раннее (eager) извлечение, используя соответственно константы LAZY или EAGER перечислимого типа javax.persistence.FetchType. По умолчанию коллекция будет извлекаться отложенно (lazy).

Следующая сущность, Person, имеет персистентное поле nicknames, которое представляет собой коллекцию классов String и для которой будет использоваться раннее (eager) извлечение. Элемент targetClass не является обязательным, потому что он использует обобщённые значения (generics) при определении поля:

@Entity
public class Person {
    ...
    @ElementCollection(fetch=EAGER)
    protected Set<String> nickname = new HashSet();
    ...
}

Коллекции элементов сущностей и отношений могут быть представлены коллекциями java.util.Map. Map состоит из ключа и значения.

При использовании элементов или отношений Map применяются следующие правила.

  • Ключ или значение Map могут быть базовым типом Java, встраиваемым классом или сущностью.

  • Если значение Map является встраиваемым классом или базовым типом, используйте аннотацию @ElementCollection.

  • Если значение Map является сущностью, используйте аннотацию @OneToMany или @ManyToMany.

  • Используйте тип Map только для одной из сторон двунаправленных отношений.

Если тип ключа Map является базовым типом Java, используйте аннотацию javax.persistence.MapKeyColumn, чтобы установить mapping столбца для ключа. По умолчанию атрибут name в @MapKeyColumn имеет форму RELATIONSHIP-FIELD/PROPERTY-NAME`_KEY`. Например, если имя поля ссылочной связи — image, атрибут name по умолчанию — IMAGE_KEY.

Если тип ключа Map является сущностью, используйте аннотацию javax.persistence.MapKeyJoinColumn. Если для отображения требуется несколько столбцов, используйте аннотацию javax.persistence.MapKeyJoinColumns, чтобы включить несколько аннотаций @MapKeyJoinColumn. Если @MapKeyJoinColumn отсутствует, по умолчанию для имени столбца сопоставления устанавливается значение RELATIONSHIP-FIELD/PROPERTY-NAME`_KEY`. Например, если имя поля отношения employee, атрибут name по умолчанию — EMPLOYEE_KEY.

Если обобщённые (generic) типы Java не используются в поле отношения или свойстве, класс ключа должен быть явно установлен с использованием аннотации javax.persistence.MapKeyClass.

Если ключ Map является первичным ключом или персистентным полем или свойством объекта, который является значением Map, используйте аннотацию javax.persistence.MapKey. Аннотации @MapKeyClass и @MapKey нельзя использовать для одного поля или свойства.

Если значение Map является базовым типом Java или встраиваемым классом, оно будет отображено как таблица коллекции в базе данных. Если обобщённые (generic) типы не используются, для атрибута targetClass аннотации @ElementCollection должен быть указан тип значения Map.

Если значение Map является сущностью и частью отношения «многие ко многим» или однонаправленного отношения «один ко многим», оно будет отображено на промежуточную таблицу в базе данных. Однонаправленное отношение «один ко многим», в котором используется Map, также может быть отображено с помощью аннотации @JoinColumn.

Если объект является частью двунаправленного отношения «один ко многим/многие к одному», он будет отображён в таблице объекта, представляющего значение Map. Если обобщённые (generic) типы не используются, атрибут targetEntity в аннотациях @OneToMany и @ManyToMany должен быть установлен на тип значения Map.

Валидация персистентных полей и свойств

Java API для валидации бинов (Bean Validation) предоставляет механизм для валидации данных приложения. Bean Validation интегрирован в контейнеры Java EE, что позволяет использовать одну и ту же логику валидации в любом слое корпоративного приложения.

Ограничения Bean Validation могут применяться к классам постоянных сущностей, встраиваемым классам и отображаемым родительским классам. По умолчанию Persistence provider будет автоматически выполнять валидацию сущностей с персистентными полями или свойствами, аннотированными ограничениями Bean Validation сразу после событий PrePersist, PreUpdate и PreRemove жизненного цикла.

Ограничения Bean Validation — это аннотации, применяемые к полям или свойствам классов Java. Bean Validation предоставляет набор ограничений, а также API для определения пользовательских ограничений. Пользовательские ограничения могут быть комбинациями ограничений по умолчанию или новыми ограничениями, которые не используют ограничения по умолчанию. Каждое ограничение связано по крайней мере с одним классом валидатора, который проверяет значение поля или свойства. Разработчики пользовательских ограничений также должны предоставить класс валидатора для ограничения.

Ограничения Bean Validation применяются к персистентным полям или свойствам персистентных классов. При добавлении ограничений Bean Validation используйте ту же стратегию доступа, что и для персистентного класса. То есть, если персистентный класс использует доступ к полям, примените аннотации ограничения Bean Validation к полям класса. Если класс использует доступ к свойству, примените ограничения на get-методы.

Таблица 22-1 перечисляет предустановленные ограничения Bean Validation, определённые в пакете javax.validation.constraints.

Все предустановленные ограничения, перечисленные в таблице 22-1, имеют соответствующую аннотацию ConstraintName`.List` для группировки нескольких ограничений одного типа в одном поле или свойстве. Например, следующее персистентное поле имеет два ограничения @Pattern:

@Pattern.List({
    @Pattern(regexp="..."),
    @Pattern(regexp="...")
})

Следующий класс сущности, Contact, имеет ограничения Bean Validation, примененные к его персистентным полям:

@Entity
public class Contact implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @NotNull
    protected String firstName;
    @NotNull
    protected String lastName;
    @Pattern(regexp = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\."
            + "[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@"
            + "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9]"
            + "(?:[a-z0-9-]*[a-z0-9])?",
            message = "{invalid.email}")
    protected String email;
    @Pattern(regexp = "^\\(?(\\d{3})\\)?[- ]?(\\d{3})[- ]?(\\d{4})$",
            message = "{invalid.phonenumber}")
    protected String mobilePhone;
    @Pattern(regexp = "^\\(?(\\d{3})\\)?[- ]?(\\d{3})[- ]?(\\d{4})$",
            message = "{invalid.phonenumber}")
    protected String homePhone;
    @Temporal(javax.persistence.TemporalType.DATE)
    @Past
    protected Date birthday;
    ...
}

Аннотация @NotNull в полях firstName и lastName указывает, что эти поля теперь обязательны для заполнения. Если создаётся новый объект Contact, где firstName или lastName не были инициализированы, Bean Validation выдаст ошибку валидации. Аналогично, если ранее созданный объект Contact был изменён так, что firstName или lastName имеют значение null, будет выдана ошибка валидации.

К полю email применено ограничение @Pattern со сложным регулярным выражением, которое соответствует большинству допустимых адресов электронной почты. Если значение email не соответствует этому регулярному выражению, будет выдана ошибка валидации.

Поля homePhone и mobilePhone имеют одинаковые ограничения @Pattern. Регулярное выражение соответствует десятизначным телефонным номерам США и ​​Канады в форме (`xxx)` xxx`-`xxxx.

Поле birthday аннотировано ограничением @Past, которое гарантирует, что значение birthday должно быть в прошлом.

Первичные ключи в сущностях

Каждый объект имеет уникальный идентификатор объекта. Например, объект клиента может быть идентифицирован по номеру клиента. Уникальный идентификатор или первичный ключ позволяет клиентам находить конкретный объект. Каждый объект должен иметь первичный ключ. Сущность может иметь простой или составной первичный ключ.

Простые первичные ключи используют аннотацию javax.persistence.Id для обозначения свойства или поля первичного ключа.

Составные первичные ключи используются, когда первичный ключ состоит из более чем одного атрибута, который соответствует набору отдельных персистентных свойств или полей. Составные первичные ключи должны быть определены в классе первичных ключей. Составные первичные ключи обозначаются с использованием аннотаций javax.persistence.EmbeddedId и javax.persistence.IdClass.

Первичный ключ или свойство или поле составного первичного ключа должны быть одного из следующих типов языка Java:

  • Java примитивы

  • Обёртки (wrapper) примитивов Java

  • java.lang.String

  • java.util.Date (временной тип должен быть DATE)

  • java.sql.Date

  • java.math.BigDecimal

  • java.math.BigInteger

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

Класс первичного ключа должен соответствовать следующим требованиям.

  • Модификатор доступа класса должен быть public.

  • Свойства класса первичного ключа должны быть public или protected, если используется доступ на основе свойств.

  • Класс должен иметь публичный конструктор по умолчанию.

  • Класс должен реализовывать методы hashCode() и equals(Object other).

  • Класс должен быть сериализуемым.

  • Составной первичный ключ должен включать несколько полей или свойств класса сущности или должен быть представлен как встраиваемый класс.

  • Если класс сопоставлен с несколькими полями или свойствами класса сущности, имена и типы полей или свойств первичного ключа в классе первичного ключа должны совпадать с именами класса сущности.

Следующий класс первичного ключа является составным ключом, и поля customerOrder и itemId вместе однозначно идентифицируют объект сущности:

public final class LineItemKey implements Serializable {
    private Integer customerOrder;
    private int itemId;

    public LineItemKey() {}

    public LineItemKey(Integer order, int itemId) {
        this.setCustomerOrder(order);
        this.setItemId(itemId);
    }

    @Override
    public int hashCode() {
        return ((this.getCustomerOrder() == null
                ? 0 : this.getCustomerOrder().hashCode())
                ^ ((int) this.getItemId()));
    }

    @Override
    public boolean equals(Object otherOb) {
        if (this == otherOb) {
            return true;
        }
        if (!(otherOb instanceof LineItemKey)) {
            return false;
        }
        LineItemKey other = (LineItemKey) otherOb;
        return ((this.getCustomerOrder() == null
                ? other.getCustomerOrder() == null : this.getCustomerOrder()
                .equals(other.getCustomerOrder()))
                && (this.getItemId() == other.getItemId()));
    }

    @Override
    public String toString() {
        return "" + getCustomerOrder() + "-" + getItemId();
    }
    /*  get- и set- методы */
}

Множественность в отношениях сущностей

Отношения между сущностями бывают следующих типов.

  • Один-к-одному: каждый объект связан с одним другим объектом. Например, для моделирования физического хранилища, в котором каждая корзина содержит один виджет, StorageBin и Widget будут иметь отношение один к одному. Отношения «один к одному» используют аннотацию javax.persistence.OneToOne для соответствующего персистентного свойства или поля.

  • Один-ко-многим: объект сущности может быть связан с несколькими объектами других сущностей. Например, заказ на продажу может содержать несколько позиций. В приложении заказа CustomerOrder будет иметь отношение «один ко многим» с LineItem. Отношения «один ко многим» используют аннотацию javax.persistence.OneToMany для соответствующего персистентного свойства или поля.

  • Многие-к-одному: несколько объектов сущности могут быть связаны с одним объектом другой сущности. Эта множественность является противоположностью отношения «один ко многим». В только что упомянутом примере отношение к CustomerOrder с точки зрения LineItem является «многие к одному». Отношения «многие к одному» используют аннотацию javax.persistence.ManyToOne для соответствующего персистентного свойства или поля.

  • Многие-ко-многим: объекты сущности могут быть связаны с несколькими объектами друг друга. Например, на каждом курсе колледжа есть много студентов, и каждый студент может пройти несколько курсов. Поэтому в заявке на зачисление Course и Student будут иметь отношение «многие ко многим». Отношения «многие ко многим» используют аннотацию javax.persistence.ManyToMany для соответствующего персистентного свойства или поля.

Направление в отношениях сущностей

Отношения могут быть двунаправленными или однонаправленными. Двунаправленное отношение имеет как собственную сторону, так и обратную сторону. Однонаправленные отношения имеют только свою сторону. Сторона-владелец связи определяет, как среда выполнения персистентности обновляет связь в базе данных.

Двунаправленные отношения

В двунаправленном отношении каждый объект имеет поле отношения или свойство, которое ссылается на другой объект. Через поле или свойство отношения код класса сущности может получить доступ к связанному объекту. Если у сущности есть связанное поле, говорят, что сущность «знает» о своём родственном объекте. Например, если CustomerOrder знает, какие у него есть объекты LineItem и если LineItem знает, к какому CustomerOrder он принадлежит, то отношение этих сущностей двунаправленное.

Двунаправленные отношения должны следовать следующим правилам.

  • Обратная сторона двунаправленного отношения должна ссылаться на сторону владельца, используя элемент mappedBy аннотаций @OneToOne, @OneToMany или @ManyToMany. Элемент mappedBy обозначает свойство или поле в объекте, который является владельцем отношения.

  • Двусторонняя связь «многие к одному» не должна определять элемент mappedBy. Множественная сторона всегда является владельцем отношения.

  • Для двунаправленных отношений «один к одному» сторона-владелец соответствует стороне, которая содержит соответствующий внешний ключ.

  • Для двунаправленных отношений «многие ко многим» любая сторона может быть стороной-владельцем.

Однонаправленные отношения

В однонаправленном отношении только один объект имеет поле или свойство отношения, которое относится к другому. Например, LineItem будет иметь поле отношения, которое идентифицирует Product, но Product не будет иметь поле отношения или свойство для LineItem. Другими словами, LineItem знает о Product, но Product не знает, какие объекты LineItem ссылаются на него.

Запросы и направление отношений

Язык запросов персистентности Java и запросы Criteria API часто перемещаются по взаимосвязям. Направление отношения определяет, может ли запрос перемещаться от одного объекта к другому. Например, запрос может перейти от LineItem к Product, но не может перейти в противоположном направлении. Для CustomerOrder и LineItem запрос может перемещаться в обоих направлениях, поскольку эти два объекта имеют двунаправленное отношение.

Каскадные операции и отношения

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

Перечислимый тип javax.persistence.CascadeType определяет каскадные операции, которые применяются в элементе cascade аннотаций отношений. Таблица 40-1 перечисляет каскадные операции для объектов.

Таблица 40-1 Каскадные операции для сущностей

Каскадная операция

Описание

ALL

Все каскадные операции будут применены к сущности, связанной с родительской сущностью. All эквивалентно указанию cascade={DETACH, MERGE, PERSIST, REFRESH, REMOVE}

DETACH

Если родительский объект отсоединён от контекста персистентности, связанный объект также будет отсоединён.

MERGE

Если родительский объект объединён с контекстом персистентности, связанный объект также будет объединён.

PERSIST

Если родительский объект сохраняется в контексте персистентности, связанный объект также будет сохраняться.

REFRESH

Если родительский объект обновляется в текущем контексте персистентности, связанный объект также будет обновляться.

REMOVE

Если родительский объект удаляется из текущего контекста персистентности, связанный объект также будет удалён.

Отношения каскадного удаления задаются с помощью указания cascade=REMOVE для отношений @OneToOne и @OneToMany. Например:

@OneToMany(cascade=REMOVE, mappedBy="customer")
public Set<CustomerOrder> getOrders() { return orders; }

Удаление потерянных объектов

Когда целевой объект в отношении «один-к-одному» или «один-ко-многим» удаляется, часто желательно каскадно удалить и связанные с ним объекты. Такие связанные объекты считаются потерянными, а атрибут orphanRemoval может использоваться для указания того, что такие потерянные объекты должны быть удалены. Например, если заказ содержит много позиций, и одна из них удалена из заказа, удалённая позиция считается потерянной. Если для orphanRemoval установлено значение true, сущность позиции будет удалена при удалении позиции из заказа.

Атрибут orphanRemoval в @OneToMany и @OneToOne принимает логическое значение и по умолчанию имеет значение false.

В следующем примере каскадно удаляется потерянный объект order при удалении объекта customer:

@OneToMany(mappedBy="customer", orphanRemoval="true")
public List<CustomerOrder> getOrders() { ... }

Встраиваемые классы в сущностях

Встраиваемые классы используются для представления состояния объекта, но не имеют собственного персистентного идентификатора в отличие от классов объектов. Объекты встраиваемого класса используют идентификатор объекта, которому они принадлежат. Встраиваемые классы существуют только как состояние другого объекта. Сущность может иметь однозначные атрибуты или атрибуты-коллекции встраиваемых классов.

Встраиваемые классы имеют те же правила, что и классы сущностей, но снабжены аннотацией javax.persistence.Embeddable вместо @Entity.

Следующий встраиваемый класс, ZipCode, имеет поля zip и plusFour:

@Embeddable
public class ZipCode {
    String zip;
    String plusFour;
    ...
}

Этот встраиваемый класс используется сущностью Address:

@Entity
public class Address {
    @Id
    protected long id
    String street1;
    String street2;
    String city;
    String province;
    @Embedded
    ZipCode zipCode;
    String country;
    ...
}

Объекты, имеющие встраиваемые классы частью своего персистентного состояния, могут аннотировать поле или свойство аннотацией javax.persistence.Embedded, но не обязаны это делать.

Встраиваемые классы могут сами использовать другие встраиваемые классы для представления своего состояния. Они также могут содержать коллекции базовых типов Java или других встраиваемых классов. Встраиваемые классы также могут содержать отношения к другим объектам или коллекциям объектов. Если у встраиваемого класса есть такая связь, то связь идёт от целевой сущности или коллекции сущностей к сущности, которой принадлежит встраиваемый класс.


Назад Вперёд Содержание
Логотип Oracle  Copyright © 2017, Oracle и/или её дочерних компаний. Все права защищены. Версия перевода 1.0.5 (Java EE Tutorial — русскоязычная версия)