Объектно-реляционные отображения могут быть определены тремя способами:
используя аннотации Java 5 (через Java Persistence 2 annotations)
используя дескрипторы развертывания JPA 2 XML (описанные в главе XXX)
используя подход "Hibernate унаследованные XML-файлы", известные как hbm.xml
Аннотации разделены на две категории: аннотации логического отображения (описание объектной модели,
связь между двумя объектами и т. д.) и аннотации физического отображения (описывающие
физическую схему, таблицы, столбцы, индексы и т. д.). Мы будем смешивать аннотации
из обеих категорий в следующих примерах кода.
Аннотации JPA находятся в пакете javax.persistence.*. Специальные расширения
для Hibernate находятся в org.hibernate.annotations.*. Ваша любимая IDE может
автоматически заполнять аннотации и их атрибуты (даже без специального плагина
«JPA», поскольку аннотации JPA — это простые аннотации Java 5).
Вот пример отображения:
package eg;
@Entity
@Table(name="cats") @Inheritance(strategy=SINGLE_TABLE)
@DiscriminatorValue("C") @DiscriminatorColumn(name="subclass", discriminatorType=CHAR)
public class Cat {
@Id @GeneratedValue
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public BigDecimal getWeight() { return weight; }
public void setWeight(BigDecimal weight) { this.weight = weight; }
private BigDecimal weight;
@Temporal(DATE) @NotNull @Column(updatable=false)
public Date getBirthdate() { return birthdate; }
public void setBirthdate(Date birthdate) { this.birthdate = birthdate; }
private Date birthdate;
@org.hibernate.annotations.Type(type="eg.types.ColorUserType")
@NotNull @Column(updatable=false)
public ColorType getColor() { return color; }
public void setColor(ColorType color) { this.color = color; }
private ColorType color;
@NotNull @Column(updatable=false)
public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; }
private String sex;
@NotNull @Column(updatable=false)
public Integer getLitterId() { return litterId; }
public void setLitterId(Integer litterId) { this.litterId = litterId; }
private Integer litterId;
@ManyToOne @JoinColumn(name="mother_id", updatable=false)
public Cat getMother() { return mother; }
public void setMother(Cat mother) { this.mother = mother; }
private Cat mother;
@OneToMany(mappedBy="mother") @OrderBy("litterId")
public Set<Cat> getKittens() { return kittens; }
public void setKittens(Set<Cat> kittens) { this.kittens = kittens; }
private Set<Cat> kittens = new HashSet<Cat>();
}
@Entity @DiscriminatorValue("D")
public class DomesticCat extends Cat {
public String getName() { return name; }
public void setName(String name) { this.name = name }
private String name;
}
@Entity
public class Dog { ... }
В подходе "Hibernate унаследованные XML-файлы" используется XML-схема, предназначенная для чтения
и редактирования вручную. Язык отображения является Java-ориентированным, что означает,
что отображения построены вокруг деклараций постоянных классов, а не деклараций таблиц.
Обратите внимание: несмотря на то, что многие пользователи Hibernate предпочитают писать
XML вручную, существует ряд инструментов для создания отображения. К ним относятся XDoclet,
Middlegen и AndroMDA.
Теперь мы обсудим концепции документов отображений (как аннотаций, так и XML). Однако мы будем
описывать только элементы и атрибуты документа, используемые Hibernate во время выполнения (runtime).
Документ отображения также содержит дополнительные необязательные атрибуты и элементы, которые влияют
на схемы базы данных, экспортируемые инструментом экспорта схемы (например, атрибут not-null).
5.1.1. Сущность (Entity)
Сущность представляет собой обычный объект Java (он же POJO), который будет сохраняться (persist) Hibernate.
Чтобы пометить объект как сущность в аннотациях, используйте аннотацию @Entity.
@Entity
public class Flight implements Serializable {
Long id;
@Id
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
Существуют возможности для настройки отображения сущности, давайте изучим их.
@Table позволяет определить таблицу, в которой объект будет сохранён. Если не указано,
то именем таблицы будет неквалифицированное имя класса сущности. Вы можете указать каталог,
схему, а также уникальные ограничения в таблице.
@Entity
@Table(name="TBL_FLIGHT",
schema="AIR_COMMAND",
uniqueConstraints=
@UniqueConstraint(
name="flight_number",
columnNames={"comp_prefix", "flight_number"} ) )
public class Flight implements Serializable {
@Column(name="comp_prefix")
public String getCompagnyPrefix() { return companyPrefix; }
@Column(name="flight_number")
public String getNumber() { return number; }
}
Имя ограничения является необязательным (сгенерировано, если не указано). Имена столбцов, составляющие
ограничение, соответствуют именам столбцов, которые определены до применения Hibernate
NamingStrategy.
Совет
Обязательно используйте имена столбцов уровня базы данных для свойства columnNames
объекта @UniqueConstraint. Например, хотя для простых типов имя столбца уровня
базы данных может быть таким же, как имя свойства уровня сущности, часто бывает что
это не сработает для реляционных свойств.
@Entity.name позволяет определить короткое имя объекта, который вы можете использовать
в запросах JP-QL и HQL. По умолчанию используется неквалифицированное имя класса.
Hibernate выходит за рамки спецификации JPA и предоставляет дополнительные конфигурации.
Некоторые из них размещены в @org.hibernate.annotations.Entity:
dynamicInsert / dynamicUpdate (по умолчанию false): указывает, что
INSERT / UPDATE SQL должен быть сгенерирован во время выполнения
и содержать только столбцы, значения которых не равны нулю. Параметры dynamic-update
и dynamic-insert не наследуются подклассами. Хотя в некоторых случаях
эти параметры могут повысить производительность, в других они могут снизить производительность.
selectBeforeUpdate (по умолчанию false): указывает, что Hibernate никогда
не должен выполнять SQL UPDATE, если не будет уверен, что объект фактически изменен.
Только когда переходный (transient) объект был ассоциирован с новой сессией (Session)
с помощью update(),
Hibernate выполнит дополнительный SQL SELECT, чтобы определить, действительно ли
требуется UPDATE. Использование select-before-update обычно снижает
производительность. Полезно запретить запуск триггера обновления базы данных без необходимости,
если вы переприсоедините (reattach) граф отсоединённых (detached) элементов к сессии (Session).
polymorphisms (по умолчанию IMPLICIT): определяет, используются ли
неявные или явные полиморфизмы запросов. Неявные (implicit) полиморфизмы означают,
что экземпляры класса будут возвращены запросом, который именует любой суперкласс или реализованный
интерфейс или класс, а экземпляры любого подкласса класса будут возвращены запросом, который
именует сам класс. Явные (Explicit) полиморфизмы означают, что экземпляры классов будут
возвращаться только запросами, которые явно именуют этот класс. Запросы, которые именуют класс,
возвращают только экземпляры отображённых подклассов. Для большинства целей, по умолчанию
подходят polymorphisms=IMPLICIT. Явные полиморфизмы полезны, когда два разных класса
отображаются на одну и ту же таблицу. Это разрешает «легковесный» класс,
который содержит подмножество столбцов таблицы.
persister: указывает пользовательский ClassPersister. Атрибут
persister позволяет настроить стратегию персистентности, используемую для класса.
Например, вы можете указать свой собственный подкласс org.hibernate.persister.EntityPersister
или даже предоставить совершенно новую реализацию интерфейса org.hibernate.persister.ClassPersister,
который реализует, например, сохранение на основе вызовов хранимой процедуры, сериализации
для плоских файлов или LDAP. См. в org.hibernate.test.CustomPersister простой
пример «persistence» для Hashtable.
optimisticLock (по умолчанию VERSION): определяет стратегию оптимистической
блокировки. Если вы включите dynamicUpdate, у вас будет выбор
стратегии оптимистической блокировки:
version: проверить столбцы version/timestamp
all: проверить все столбцы
dirty: проверить изменённые столбцы, разрешить некоторые одновременные обновления
none: не использовать оптимистическую блокировку
Настоятельно рекомендуется использовать столбцы version/timestamp для оптимистической блокировки
с помощью Hibernate. Эта стратегия оптимизирует производительность и корректно обрабатывает изменения,
внесённые в отсоединённые (detached) экземпляры (т. е. когда используется
Session.merge()).
Совет
Обязательно импортируйте @javax.persistence.Entity, чтобы пометить класс как сущность.
Распространенной ошибкой является импорт @org.hibernate.annotations.Entity.
Некоторые сущности не являются изменяемыми (mutable). Они не могут быть обновлены приложением.
Это позволяет Hibernate сделать некоторые незначительные оптимизации производительности. Для таких объектов
используйте аннотацию @Immutable.
Вы также можете изменить то, как Hibernate поступает с ленивой (lazy) инициализацией этого класса.
На @Proxy используйте lazy=false, чтобы отключить ленивую выборку (fetching)
(не рекомендуется). Вы также можете указать интерфейс для использования ленивых инициализирующих
прокси (по умолчанию относится к самому классу): используйте proxyClass
на @Proxy. Hibernate первоначально будет возвращать прокси (используя провайдер байтового кода,
определённый hibernate.bytecode.provider), который реализует именованный интерфейс.
Постоянный (persistent) объект будет загружаться при вызове метода прокси. См.
«Инициализация коллекций и прокси» ниже.
@BatchSize указывает «размер пакета (batch size)» для получения экземпляров этого класса
по идентификатору. Пока не загруженные экземпляры загружаются в размере пакета разом
(по умолчанию 1).
Вы можете указать произвольное условие SQL WHERE, которое будет использоваться при извлечении объектов
этого класса. Для этого используйте @Where.
Нет никакой разницы между представлением (view) и базовой таблицей для отображения Hibernate. Это прозрачно
на уровне базы данных, хотя некоторые СУБД не поддерживают представления должным образом,
особенно с обновлениями. Иногда вам захочется использовать представление, но вы не сможете
создать его в базе данных (т. е. с унаследованной схемой). В этом случае вы можете
отобразить неизменяемую (immutable) и доступную только для чтения сущность в SQL-выражение
подзапроса выборки (subselect), используя @org.hibernate.annotations.Subselect:
@Entity
@Subselect("select item.name, max(bid.amount), count(*) "
+ "from item "
+ "join bid on bid.item_id = item.id "
+ "group by item.name")
@Synchronize( {"item", "bid"} ) //tables impacted
public class Summary {
@Id
public String getId() { return id; }
...
}
Объявите таблицы с которыми будет синхронизироваться эта сущность, с гарантией, что автоматическая очистка
(auto-flush) происходит правильно и что запросы к производной сущности не возвращают
устаревшие данные. <subselect> доступен в обоих видах: как атрибут и как вложенный элемент отображения.
Теперь мы рассмотрим те же параметры, используя структуру hbm.xml. Вы можете объявить постоянный
(persistent) класс с помощью элемента class. Например:
(необязательный): полное имя класса Java для постоянного класса или интерфейса.
Если этот атрибут отсутствует, предполагается, что это отображение для сущности, не являющейся POJO.
table
(необязательный. по умолчанию используется неквалифицированное имя класса):
имя его таблицы базы данных.
discriminator-value
(необязательный. по умолчанию используется имя класса): значение, которое отличает отдельные
подклассы, которые используются для полиморфного поведения. Допустимые значения включают null
и not null.
mutable
(необязательный. по умолчанию true): указывает, что экземпляры класса (не) изменяемы.
schema
(необязательный): переопределяет имя схемы, указанное корневым элементом
<hibernate-mapping>.
catalog
(необязательный): переопределяет имя каталога, указанное корневым элементом
<hibernate-mapping>.
proxy
(необязательный): указывает интерфейс, используемый для ленивых инициализирующих прокси.
Вы можете указать имя самого класса.
dynamic-update
(необязательный. по умолчанию false): указывает, что UPDATE SQL
должен быть сгенерирован во время выполнения и может содержать только те столбцы,
значения которых изменены.
dynamic-insert
(необязательный. по умолчанию false): указывает, что INSERT SQL
должен быть сгенерирован во время выполнения и содержать только те столбцы,
значения которых не null.
select-before-update
(необязательный. по умолчанию false): указывает, что Hibernate никогда
не должен выполнять SQL UPDATE, если не будет уверен, что объект действительно
изменен. Только когда переходный (transient) объект был связан с новой сессей с помощью
update(), Hibernate выполнит дополнительный SQL SELECT, чтобы определить,
действительно ли требуется UPDATE.
polymorphisms
(необязательный. по умолчанию implicit): определяет какие полиморфизмы запросов
использовать: неявные (implicit) или явные (explicit).
where
(необязательный): задает произвольное условие SQL WHERE, которое будет использоваться
при извлечении объектов этого класса.
persister
(необязательный): указывает пользовательский ClassPersister.
batch-size
(необязательный. по умолчанию 1): определяет размер пакета (batch size)
для получения экземпляров этого класса по идентификатору.
optimistic-lock
(необязательный. по умолчанию version): определяет "оптимистическую стратегию блокировки".
lazy
(необязательный): lazy fetching (ленивая выборка) можно отключить, установив lazy=«false».
entity-name
(необязательный. по умолчанию имя класса): Hibernate позволяет отображать класс несколько раз,
потенциально для разных таблиц. Он также позволяет отображать сущности, которые представлены
Картами (Maps) или XML на уровне Java. В этих случаях вы должны указать явное
произвольное имя для сущности. См. Разделы
4.4. «Динамические модели»
и 5.1.1. СущностьЧтобы получить больше информации.
check
(необязательный): выражение SQL, используемое для генерации многострочного контрольного
ограничения (multi-row check constraint) для автоматической генерации схемы.
rowid
(необязательный): Hibernate может использовать ROWID в базах данных. В Oracle, например,
Hibernate может использовать дополнительный столбец rowid для быстрых обновлений,
как только этот параметр установлен в столбец rowid.
ROWID является деталью реализации и представляет физическое местоположение хранимого кортежа (tuple).
subselect
(необязательный): отображает неизменяемую и доступную только для чтения сущность
в subselect базы данных. Это полезно если вы хотите иметь представление (view)
вместо базовой таблицы. Смотрите ниже для получения дополнительной информации.
abstract
(необязательный): используется для обозначения абстрактных суперклассов в иерархиях
<union-subclass>.
Допустимо для названного постоянного класса быть интерфейсом. Вы можете объявить классы,
реализующие этот интерфейс, с помощью элемента <subclass>. Вы можете сохранить (persist) любой
статический внутренний класс. Укажите имя класса, используя стандартную форму,
например, Foo$Bar.
Вот как сделать виртуальный вид (virtual view) (подзапрос (subselect)) в XML:
<class name="Summary">
<subselect>
select item.name, max(bid.amount), count(*)
from item
join bid on bid.item_id = item.id
group by item.name
</subselect>
<synchronize table="item"/>
<synchronize table="bid"/>
<id name="name"/>
...
</class>
<Subselect> доступен и как атрибут, и как вложенный элемент отображения.
5.1.2. Идентификаторы
Классы отображений должны объявлять столбец первичного ключа таблицы базы данных. У большинства классов
также есть свойство стиля JavaBeans, содержащее уникальный идентификатор экземпляра.
Отметьте свойство идентификатора с помощью @Id.
@Entity
public class Person {
@Id Integer getId() { ... }
...
}
В hbm.xml используйте элемент <id>, который определяет отображение этого свойства на столбец
первичного ключа.
(необязательный. по умолчанию имя совйства): имя столбца с первичным ключём.
unsaved-value
(необязательный. по умолчанию «sensible»): значение свойства идентификатора,
которое указывает, что экземпляр только что создан (не сохранён), отличая его от отделённых
(detached) экземпляров, которые были сохранены или загружены в предыдущей сессии.
access
(необязательный. по умолчанию property): стратегия, которую Hibernate должна использовать
для доступа к значению свойства.
Если атрибут name отсутствует, предполагается, что класс не имеет свойства идентификатора.
Атрибут unsaved-value почти никогда не нужен в Hibernate и не имеет
соответствующего элемента в аннотациях.
Вы также можете объявить идентификатор как составной идентификатор. Это позволяет получить доступ
к унаследованным данным с составными ключами. Его использование настоятельно не рекомендуется
ни для чего другого.
5.1.2.1. Составные идентификаторы
Вы можете определить составной первичный ключ посредством несколько синтаксисов:
использовать тип компонента для представления идентификатора и отобразить его как свойство в сущности,
добавив к свойству аннотацию @EmbeddedId. Тип компонента должен быть Serializable.
отобразить несколько свойств как свойства @Id: тип идентификатора тогда будет являться классом
сущности и должен быть Serializable. Этот подход, к сожалению, не является
стандартным и поддерживается только Hibernate.
отобразить несколько свойств как свойства @Id и объявить внешний класс как тип идентификатора.
Этот класс, который должен быть Serializable, объявляется в сущности посредством аннотации
@IdClass. Тип идентификатора должен содержать те же свойства, что и свойства
идентификатора сущности: каждое имя свойства должно быть таким же, его тип должен быть таким же,
если свойство объекта имеет базовый тип, его тип должен быть типом первичного ключа связанной сущности,
если свойство entity является ассоциацией (либо @OneToOne, либо @ManyToOne).
Как вы можете видеть, последний случай далеко не очевиден. Он был унаследован
с "темных времен" EJB 2 для обратной совместимости, и мы рекомендуем вам
не использовать его (для поддержания простоты в коде).
Давайте рассмотрим все три случая, используя примеры.
5.1.2.1.1. id как свойство с использованием типа компонента
Вот простой пример @EmbeddedId.
@Entity
class User {
@EmbeddedId
@AttributeOverride(name="firstName", column=@Column(name="fld_firstname")
UserId id;
Integer age;
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
}
Вы можете заметить, что класс UserId является сериализуемым. Чтобы переопределить
отображение столбцов, используйте @AttributeOverride.
Встроенный идентификатор сам может содержать первичный ключ связанного объекта.
@Entity
class Customer {
@EmbeddedId CustomerId id;
boolean preferredCustomer;
@MapsId("userId")
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
@OneToOne User user;
}
@Embeddable
class CustomerId implements Serializable {
UserId userId;
String customerNumber;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
Во встроенном объекте id ассоциация представляется как идентификатор связанной сущности.
Но вы можете связать его значение с регулярной ассоциацией в сущности с помощью
аннотации @MapsId. Значение @MapsId соответствует имени свойства объекта
встроенного id, содержащего идентификатор связанной сущности. В базе данных это означает,
что свойства Customer.user и CustomerId.userId имеют один и тот же
базовый столбец (в этом случае user_fk).
Совет
Тип компонента, используемый как идентификатор, должен реализовывать equals()
и hashCode().
На практике ваш код устанавливает только свойство Customer.user, а значение идентификатора
пользователя копируется Hibernate в свойство CustomerId.userId.
Внимание
Значение id может быть скопировано во вермя сброса (flush),
не полагайтесь на него, пока не закончите сброс (flush).
Несмотря на то, что в JPA это не поддерживается, Hibernate позволяет разместить вашу связь непосредственно
во встроенном компоненте id (вместо того, чтобы использовать аннотацию @MapsId).
@Entity
class Customer {
@EmbeddedId CustomerId id;
boolean preferredCustomer;
}
@Embeddable
class CustomerId implements Serializable {
@OneToOne
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
String customerNumber;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
Теперь перепишем эти примеры, используя синтаксис hbm.xml.
Обратите внимание на несколько моментов в предыдущем примере:
порядок свойств (и столбца) имеет значение. Он должен быть одинаковым между ассоциацией
и первичным ключом связанной сущности
многие к одному используют те же столбцы, что и первичный ключ, и поэтому должны быть
отмечены как "только для чтения" (insertable и updatable к false).
в отличие от @MapsId, значение id связанной сущности копируется не прозрачно,
для получения дополнительной информации сверьтесь с генератором идентификатора foreign.
В последнем примере показано, как отобразить ассоциацию прямо во встроенный компонент id.
Это рекомендуемый подход к отображению составного идентификатора. Следующие параметры не следует
рассматривать если не существует какого-либо ограничения (constraint).
5.1.2.1.2. Несколько свойств id без идентификатора типа
Другой, возможно более естественный подход — разместить @Id по нескольким
свойствам вашей сущности. Этот подход поддерживается только Hibernate (не совместимым с JPA),
но не требует дополнительного встраиваемого компонента.
@Entity
class Customer implements Serializable {
@Id @OneToOne
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
@Id String customerNumber;
boolean preferredCustomer;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
В этом случае Customer является собственным представлением идентификатора:
он должен реализовывать Serializable и должен реализовывать equals()
и hashCode().
5.1.2.1.3. Несколько свойств id с выделенным идентификатора типа
@IdClass на сущности указывает на класс (компонент), представляющий идентификатор класса.
Свойства, отмеченные @Id на сущности, должны иметь соответствующее свойство
на @IdClass. Возвращаемый тип поиска свойства-близнеца должен быть либо идентичен для базовых
свойств, либо должен соответствовать классу идентификатора ассоциированной сущности для ассоциации.
Внимание
Этот подход наследуется со вермён EJB 2, и мы не рекомендуем его использовать.
Но, в конце концов, это ваше приложение, и Hibernate поддерживает его.
@Entity
@IdClass(CustomerId.class)
class Customer implements Serializable {
@Id @OneToOne
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
@Id String customerNumber;
boolean preferredCustomer;
}
class CustomerId implements Serializable {
UserId user;
String customerNumber;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
//implements equals and hashCode
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
Customer и CustomerId имеют те же свойства customerNumber,
что и user. CustomerId должен быть Serializable
и реализовать equals() и hashCode().
Не являясь стандартом JPA, Hibernate даваёт вам возможность объявить "ванильное" ассоциированное свойство
в @IdClass.
@Entity
@IdClass(CustomerId.class)
class Customer implements Serializable {
@Id @OneToOne
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
@Id String customerNumber;
boolean preferredCustomer;
}
class CustomerId implements Serializable {
@OneToOne User user;
String customerNumber;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
//implements equals and hashCode
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
}
Эта функция имеет ограниченный интерес. Вероятно вы выбрали подход с @IdClass,
чтобы поддержать совместимость с JPA или у вас довольно изощрённый ум.
Hibernate может автоматически генерировать и заполнять значения идентификатора.
Это рекомендуемый подход к «деловому»(business) или «естественному» (natural)
идентификатору (особенно составные идентификаторы).
Hibernate предлагает различные стратегии генерации, давайте рассмотрим наиболее распространенные,
которые будут стандартизированы JPA:
IDENTITY: поддерживает столбцы идентификации в DB2, MySQL, MS SQL Server, Sybase и HypersonicSQL.
Возвращаемый идентификатор имеет тип long, short или int.
SEQUENCE: (называемый seqhilo в Hibernate): использует алгоритм hi/lo для эффективной
генерации идентификаторов типа long, short или int, заданного
именованной последовательностью базы данных.
TABLE (в Hibernate назван MultipleHiLoPerTableGenerator): использует алгоритм hi/lo для
эффективной генерации идентификаторов типа long, short или int,
учитывая таблицу и столбец в качестве источника значений hi. Алгоритм hi/lo
генерирует идентификаторы, которые уникальны только для конкретной базы данных.
AUTO: выбирает IDENTITY, SEQUENCE или TABLE в зависимости
от возможностей базовой базы данных.
Важно
Мы рекомендуем для всех новых проектов использовать новые расширенные генераторы идентификаторов.
Они деактивируются по умолчанию для сущностей, использующих аннотации, но могут быть активированы
с помощью hibernate.id.new_generator_mappings=true. Эти новые генераторы более эффективны
и ближе к семантике спецификации JPA 2.
Однако они не имеют обратной совместимости с существующим приложением на базе Hibernate
(если для генерации идентификатора используется последовательность или таблица). См. XXXXXXX ???
для получения дополнительной информации о том, как их активировать.
Чтобы пометить свойство id как сгенерированное, используйте аннотацию @GeneratedValue.
Вы можете указать используемую стратегию (по умолчанию AUTO), установив стратегию.
@Entity
public class Customer {
@Id @GeneratedValue
Integer getId() { ... };
}
@Entity
public class Invoice {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
Integer getId() { ... };
}
SEQUENCE и TABLE требуют дополнительных конфигураций, которые вы можете
установить с помощью @SequenceGenerator и @TableGenerator:
name: имя генератора
table / sequenceName: имя таблицы или последовательности (по умолчанию
соответственно hibernate_sequences и hibernate_sequence)
catalog / schema:
initialValue: значение, с которого идентификатор должен начать генерировать
allocationSize: сумма для увеличения при выделении идентификационных номеров из генератора
Кроме того, стратегия TABLE также позволяет настроить:
pkColumnName: имя столбца, содержащего идентификатор сущности
valueColumnName: имя столбца, содержащего значение идентификатора
pkColumnValue: идентификатор сущности
uniqueConstraints: любое потенциальное ограничение столбца в таблице,
содержащей идентификаторы
Чтобы связать определение генератора таблицы или последовательности с фактическим сгенерированным свойством,
используйте одно и то же имя как в определении name, так и в значении
генератора generator, как показано ниже.
Область видимости определения генератора может быть приложением или классом. Определённые в классе генераторы
не видны вне класса и могут переопределять генераторы уровня приложения. Генераторы уровня приложений
определены в дескрипторах развертывания XML JPA:
Если для определения генераторов используется дескриптор JPA XML (например, META-INF/orm.xml),
EMP_GEN и SEQ_GEN являются генераторами уровня приложения.
Заметка
Определение уровня пакета не поддерживается спецификацией JPA. Однако вы можете использовать
@GenericGenerator на уровне пакета.
Это четыре стандартных генератора JPA. Hibernate выходит за рамки этого и предоставляет дополнительные
генераторы или дополнительные опции, как мы увидим ниже. Вы также можете написать собственный генератор
идентификаторов, имплементировав org.hibernate.id.IdentifierGenerator.
Чтобы определить собственный генератор, используйте аннотацию @GenericGenerator (и ее множественную
часть счетчика @GenericGenerators), которая описывает класс генератора идентификатора или его
короткое имя (как описано ниже) и список параметров ключ/значение. При использовании @GenericGenerator
и назначении его через @GeneratedValue.generator, функция @GeneratedValue.strategy
игнорируется: оставьте поле пустым.
В подходе hbm.xml используется дополнительный дочерний элемент <generator> внутри
<id>. Если для настройки или инициализации экземпляра генератора требуются какие-либо параметры,
они передаются с использованием элемента <param>.
Все генераторы реализуют интерфейс org.hibernate.id.IdentifierGenerator. Это очень простой интерфейс.
Некоторые приложения могут выбирать свои собственные специализированные реализации, однако Hibernate предоставляет
ряд встроенных реализаций. Короткие названия для встроенных генераторов следующие:
increment
генерирует идентификаторы типа long, short или int, которые являются
уникальными только тогда, когда другой процесс не вставляет данные в одну и ту же таблицу.
Не используйте в кластере.
identity
поддерживает столбцы идентификации в DB2, MySQL, MS SQL Server, Sybase и HypersonicSQL.
Возвращаемый идентификатор имеет тип long, short или int.
sequence
использует последовательность в DB2, PostgreSQL, Oracle, SAP DB, McKoi или генератор в Interbase.
Возвращаемый идентификатор имеет тип long, short или int.
hilo
использует алгоритм hi/lo для эффективной генерации идентификаторов типа long,
short или int, учитывая таблицу и столбец (по умолчанию
hibernate_unique_key и next_hi соответственно) в качестве источника
значений hi. Алгоритм hi/lo генерирует идентификаторы, которые уникальны только для конкретной
базы данных.
seqhilo
использует алгоритм hi/lo для эффективного генерации идентификаторов типа long,
short или int, заданного именованной последовательностью базы данных.
uuid
Генерирует 128-битный UUID на основе пользовательского алгоритма. Сгенерированное значение представляется
в виде строки из 32 шестнадцатеричных цифр. Пользователи также могут настроить его для
использования разделителя (параметр конфигурации «separator»), который разделяет шестнадцатеричные
цифры на 8{sep}8{sep}4{sep}8{sep}4. Обратите внимание, что это отличается от представления
IETF RFC 4122 8-4-4-4-12. Если вам нужны совместимые с RFC 4122 UUID, рассмотрите возможность
использования генератора «uuid2», рассмотренного ниже.
uuid2
Генерирует IETF RFC 4122 совместимый (вариант 2) 128-битный UUID. Точная «версия» (version)
(термин RFC) зависит от используемой «стратегии генерации» (см. Ниже). Способна генерировать
значения как java.util.UUID, java.lang.String или как байтовый массив длиной 16
(byte[16]). «Стратегия генерации» определяется интерфейсом
org.hibernate.id.UUIDGenerationStrategy. Генератор определяет 2 конфигурационных параметра
для определения того, какую стратегию генерации использовать:
uuid_gen_strategy_class
Именовать класс UUIDGenerationStrategy для использования
uuid_gen_strategy
Именовать экземпляр UUIDGenerationStrategy для использования
"Из коробки" поставляются следующие стратегии:
org.hibernate.id.uuid.StandardRandomStrategy (по умолчанию) - генерирует
«version 3» (он же «random») через метод randomUUID
для java.util.UUID
org.hibernate.id.uuid.CustomVersionOneStrategy — генерирует
«version 1» значения UUID, используя IP-адрес, так как адрес mac не доступен.
Если вам нужен MAC-адрес, который следует использовать, подумайте о том, чтобы использовать
один из существующих генераторов UUID третьих сторон, которые вынюхивают адрес mac
и интегрируют его через контракт org.hibernate.id.UUIDGenerationStrategy.
Две такие библиотеки, известные на момент написания этой статьи, включают
https://johannburkard.de/software/uuid/
и http://commons.apache.org/sandbox/commons-id/uuid.html
guid
использует строку GUID, сгенерированную базой данных на MS SQL Server и MySQL.
native
выбирает идентификатор, последовательность или hilo в зависимости от возможностей
используемой базы данных.
assigned
позволяет приложению назначать идентификатор объекту перед вызовом save(). Это стратегия
по умолчанию, если не указан элемент <generator>.
select
извлекает первичный ключ, назначенный триггером базы данных, путем выбора строки с помощью некоторого
уникального ключа и получения значения первичного ключа.
foreign
использует идентификатор другого связанного объекта. Он обычно используется в сочетании
с ассоциацией первичных ключей <one-to-one>.
sequence-identity
специализированная стратегия генерации последовательности, которая использует последовательность базы данных
для генерации фактического значения, но объединяет ее с JDBC3 getGeneratedKeys,
чтобы вернуть сгенерированное значение идентификатора как часть выполнения инструкции insert.
Эта стратегия поддерживается только для драйверов Oracle 10g, ориентированных на JDK 1.4.
Комментарии к этим операторам вставки отключены из-за ошибки в драйверах Oracle.
5.1.2.2.2. Алгоритм Hi/lo
Генераторы hilo и seqhilo предоставляют две альтернативные реализации алгоритма
hi/lo. Для первой реализации требуется «специальная» таблица базы данных для хранения
следующего доступного значения «hi». Где поддерживается, второй использует последовательность
в стиле Oracle.
К сожалению, вы не можете использовать hilo при поставке своего собственного
Connectionв Hibernate. Когда Hibernate использует источник данных (datasource) сервера приложений
для получения подключений, работающих с JTA, вы должны настроить
hibernate.transaction.manager_lookup_class.
5.1.2.2.3. Алгоритм UUID
UUID содержит: IP-адрес, время запуска JVM с точностью до четверти секунды, системное время
и значение счетчика, которое уникально в JVM. Невозможно получить MAC-адрес или адрес памяти
из кода Java, поэтому это лучший вариант без использования JNI.
5.1.2.2.4. Столбцы и последовательности идентификаторов
Для баз данных, которые поддерживают столбцы идентификации (DB2, MySQL, Sybase, MS SQL), вы можете
использовать генерацию ключей identity. Для баз данных, поддерживающих последовательности
(DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB), вы можете использовать генерацию ключей
в стиле sequence. Обе эти стратегии требуют двух SQL-запросов для вставки нового объекта.
Например:
Для кросс-платформенной разработки собственная стратегия будет, в зависимости от возможностей
используемой базы данных, выбирать из стратегий identity, sequence
и hilo.
5.1.2.2.5. Назначенные (assigned) идентификаторы
Если вы хотите, чтобы приложение назначало идентификаторы, вместо генерации их Hibernate,
вы можете использовать назначенный (assigned) генератор. Этот специальный генератор использует значение
идентификатора, уже присвоенное свойству идентификатора объекта. Генератор используется, когда первичный ключ
является естественным (natural) ключом вместо суррогатного ключа. Это поведение по умолчанию,
если вы не укажете элементы @GeneratedValue и <generator>.
Назначенный (assigned) генератор побуждает Hibernate использовать
unsaved-value=«undefined». Это заставляет Hibernate перейти в базу данных,
чтобы определить, является ли экземпляр временным (transient) или отсоединенным (detached), если нет
свойства версии или времени или вы определяете Interceptor.isUnsaved().
В приведенном выше примере существует уникальное ценное свойство с именем
socialSecurityNumber. Оно определяется классом как естественный ключ и суррогатный ключ
с именем person_id, значение которого генерируется триггером.
Наконец, вы можете попросить Hibernate скопировать идентификатор из другого ассоциированной сущности.
На жаргоне Hibernate он известен как посторонний (foreign) генератор, но отображение JPA лучше
читается и поощряется.
@Entity
class MedicalHistory implements Serializable {
@Id @OneToOne
@JoinColumn(name = "person_id")
Person patient;
}
@Entity
public class Person implements Serializable {
@Id @GeneratedValue Integer id;
}
Или по-другому
@Entity
class MedicalHistory implements Serializable {
@Id Integer id;
@MapsId @OneToOne
@JoinColumn(name = "patient_id")
Person patient;
}
@Entity
class Person {
@Id @GeneratedValue Integer id;
}
Начиная с версии 3.2.3, есть 2 новых генератора, которые представляют собой переосмысление двух разных
аспектов генерации идентификатора. Первый аспект — переносимость базы данных;
вторая - оптимизация "Оптимизация" означает, что вам не нужно запрашивать базу данных для каждого запроса
для нового значения идентификатора. Эти два новых генератора предназначены для замены некоторых из названных
генераторов, описанных выше, начиная с 3.3.x. Однако они включены в текущие выпуски и могут
быть указаны FQN.
Первым из этих новых генераторов является org.hibernate.id.enhanced.SequenceStyleGenerator,
который предназначен, во-первых, в качестве замены генератора sequence и, во-вторых,
как лучший чем native генератор по переносимости. Это связано с тем, что native
обычно выбирает между identity и sequence, которые имеют в значительной
степени различную семантику, которые могут вызвать тонкие проблемы в приложениях, рассматривающих
переносимость. org.hibernate.id.enhanced.SequenceStyleGenerator, однако, обеспечивает переносимость
по-другому. Он выбирает между table или sequence в базе данных, чтобы хранить
ее увеличивающиеся значения, в зависимости от возможностей используемого диалекта.
Разница между этим и native заключается в том, что хранилище на основе таблиц
и последовательностей имеет одинаковую точную семантику. Фактически, последовательности —
это именно то, что Hibernate пытается эмулировать с помощью своих генераторов на основе таблиц.
Этот генератор имеет несколько параметров конфигурации:
sequence_name (необязательный. По умолчанию — hibernate_sequence):
имя последовательности или таблицы, которые будут использоваться.
initial_value (необязательный. По умолчанию — 1): начальное значение,
которое нужно извлечь из последовательности/таблицы. В терминах создания последовательности
это аналогично предложению, обычно называемому «STARTS WITH».
increment_size (необязательный. По умолчанию — 1): значение, по которому
последующие вызовы в последовательности/таблице должны отличаться. В терминах создания
последовательности это аналогично предложению, обычно называемому «INCREMENT BY».
force_table_use (необязательный. По умолчанию — false): следует ли принудительно
использовать таблицу в качестве базовой структуры, даже если диалект может поддерживать
последовательность?
value_column (необязательный. по умолчанию — next_val): только для табличных
структур, это имя столбца таблицы, которое используется для хранения значения.
prefer_sequence_per_entity (необязательный. По умолчанию — false):
следует ли создать отдельную последовательность для каждого сущности, которая использует текущий
генератор на основе его имени?
sequence_per_entity_suffix (необязательный. По умолчанию — _SEQ):
суффикс добавлен к имени выделенной последовательности.
Второй из этих новых генераторов — org.hibernate.id.enhanced.TableGenerator,
который предназначен, во-первых, в качестве замены генератора table, хотя он фактически
по функционалу гораздо больше похож на org.hibernate.id.MultipleHiLoPerTableGenerator,
а во-вторых, как повторная реализация org.hibernate.id.MultipleHiLoPerTableGenerator,
который использует понятие подключаемых оптимизаторов. По существу этот генератор определяет таблицу,
способную одновременно удерживать несколько различных значений приращения, используя несколько четко
определенных строк. Этот генератор имеет несколько параметров конфигурации:
table_name (необязательный. По умолчанию — hibernate_sequences):
имя используемой таблицы.
value_column_name (необязательный. По умолчанию — next_val):
имя столбца таблицы, которое используется для хранения значения.
segment_column_name (необязательный — По умолчанию — sequence_name):
имя столбца таблицы, которое используется для хранения «segment key». Это значение,
которое определяет, какое значение приращения использовать.
segment_value (необязательный. — По умолчанию — default):
значение «segment key» для сегмента, из которого мы хотим вывести значения приращения
для этого генератора.
segment_value_length (необязательный. По умолчанию — 255):
используется для генерации схемы; размер столбца для создания этого столбца segment key.
initial_value (необязательный. По умолчанию — 1): начальное значение,
которое нужно извлечь из таблицы.
increment_size (необязательный. По умолчанию — 1): значение, по которому
последующие вызовы в таблице должны отличаться.
Для генераторов идентификаторов, которые хранят значения в базе данных, для них неэффективно "дёргать" (hit)
базу данных по каждому вызову для генерации нового значения идентификатора. Вместо этого вы можете
сгруппировать из них пачку в памяти и "дёргать" базу данных только тогда,
когда вы исчерпали свою пачку значений в памяти. Это роль подключаемых оптимизаторов.
В настоящее время есть только два усовершенствованных генератора (см. раздел
5.1.2.3, «Усовершенствованные генераторы идентификаторов»).
none (обычно это значение по умолчанию, если оптимизатор не указан):
не будет выполнять никаких оптимизаций и "дёргает" базу данных для каждого запроса.
hilo: применяет алгоритм hi/lo к значениям, полученным из базы данных.
Ожидается, что значения из базы данных для этого оптимизатора будут последовательными.
Значения, полученные из структуры базы данных для этого оптимизатора, указывают
на «номер группы». increment_size умножается на это значение
в памяти для определения группы «hi value».
pooled: как и в случае с hilo, этот оптимизатор пытается
минимизировать количество обращений к базе данных. Здесь, однако, мы просто сохраняем начальное
значение для «следующей группы» в структуре базы данных, а не последовательное
значение в сочетании с алгоритмом группировки в памяти. Здесь increment_size
ссылается на значения, поступающие из базы данных.
5.1.2.4. Генерация частичного идентификатора
Hibernate поддерживает автоматическую генерацию некоторых свойств идентификатора. Просто используйте аннотацию
@GeneratedValue для одного или нескольких свойств id.
Внимание
Команда Hibernate всегда считала такую конструкцию принципиально неправильной.
Постарайтесь исправить вашу модель данных перед использованием этой функции.
Вы также можете создавать свойства внутри класса @EmbeddedId.
5.1.3. Свойства оптимистической блокировки (необязательно)
При использовании длинных транзакций или «разговоров» (conversations), которые охватывают несколько транзакций базы
данных, полезно хранить данные о версиях, чтобы гарантировать, что если одна и та же сущность будет
обновлена двумя разговорами, последний зафиксируя (commit) изменения, будет проинформирован и не будет
переопределять работу другого разговора. Это гарантирует некоторую изоляцию, сохраняя при этом хорошую
масштабируемость и особенно хорошо работает в ситуациях частого чтения и редкой записи.
Вы можете использовать два подхода: выделенный номер версии или метку времени (timestamp).
Свойство version или timestamp никогда не должно быть null для отделённого экземпляра. Hibernate обнаружит
любой экземпляр с нулевой версией или меткой времени как переходный (transient), независимо
от того, какие другие стратегии unsaved-value указаны.
Объявление свойства с null`евой версией или меткой времени — это простой способ избежать проблем
с переходным переприсоединением в Hibernate. Это особенно полезно для людей, использующих назначенные
идентификаторы или составные ключи.
5.1.3.1. Номер версии
Вы можете добавить возможность оптимистической блокировки для сущность, используя аннотацию
@Version:
@Entity
public class Flight implements Serializable {
...
@Version
@Column(name="OPTLOCK")
public Integer getVersion() { ... }
}
Свойство версии будет отображено на столбец OPTLOCK, и диспетчер сущностей будет использовать
его для обнаружения конфликтующих обновлений (предотвращая потерю обновлений, которые вы, в противном случае,
могли бы увидеть с помощью стратегии last-commit-wins).
Столбец версии может быть числовым. Hibernate поддерживает любой тип, если вы определяете и реализуете
с соответствующим UserVersionType.
Приложение не должно изменять номер версии, установленной Hibernate. Чтобы искусственно увеличить номер версии,
загляните в справочную документацию Hibernate Entity Manager
LockModeType.OPTIMISTIC_FORCE_INCREMENT или LockModeType.PESSIMISTIC_FORCE_INCREMENT.
Если номер версии генерируется базой данных (например, с помощью триггера), обязательно используйте
@org.hibernate.annotations.Generated(GenerationTime.ALWAYS).
Чтобы объявить свойство version в файле hbm.xml, используйте:
(необязательный. по умолчанию имя совйства): имя столбца, содержащего номер версии.
type
(необязательный. по умолчанию integer): тип номера версии.
access
(необязательный. по умолчанию property): стратегия, используемая Hibernate
для доступа к значению свойства.
unsaved-value
(необязательный. по умолчанию «undefined»): значение свойства версии, указывающее,
что вновь созданный (несохранённый) экземпляр, отличается от отсоединённых экземпляров,
которые были сохранены или загружены в предыдущей сессии. Undefined указывает,
что значение свойства идентификатора должно использоваться.
generated
(необязательный. по умолчанию never): указывает, что это значение свойства версии
генерируется базой данных. Дополнительную информацию см. в обсуждении
сгенерированных свойств.
insert
(необязательный. по умолчанию true): указывает, должен ли быть включён столбец
версии в SQL инструкцию вставки. Его можно установить в значение false,
если столбец базы данных задан со значением по умолчанию 0.
5.1.3.2. Timestamp (метка времени)
Кроме того, вы можете использовать метку времени. Метки времени — это менее безопасная реализация
оптимистической блокировки. Однако иногда приложение также может использовать метки времени другими способами.
Просто отметьте свойство типа Date или Calendar аннотацией @Version.
@Entity
public class Flight implements Serializable {
...
@Version
public Date getLastUpdate() { ... }
}
При использовании timestamp для управления версиями вы можете указать Hibernate, где можно получить значение
метки времени из базы данных или JVM, опционально добавив при этом аннотацию
@org.hibernate.annotations.Source к свойству. Возможными значениями атрибута value аннотации
являются org.hibernate.annotations.SourceType.VM
и org.hibernate.annotations.SourceType.DB. По умолчанию используется
SourceType.DB, который также используется в случае отсутствия аннотации @Source.
Как и в случае номеров версий, метки времени также может быть сгенерирована базой данных вместо Hibernate.
Для этого используйте @org.hibernate.annotations.Generated(GenerationTime.ALWAYS).
(необязательный. по умолчанию имя совйства): имя столбца, содержащего метку времени.
name
имя свойства в стиле JavaBeans типа Java Date или Timestamp постоянного
класса.
access
(необязательный. по умолчанию property): стратегия, используемая Hibernate
для доступа к значению свойства.
unsaved-value
(необязательный. по умолчанию «null»): значение свойства версии, указывающее,
что вновь созданный (несохранённый) экземпляр, отличается от отсоединённых экземпляров,
которые были сохранены или загружены в предыдущей сессии. Undefined указывает,
что значение свойства идентификатора должно использоваться.
source
(необязательный. по умолчанию vm): Где Hibernate должен извлекает значение метки времени?
Из базы данных или из текущей JVM? Метки времени, основанные на базе данных, несут
накладные расходы, потому что Hibernate должен попасть в базу данных, чтобы определить
«следующее значение». Это безопаснее использовать в кластерных средах. Известно,
что не все Dialects поддерживают получение текущей метки времени из базы данных.
Другие могут также быть небезопасными для использования при блокировке из-за отсутствия точности
(например, Oracle 8).
generated
(необязательный. по умолчанию never): указывает, что это значение свойства версии
фактически генерируется базой данных. Дополнительную информацию см. в обсуждении
сгенерированных свойств.
Заметка
<Timestamp> это эквивалент <version type="timestamp">. А <timestamp source="db"> эквивалент
<version type="dbtimestamp">.
5.1.4. Свойство
Вам нужно решить, какое свойство необходимо сделать постоянным в данной сущности. Это немного отличается между
метаданными, управляемыми аннотациями, и файлами hbm.xml.
5.1.4.1. Отображение свойств с аннотациями
В мире аннотаций каждое нестатической непереходное свойство (поле или метод в зависимости от типа
доступа) сущности считается постоянным, если вы не аннотируете его как @Transient.
Отсутствие аннотации для вашего свойства эквивалентно соответствующей аннотации @Basic.
JPA поддерживает отображение свойств всех базовых типов, поддерживаемых Hibernate (все базовые типы Java,
их соответствующие оболочки и сериализуемые классы). Hibernate Annotations «из коробки»
поддерживают отображение типа enum либо на порядковый столбец (сохранение порядкового номера enum),
либо на строкоый столбец (сохранение строкового представления enum): представление постоянства,
по умолчанию «порядковый» (ordinal), может быть переопределено через @Enumerated
аннотацию как показано в примере свойства note.
В простых Java-API временная точность времени не определена. При работе с временными данными
вы можете описать ожидаемую точность в базе данных. Временные данные могут иметь точность
DATE, TIME или TIMESTAMP (т.е. фактическую дату, только время или оба).
Используйте аннотацию @Temporal для точной настройки.
@Lob указывает, что свойство должно сохраняться в Blob или Clob в зависимости от типа
свойства: java.sql.Clob, Character[], char[]
и java.lang.String будет сохраняться в Clob. java.sql.Blob, Byte[],
byte[] и Serializable будут сохраняться в Blob.
@Lob
public String getFullText() {
return fullText;
}
@Lob
public byte[] getFullCode() {
return fullCode;
}
Если тип свойства реализует java.io.Serializable и не является базовым типом, и если
свойство не аннотируется с помощью @Lob то используется тип serializable
Hibernate.
5.1.4.1.1. Тип
Вы также можете вручную указать тип, используя @org.hibernate.annotations.Type и некоторые
параметры, если это необходимо. @Type.type может быть:
Имя базового типа Hibernate:
integer, string, character, date, timestamp, float, binary, serializable, object, blob
и т.д.
Имя класса Java с базовым типом по умолчанию:
int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob и т.д.
Имя сериализуемого Java-класса.
Имя класса настраиваемого типа: com.illflow.type.MyCustomType и т.д.
Если вы не укажете тип, Hibernate будет использовать отражение для свойства именованного
и предполагать правильный тип Hibernate. Hibernate попытается интерпретировать имя возвращаемого класса
свойства, используя getter в порядке, правила 2, 3 и 4.
@org.hibernate.annotations.TypeDef и @org.hibernate.annotations.TypeDefs позволяет
объявлять определения типов. Эти аннотации могут быть размещены на уровне класса или пакета. Обратите
внимание, что эти определения являются глобальными для фабрики сессии (даже если они определены на уровне
класса). Если тип используется для единственной сущности, вы можете поместить определение в сущность.
В противном случае рекомендуется установить определение на уровне пакета. В приведенном ниже примере,
когда Hibernate встречает свойство класса PhoneNumer, он делегирует стратегию постоянства
на пользовательский тип отображения PhoneNumberType. Однако свойства, принадлежащие другим
классам, тоже могут делегировать свои стратегии постоянства в PhoneNumberType, явно используя
аннотацию @Type.
Заметка
Аннотации уровня пакета размещаются в файле с именем package-info.java
в соответствующем пакете. Поместите свои аннотации перед объявлением пакета.
@TypeDef(
name = "phoneNumber",
defaultForType = PhoneNumber.class,
typeClass = PhoneNumberType.class
)
@Entity
public class ContactDetails {
[...]
private PhoneNumber localPhoneNumber;
@Type(type="phoneNumber")
private OverseasPhoneNumber overseasPhoneNumber;
[...]
}
В следующем примере показано использование атрибута parameters для настройки TypeDef.
При использовании составного пользовательского типа вам придется выражать определения столбцов. Для этой цели были
введены @Columns.
@Type(type="org.hibernate.test.annotations.entity.MonetaryAmountUserType")
@Columns(columns = {
@Column(name="r_amount"),
@Column(name="r_currency")
})
public MonetaryAmount getAmount() {
return amount;
}
public class MonetaryAmount implements Serializable {
private BigDecimal amount;
private Currency currency;
...
}
5.1.4.1.2. Тип доступа
По умолчанию тип доступа иерархии классов определяется положением аннотаций @Id или
@EmbeddedId. Если эти аннотации находятся на поле, тогда только поля рассматриваются
для сохранения и доступ к состоянию осуществляется через поле. Если эти аннотации находятся
на геттере, тогда только геттеры рассматриваются для сохранения и доступ к состоянию осуществляется
через геттер/сеттер. Это хорошо работает на практике и является рекомендуемым подходом.
Заметка
Размещение аннотаций в иерархии классов должно быть согласованным (по полям или по свойствам),
чтобы иметь возможность определять тип доступа по умолчанию. Рекомендуется придерживаться одной стратегии
размещения аннотаций на протяжении всего вашего приложения.
Однако в некоторых ситуациях вам необходимо:
принудительно ввести тип доступа иерархии сущностей
переопределить тип доступа определённой сущности в иерархии классов
переопределить тип доступа встраиваемого типа
Наилучший вариант использования — это внедряемый класс, используемый несколькими сущностями, которые
могут не использовать один и тот же тип доступа. В этом случае лучше ввести тип доступа
на уровне встраиваемого класса.
Чтобы принудительно ввести тип доступа в данном классе, используйте аннотацию @Access,
как показано ниже:
@Entity
public class Order {
@Id private Long id;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
@Embedded private Address address;
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
}
@Entity
public class User {
private Long id;
@Id public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
private Address address;
@Embedded public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
}
@Embeddable
@Access(AcessType.PROPERTY)
public class Address {
private String street1;
public String getStreet1() { return street1; }
public void setStreet1(String street1) { this.street1 = street1; }
private hashCode; //не постоянный(not persistent)
}
Вы также можете переопределить тип доступа для одного свойства, сохраняя при этом другие свойства.
@Entity
public class Order {
@Id private Long id;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
@Transient private String userId;
@Transient private String orderId;
@Access(AccessType.PROPERTY)
public String getOrderNumber() { return userId + ":" + orderId; }
public void setOrderNumber(String userId, String orderId) { this.userId = userId; this.orderId = orderId; }
}
В этом примере тип доступа по умолчанию — FIELD, за исключением свойства
orderNumber. Обратите внимание на соответствующее поле, если оно должно быть отмечено как
@Transient или переходное.
@org.hibernate.annotations.AccessType
Аннотации @org.hibernate.annotations.AccessType следует считать устаревшими для доступа
FIELD и PROPERTY. Однако они всё ещё полезны если вам нужно использовать
настраиваемый тип доступа.
5.1.4.1.3. Оптимистическая блокировка
Иногда бывает полезно избежать увеличения номера версии, даже если данное свойство - грязное (в частности
коллекции). Вы можете сделать это, аннотируя свойство (или коллекцию) с помощью
@OptimisticLock(excluded = true).
5.1.4.1.4. Объявление атрибутов столбца
Столбец (столбцы), используемые для отображения сущностей, могут быть определены с помощью аннотации
@Column. Используйте её для переопределения значений по умолчанию (дополнительную
информацию о значениях по умолчанию см. в спецификации JPA). Вы можете использовать
эту аннотацию на уровне свойства для свойств, которые:
не аннотированны вообще
аннотированны @Basic
аннотированны @Version
аннотированны @Lob
аннотированны @Temporal
@Entity
public class Flight implements Serializable {
...
@Column(updatable = false, name = "flight_name", nullable = false, length=50)
public String getName() { ... }
Свойство name отображается на столбец flight_name, который не имеет не NULL,
имеет длину 50 и не обновляется (что делает свойство неизменяемым).
Эта аннотация может применяться к регулярным свойствам, а также к свойствам @Id
или @Version.
@Column(
name="columnName";
boolean unique() по умолчанию false;
boolean nullable() по умолчанию true;
boolean insertable() по умолчанию true;
boolean updatable() по умолчанию true;
String columnDefinition() по умолчанию "";
String table() по умолчанию "";
int length() по умолчанию 255;
int precision() по умолчанию 0; // десятичная точность
int scale() по умолчанию 0; // десятичный масштаб
name
(необязательный. по умолчанию имя свойства): имя столбца.
unique
(необязательный. по умолчанию false): установливать или нет уникальное ограничение
для этого столбца
nullable
(необязательный. по умолчанию true): установливать столбец как обнуляемый (nullable)
insertable
(необязательный. по умолчанию true): будет ли столбец частью инструкции insert
updatable
(необязательный. по умолчанию true): будет ли столбец частью инструкции update
columnDefinition
(необязательный): переопределить фрагмент DDL sql для этого конкретного столбца
(не переносимый)
table
(необязательный): определить целевую таблицу (по умолчанию основная таблица)
length
(необязательный): длина столбца (по умолчанию 255)
precision
(необязательный): десятичная точность столбца (по умолчанию 0)
scale
(необязательный): десятичный масштаб столбца (если используется) (по умолчанию 0)
5.1.4.1.5. Формула
Иногда вы хотите, чтобы база данных выполняла некоторые вычисления для вас, а не JVM,
вы также можете создать какой-то виртуальный столбец. Вы можете использовать фрагмент SQL
(она же формула) вместо отображения свойства на столбец. Этот вид свойства доступен только
для чтения (его значение рассчитывается по фрагменту формулы).
@Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()
Фрагмент SQL может быть любыи, и даже включать подзапросы.
5.1.4.1.6. Не аннотированные значения свойств по умолчанию
Если свойство не аннотируется, применяются следующие правила:
Если свойство имеет один тип, оно отображается как @Basic
В противном случае, если тип свойства аннотируется как @Embeddable, оно отображается
как @Embedded
В противном случае, если тип свойства Serializable, оно отображается
как @Basic в столбце, содержащем объект в его сериализованной версии
В противном случае, если тип свойства java.sql.Clob или java.sql.Blob,
оно отображается как @Lob с соответствующим LobType
5.1.4.2. Отображение свойств с hbm.xml
Элемент <property> объявляет постоянное свойство класса в стиле JavaBean.
(необязательный. по умолчанию имя свойства): имя отображённого столбца таблицы базы данных.
Его также можно указать вложенными элементами <column>.
type
(необязательный): имя, которое указывает тип Hibernate.
update, insert
(необязательный. по умолчанию true): указывает, что отображённы столбцы должны
быть включены в инструкции SQL UPDATE и/или INSERT. Установка обоих
в false указывает на чистое«производное» свойство,
значение которого инициализируется из некоторого другого свойства, которое отображается
на тот же столбец (столбцы) или с помощью триггера или другого приложения.
formula
(необязательный): выражение SQL, которое определяет значение для вычисленного свойства.
Вычислимые свойства не имеют собственного отображения на столбцы.
access
(необязательный. по умолчанию property): стратегия Hibernate, используемая
для доступа к значению свойства
lazy
(необязательный. по умолчанию false): указывает, что выборка этого свойства должна
быть ленивой, когда произойдёт первое обращение к переменная экземпляра. Для этого требуется
инструментарий байт-кода времени сборки.
not-null
(необязательный): позволяет генерировать DDL null-ограничения столбцов.
optimistic-lock
(необязательный. по умолчанию true): указывает, что обновления этого свойства
требуют или не требуют приобретения оптимистичной блокировки. Другими словами,
оно определяет, будет ли приращение версии происходить, когда это свойство
«загрязнено».
generated
(необязательный. по умолчанию never): указывает, что это значение свойства
фактически генерируется базой данных. Дополнительную информацию см. в обсуждении
сгенерированных свойств.
typename может быть:
именем простого типа Hibernate:
integer, string, character, date, timestamp, float, binary, serializable, object, blob и т.п.
именем класса Java с простым типом по умолчанию:
int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob и т.п.
именем сериализуемого Java-класса.
Имя класса настраиваемого типа: com.illflow.type.MyCustomType и т. п.
Если вы не укажете тип, Hibernate будет использовать отражение для именованного свойства
и попытаетя угадать правильный тип Hibernate. Hibernate попытается интерпретировать имя возвращаемого класса
свойства использовав getter в порядке, правила 2, 3 и 4. В некоторых случаях вам понадобится
атрибут type. Например, чтобы различать Hibernate.DATE
и Hibernate.TIMESTAMP, или для указания настраиваемого типа.
Атрибут access позволяет вам контролировать, как Hibernate обращается к свойству во время
выполнения. По умолчанию Hibernate вызовет для свойства пару get/set. Если вы укажете
access="field", Hibernate пропустит пару get/set и напрямую обратится к полю
с помощью отражения. Вы можете указать собственную стратегию доступа к свойству, назвав класс,
который реализует интерфейс org.hibernate.property.access.spi.PropertyAccessStrategy.
Мощная особенность — это производные свойства. Эти свойства по определению доступны только
для чтения. Значение свойства вычисляется во время загрузки. Вы объявляете вычисление
как выражение SQL. Затем переводит в подзапрос секцию SELECT в запросе SQL,
который загружает экземпляр:
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>
Вы можете ссылаться на таблицу сущности, не объявляя псевдоним определённого столбца.
В данном примере это будет customerId. Вы также можете использовать вложенный элемент
отображения <formula>, если вы не хотите использовать этот атрибут.
5.1.5. Встроенные объекты (они же компоненты)
Встраиваемые объекты (или компоненты) являются объектами, свойства которых отображаются на ту же
таблицу, что и таблица владельца сущности. Компоненты могут, в свою очередь, объявлять свои собственные
свойства, компоненты или коллекции.
Можно объявлять встроенный компонент внутри сущности и даже переопределять его отображение на столбцы.
Классы компонентов должны быть аннотированы на уровне класса с помощью аннотации
@Embeddable. Можно переопределять отображения столбцов встроенного объекта для конкретного
объекта с использованием аннотации @Embedded и @AttributeOverride
в связанном свойстве:
@Entity
public class Person implements Serializable {
// Постоянный компонент, использующий значения по умолчанию
Address homeAddress;
@Embedded
@AttributeOverrides( {
@AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
@AttributeOverride(name="name", column = @Column(name="bornCountryName") )
} )
Country bornIn;
...
}
@Embeddable
public class Country implements Serializable {
private String iso2;
@Column(name="countryName") private String name;
public String getIso2() { return iso2; }
public void setIso2(String iso2) { this.iso2 = iso2; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
...
}
Встраиваемый объект наследует тип доступа его собственной сущности (обратите внимание, что вы можете
переопределить его с помощью аннотации @Access).
Объект Person имеет два свойства компонента: homeAddress и bornIn.
Свойство homeAddress не было аннотировано, но Hibernate предположит, что оно является
постоянным (persistent) компонентом, ища аннотацию @Embeddable в классе Address.
Мы также переопределяем отображение имени столбца (на bornCountryName)
аннотациями @Embedded и @AttributeOverride для каждого отображаемого атрибута
Country. Как вы можете видеть, Country также является вложенным компонентом
Address, Hibernate по умолчанию снова использует автоматическое обнаружение и JPA.
Переопределение столбцов встроенных объектов встроенных объектов осуществляется через точечные выражения.
Аннотации Hibernate поддерживают то, что явно не поддерживается спецификацией JPA.
Вы можете аннотировать встроенный объект аннотацией @MappedSuperclass, чтобы сделать свойства
суперкласса постоянными (более подробную информацию см. в @MappedSuperclass).
Вы также можете использовать связывающие аннотации во встраиваемом объекте (то есть
@OneToOne, @ManyToOne, @OneToMany или @ManyToMany).
Чтобы переопределить столбцы ассоциации, вы можете использовать @AssociationOverride.
Если вы хотите иметь один и тот же тип встраиваемого объекта дважды в одной и той же
сущности, то имя столбца по умолчанию не будет работать, поскольку несколько встроенных объектов
будут разделяить один и тот же набор столбцов. В чистом JPA вам необходимо переопределить
хотя бы один набор столбцов. Однако Hibernate позволяет вам улучшить механизм именования по умолчанию
через интерфейс NamingStrategy. Вы можете написать стратегию, которая предотвратит совпадение
имен в такой ситуации. Примером этого является DefaultComponentSafeNamingStrategy.
Если свойство встроенного объекта указывает на объект-владелец, аннотируйте его аннотацией
@Parent. Hibernate убедиться, что это свойство правильно загружено по ссылке на сущность.
(необязательный. по умолчанию тип свойства определяется отражением): стратегия Hibernate,
используемая для доступа к значению свойства
insert
встретятся ли отображаемые столбцы в SQL update?
update
встретятся ли отображаемые столбцы в SQL UPDATE?
access
(необязательный. по умолчанию property): стратегия Hibernate, используемая
для доступа к значению свойства
lazy
(необязательный. по умолчанию false): указывает, что выборка этого компонента должена
быть ленивой, когда произойдёт первое обращение к экземпляру. Для этого требуется инструментарий
байт-кода времени сборки.
optimistic-lock
(необязательный. по умолчанию true): указывает, что обновления этого компонента
либо требуют, либо не требуют приобретения оптимистичной блокировки. Он определяет,
должно ли приращение версии происходить, когда это свойство «загрязнено».
unique
(необязательный. по умолчанию false): указывает, что существует ограничение
на уникальность для всех отображённых столбцов компонента.
Вложенные теги <property> отображают свойства дочернего класса на столбцы таблицы.
Элемент <component> позволяет подэлементу <parent> отобразить свойство класса компонента
в качестве обратной ссылки на содержащий объект.
Элемент <dynamic-component> позволяет отображать Map как компонент, где имена
свойств ссылаются на ключи карты. Дополнительную информацию см.
в разделе 9.5 «Динамические компоненты». Этот функционал не поддерживается в аннотациях.
5.1.6. Стратегия наследования
Java — это язык, поддерживающий полиморфизм: класс может наследовать от другого.
Существует несколько стратегий для сохранения иерархии классов:
Стратегия одной таблицы на иерархию классов: одна таблица содержит все экземпляры иерархии классов.
Присоединительная стратегия подкласса: одна таблица для каждого класса и подкласса,
и каждая таблица сохраняет свойства, специфичные для данного подкласса. Состояние сущности затем
сохраняется в соответствующей таблице класса и всех его суперклассов.
Стратегия таблица на класс: одна таблица для конкретного класса и подкласса,
и каждая таблица сохраняет свойства класса и его суперклассов. Состояние сущности затем полностью
сохраняется в выделенной таблице для своего класса.
5.1.6.1. Стратегия одной таблицы на иерархию классов
При таком подходе свойства всех подклассов в заданной иерархии классов отображены на одну таблицу.
Каждый подкласс объявляет свои собственные постоянные свойства и подклассы. Предполагается, что свойства
версии и id наследуются от корневого класса. Каждый подкласс в иерархии должен определять
уникальное значение дискриминатора. Если это не указано, используется полное квалифицированное имя класса Java.
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="planetype",
discriminatorType=DiscriminatorType.STRING
)
@DiscriminatorValue("Plane")
public class Plane { ... }
@Entity
@DiscriminatorValue("A320")
public class A320 extends Plane { ... }
В hbm.xml для стратегии отображения "Одна таблицы на иерархию классов" используется декларация
<subclass>. Например:
Дискриминаторы требуются для полиморфного постоянства, используя стратегию отображения "Одна таблицы
на иерархию классов". Он объявляет столбец дискриминатора таблицы. Столбец дискриминатора содержит
значения маркера, которые указывают слою постоянства, какой подкласс должен быть создан для конкретной
строки. Hibernate Core поддерживает следующий ограниченный набор типов в качестве столбца дискриминатора:
string, character, integer, byte, short, boolean, yes_no, true_false.
Используйте @DiscriminatorColumn для определения столбца дискриминатора, а также типа
дискриминатора.
Заметка
Тип перечисление DiscriminatorTyp, используемое
в javax.persitence.DiscriminatorColumn содержит только значения STRING,
CHAR и INTEGER, что означает, что не все поддерживаемые типы
Hibernate доступны через аннотацию @DiscriminatorColumn.
Вы также можете использовать @DiscriminatorFormula чтобы выразить в SQL столбец
виртуального дискриминатора. Это особенно полезно, когда значение дискриминатора может быть извлечено
из одного или нескольких столбцов таблицы. Как @DiscriminatorColumn, так
и @DiscriminatorFormula должны быть установлены на корневую сущность (один раз
за постоянную иерархию).
@org.hibernate.annotations.DiscriminatorOptions позволяет дополнительно указывать параметры
дискриминатора Hibernate, которые не стандартизированы в JPA. Доступными параметрами являются
force и insert. Атрибут force полезен, если таблица содержит строки
с «дополнительными» значениями дискриминатора, которые не отображаются на постоянный
класс. Это может произойти, например, при работе с устаревшей базой данных. Если для параметра
force установлено значение true Hibernate будет указывать допустимые значения
дискриминатора в запросе SELECT, даже при получении всех экземпляров корневого класса.
Второй вариант — insert — указывает Hibernate, включать или не включать
столбец дискриминатора в SQL INSERT. Обычно столбец должен быть частью инструкции
INSERT, но если ваш столбец дискриминатора также является частью отображённого составного
идентификатора, вы должны установить этот параметр в значение false.
Совет
Существует также аннотация @org.hibernate.annotations.ForceDiscriminator, которая устарела
с версии 3.6. Вместо этого используйте @DiscriminatorOptions.
Наконец, используйте @DiscriminatorValue для каждого класса иерархии, чтобы указать значение,
сохраненное в столбце дискриминатора для данной сущности. Если вы не устанавливаете
@DiscriminatorValue в классе, используется полное квалифицированное имя класса.
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="planetype",
discriminatorType=DiscriminatorType.STRING
)
@DiscriminatorValue("Plane")
public class Plane { ... }
@Entity
@DiscriminatorValue("A320")
public class A320 extends Plane { ... }
В hbm.xml элемент <дискриминатор> используется для определения столбца дискриминатора
или формулы:
(необязательный. по умолчанию class): имя столбца дискриминатора.
type
(необязательный. по умолчанию string): имя, указывающее тип Hibernate.
force
(необязательный. по умолчанию false): «Заставляет» Hibernate указывать
допустимые значения дискриминатора, даже при получении всех экземпляров корневого класса.
insert
(необязательный. по умолчанию true): установите значение false,
если столбец дискриминатора также является частью отображённого составного идентификатора.
Он сообщит Hibernate не включать столбец в SQL INSERT.
formula
(необязательный): произвольное выражение SQL, которое выполняется, когда тип должен быть оценен.
Он допускает дискриминацию на основе контента.
Фактические значения столбца дискриминатора задаются атрибутом discinator-value элементов
<class> и <subclass>.
Атрибут formula позволяет объявить произвольное выражение SQL, которое будет использоваться
для оценки типа записи в БД. Например:
<discriminator
formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end"
type="integer"/>
5.1.6.2. Присоединительная стратегия подкласса
Каждый подкласс также можно отоюразить на его собственную таблицу. Это называется стратегией отображения
таблицы на подкласс. Унаследованное состояние извлекается путем соединения с таблицей суперкласса.
Для этой стратегии отображения столбец дискриминатора не требуется. Однако каждый подкласс должен
объявлять столбец таблицы, содержащий идентификатор объекта. Первичный ключ этой таблицы также является внешним
ключом таблицы суперкласса и описывается @PrimaryKeyJoinColumns или элементом <key>.
@Entity @Table(name="CATS")
@Inheritance(strategy=InheritanceType.JOINED)
public class Cat implements Serializable {
@Id @GeneratedValue(generator="cat-uuid")
@GenericGenerator(name="cat-uuid", strategy="uuid")
String getId() { return id; }
...
}
@Entity @Table(name="DOMESTIC_CATS")
@PrimaryKeyJoinColumn(name="CAT")
public class DomesticCat extends Cat {
public String getName() { return name; }
}
Заметка
Имя таблицы по умолчанию - это неквалифицированное имени класса. Кроме того, если
@PrimaryKeyJoinColumn не установлен, предполагается, что столбцы первичного
ключа/внешнего ключа имеют те же имена, что и столбцы первичного ключа в основной
таблице суперкласса.
В hbm.xml используйте элемент <join-subclass>. Например:
Третий вариант — отображать только конкретные классы иерархии наследования на таблицы.
Это называется стратегией Таблица на конкретный класс. Каждая таблица определяет все постоянные состояния
класса, включая унаследованное состояние. В Hibernate нет необходимости явно отображать такие иерархии
наследования. Вы можете отобразить каждый класс как отдельную корневую сущность. Однако,
если вы хотите использовать полиморфные ассоциации (например, связь с суперклассом вашей иерархии),
вам нужно использовать отображение <union-subclass>.
Иногда бывает полезно обмениваться общими свойствами через технический или бизнес-суперкласс без включения
его в качестве обычного отображённой сущности (т. е. никакой конкретной таблицы для этой
сущности). Для этого вы можете отобразить их как @MappedSuperclass.
@MappedSuperclass
public class BaseEntity {
@Basic
@Temporal(TemporalType.TIMESTAMP)
public Date getLastUpdate() { ... }
public String getLastUpdater() { ... }
...
}
@Entity class Order extends BaseEntity {
@Id public Integer getId() { ... }
...
}
В базе данных эта иерархия будет представлена в виде таблицы Order, содержащей
столбцы id, lastUpdate и lastUpdater. Отображение свойств
встроенных суперклассов копируются в сущности их подклассов. Помните, что встроенный суперкласс
не является корнем иерархии.
Заметка
Свойства из суперклассов, не отображаемых как @MappedSuperclass, игнорируются.
Заметка
Используется тип доступа по умолчанию (поле или методы), если вы не используете аннотацию
@Access.
Заметка
Это же понятие может быть применено к объектам @Embeddable для сохранения свойств
из их суперклассов. Вам также нужно использовать @MappedSuperclass для этого
(это не следует рассматривать как стандартную функцию EJB3)
Заметка
Разрешено отмечать класс как @MappedSuperclass в середине отображённой иерархии
наследования.
Заметка
Любой класс в иерархии, не аннотированный с помощью @MappedSuperclass
или @Entity, будет проигнорирован.
Вы можете переопределять столбцы, определённые в сущности суперклассов на уровне корневой сущности,
с помощью аннотации @AttributeOverride.
@MappedSuperclass
public class FlyingObject implements Serializable {
public int getAltitude() {
return altitude;
}
@Transient
public int getMetricAltitude() {
return metricAltitude;
}
@ManyToOne
public PropulsionType getPropulsion() {
return metricAltitude;
}
...
}
@Entity
@AttributeOverride( name="altitude", column = @Column(name="fld_altitude") )
@AssociationOverride(
name="propulsion",
joinColumns = @JoinColumn(name="fld_propulsion_fk")
)
public class Plane extends FlyingObject {
...
}
Свойство altitude будет храниться постоянным в столбце fld_altitude таблицы
Plane, и ассоциация движений будет реализована в столбце внешнего ключа
fld_propulsion_fk.
Вы можете определить @AttributeOverride и @AssociationOverride для классов
@Entity, классов и свойств @MappedSuperclass, указывающих на объект
@Embeddable.
В hbm.xml просто отобразите свойства суперкласса в элементе <class> сущности, которая
должен наследовать от его.
5.1.6.5. Отображение одной сущности на несколько таблиц
Хотя это не рекомендуется для новой схемы, некоторые устаревшие базы данных заставляют вас отображать
одну сущность на несколько таблиц.
Использование аннотаций уровня @SecondaryTable или @SecondaryTables.
Чтобы выразить, что столбец находится в определённой таблице, используйте параметр таблицы
@Column или @JoinColumn.
В этом примере имя будет в MainCat. storyPart1 будет
в Cat1, а storyPart2 будет в Cat2. Cat1
будет присоединен к MainCat, используя cat_id как внешний ключ,
а Cat2 с использованием id (то есть то же имя столбца,
столбец идентификатора MainCat). Кроме того, установлено уникальное ограничение
на storyPart2.
Существует также дополнительная настройка, доступная через аннотацию @org.hibernate.annotations.Table:
fetch: Если установлено значение JOIN, по умолчанию, Hibernate будет использовать
inner join для извлечения из вторичной таблицы, определённой классом или его
суперклассами, и outer join из вторичной таблицы, определённой подклассом.
Если установлено значение SELECT, то Hibernate будет использовать последовательный
выбор для вторичной таблицы, определённой в подклассе, который будет выдаваться только
в том случае, если записть окажется представлением экземпляра подкласса. inner join
будут по-прежнему использоваться для извлечения вторичной информации, определённой классом
и его суперклассами.
inverse: Если true, Hibernate не будет пытаться вставлять или обновлять
свойства, определённые этим соединением (join). По умолчанию установлено значение false.
optional: Если включено (по умолчанию), Hibernate будет вставлять записть только
в том случае, если свойства, определённые этим соединением (join ), не null и всегда
будут использовать outer join для извлечения свойств.
foreignKey: определяет имя внешнего ключа вторичной таблицы, указывающей на основную
таблицу.
Обязательно используйте имя вторичной таблицы в свойстве appliesto
@Entity
@Table(name="MainCat")
@SecondaryTable(name="Cat1")
@org.hibernate.annotations.Table(
appliesTo="Cat1",
fetch=FetchMode.SELECT,
optional=true)
public class Cat implements Serializable {
private Integer id;
private String name;
private String storyPart1;
private String storyPart2;
@Id @GeneratedValue
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Column(table="Cat1")
public String getStoryPart1() {
return storyPart1;
}
@Column(table="Cat2")
public String getStoryPart2() {
return storyPart2;
}
}
(необязательный): переопределяет имя схемы, указанное корневым элементом <hibernate-mapping>.
catalog
(необязательный): переопределяет имя каталога, указанное корневым элементом <hibernate-mapping>.
fetch
(необязательный. по умолчанию join): если установлено join,
Hibernate будет использовать inner join для извлечения <join>,
определённого классом или его суперклассами. Он будет использовать outer join
для <join>, определённого подклассом. Если установлено select,
то Hibernate будет использовать последовательный выбор для <join>, определённого
в подклассе. Это будет выдаваться только в том случае, если запись представляет экземпляр
подкласса. inner join будет по-прежнему использоваться для извлечения <join>,
определённого классом и его суперклассами.
fetch
(необязательный. по умолчанию false): если включено, Hibernate не будет
вставлять или обновлять свойства, определённые этим соединением.
optional
(необязательный. по умолчанию false): если включено, Hibernate вставляет запись
только в том случае, если свойства, определённые этим join, не null. Он всегда будет
использовать outer join для извлечения свойств.
Например, адресная информация для человека может быть отображена на отдельную таблицу при сохранении
семантики типа значения для всех свойств:
Эта функция часто используется только для устаревших моделей данных. Мы рекомендуем меньше таблиц,
чем классов, и «дроблёную» модель домена. Однако он полезен для переключения
между стратегиями отображения наследования в одной иерархии, как объясняется ниже.
5.1.7. Отображение ассоциаций один-к-одному и многие-к-одному
Чтобы связать одну сущность с другой, вам нужно отобразить свойство ассоциации как одну ассоциацию.
В реляционной модели вы можете либо использовать внешний ключ, либо таблицу ассоциации, либо
(менее распространено) совместно использовать одно и то же значение первичного ключа между двумя
сущностями.
Чтобы отметить ассоциацию, используйте либо @ManyToOne, либо @OnetoOne.
@ManyToOne и @OneToOne имеют параметр с именем targetEntity,
который описывает имя целевой сущности. Вам обычно не нужен этот параметр, поскольку значение
по умолчанию (тип свойства, хранящего ассоциацию) хорош почти во всех случаях. Однако это полезно,
если вы хотите использовать интерфейсы как возвращаемый тип вместо обычной сущности.
Установка значения атрибута cascade для любого многозначительного значения, отличного
от ничто, будет распространять определённые операции на связанный объект. Многозначительные значения
делятся на три категории.
основные операции, которые включают:
persist, merge, delete, save-update, evict, replicate,
lock and refresh
;
специальные значения: delete-orphan or all;
разделённые запятыми комбинации названий операций: cascade="persist,merge,evict"
или cascade="all,delete-orphan". См.
Раздел 11.11. Переходное постоянство)
для полного объяснения. Обратите внимание, что однозначные ассоциации
«многие-к-одному» не поддерживают удаление «сирот» (orphan).
По умолчанию в JPA 2 одноточечные ассоциации имеют нетерпеливую выборку (eagerly fetched).
Вы можете указать им делать ленивую выбоку (lazily fetch) с помощью
@ManyToOne(fetch=FetchType.LAZY), и в этом случае Hibernate будет проксировать связь
и загружать её, когда состояние связанной сущности достигнуто. Вы можете заставить Hibernate
не использовать прокси, используя @LazyToOne(NO_PROXY). В этом случае выборка свойства
бфдет ленивой, когда к переменной произойдёт первое обращение. Для этого требуется инструментарий
байт-кода времени сборки. lazy="false" указывает, что ассоциация всегда будет сделана нетерпеливой выборкой
(eagerly fetched).
С параметрами JPA по умолчанию односторонние ассоциации загружаются с последующим выбором,
если установлено LAZY, или SQL JOIN используется для EAGER-ассоциаций.
Однако вы можете настроить стратегию выборки, то есть как данные извлекаются с помощью
@Fetch. FetchMode может быть SELECT (выбор запускается при загрузке
ассоциации) или JOIN (используйте SQL JOIN для загрузки ассоциации
при загрузке сущности владельца). JOIN переопределяет любой ленивый атрибут (ассоциация,
загруженная с помощью стратегии JOIN, не может быть ленивой).
5.1.7.1. Использование внешнего ключа или таблицы ассоциаций
Обычная связь с другим постоянным классом объявляется с использованием
@ManyToOne, если несколько сущностей могут указывать на целевую сущность;
@OneToOne, если только одна сущность может указывать на целевую сущность;
и внешний ключ в одной таблице ссылается на столбец(столбцы) первичных ключей целевой таблицы.
@Entity
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}
Атрибут @JoinColumn не является обязательным, значение(я) по умолчанию —
это объединение имени отношения на стороне владельца, _ (подчеркивание) и имя столбца первичного
ключа в принадлежащей ему стороне. В этом примере company_id, поскольку имя
свойства — компания, а идентификатор столбца компании — id.
@Entity
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}
public interface Company {
...
}
Вы также можете отобразить одну ассоциацию через таблицу ассоциаций. Эта таблица ассоциаций, описываемая
аннотацией @JoinTable, будет содержать внешний ключ, ссылающийся обратно на таблицу сущности
(через @JoinTable.joinColumns) и внешний ключ, ссылающийся на таблицу целевой сущности
(через @JoinTable.inverseJoinColumns).
@Entity
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinTable(name="Flight_Company",
joinColumns = @JoinColumn(name="FLIGHT_ID"),
inverseJoinColumns = @JoinColumn(name="COMP_ID")
)
public Company getCompany() {
return company;
}
...
}
Заметка
Вы можете использовать фрагмент SQL для имитации столбца физического соединения с помощью
аннотаций @JoinColumnOrFormula / @JoinColumnOrformulas (так же, как
вы можете использовать фрагмент SQL для имитации столбца свойств через аннотацию
@Formula).
@Entity
public class Ticket implements Serializable {
@ManyToOne
@JoinColumnOrFormula(formula="(firstname + ' ' + lastname)")
public Person getOwner() {
return person;
}
...
}
Вы можете пометить ассоциацию как обязательную, используя атрибут optional=false.
Однако мы рекомендуем использовать аннотацию Bean Validation @NotNull в качестве
лучшей альтернативы. Как следствие, столбец(столбцы) внешнего ключа будет отмечен как не null
(если возможно).
Когда Hibernate не может разрешить ассоциацию, потому что ожидаемый связанный элемент не находится
в базе данных (неверный идентификатор в столбце ассоциации), возникает исключение. Это может быть
неудобно для устаревших и плохо поддерживаемых схем. Вы можете попросить Hibernate игнорировать
такие элементы, а не создавать исключение, используя аннотацию @NotFound.
Пример 5.1. Ассоциация @NotFound
@Entity
public class Child {
...
@ManyToOne
@NotFound(action=NotFoundAction.IGNORE)
public Parent getParent() { ... }
...
}
Иногда вы хотите делегировать своей базе данных удаление каскада когда данная сущность удалена.
В этом случае Hibernate генерирует ограничение каскадного удаления на уровне базы данных.
Пример 5.2. Ассоциация @OnDelete
@Entity
public class Child {
...
@ManyToOne
@OnDelete(action=OnDeleteAction.CASCADE)
public Parent getParent() { ... }
...
}
Ограничения внешнего ключа, генерируемые Hibernate, имеют довольно нечитаемое имя. Вы можете переопределить
имя ограничения с помощью @ForeignKey.
Пример 5.3. Ассоциация @ForeignKey
@Entity
public class Child {
...
@ManyToOne
@ForeignKey(name="FK_PARENT")
public Parent getParent() { ... }
...
}
alter table Child add constraint FK_PARENT foreign key (parent_id) references Parent
Иногда вы хотите связать одну сущность с другой не с помощью первичного ключа целевой сущности,
а с помощью другого уникального ключа. Это можно сделать, указав уникальные столбцы ключей
в @JoinColumn.referenceColumnName.
@Entity
class Person {
@Id Integer personNumber;
String firstName;
@Column(name="I")
String initial;
String lastName;
}
@Entity
class Home {
@ManyToOne
@JoinColumns({
@JoinColumn(name="first_name", referencedColumnName="firstName"),
@JoinColumn(name="init", referencedColumnName="I"),
@JoinColumn(name="last_name", referencedColumnName="lastName"),
})
Person owner
}
Однако это не поощряется и должно быть зарезервировано для устаревших отображений.
В hbm.xml отображение ассоциации аналогично. Основное отличие состоит в том, что @OneToOne
отображается как <many-to-one unique="true" />, давайте погрузимся в тему.
(необязательный): имя столбца внешнего ключа. Его также можно указать вложенными элементами
<column>.
class
(необязательный. по умолчанию определяется тип свойства, определяемый отражением):
имя ассоциированного класса.
cascade
(необязательный): указывает, какие операции должны быть каскадированы от родительского объекта
к связанному объекту.
fetch
(необязательный. по умолчанию select): выбирает между выборкой outer-join
или последовательной выборкой select.
update, insert
(необязательный. по умолчанию true): указывает, что отображённые столбцы должны
быть включены в операторы SQL и/или INSERT. Установка обоих
значений в false позволяет получить чистую «производную» ассоциацию,
значение которой инициализируется из другого свойства, которое отображается на один
и тот же столбец(-столбцы), или с помощью триггера или другого приложения.
property-ref
(необязательный): имя свойства ассоциированного класса, которое связано с этим внешним ключом.
Если не указано, используется первичный ключ ассоциированного класса.
access
(необязательный. по умолчанию property): стратегия, которую Hibernate использует
для доступа к значению свойства.
unique
(необязательный): позволяет DDL генерировать уникальное ограничение для столбца внешнего ключа.
Позволяя ему быть объектом свойства-ref, вы можете сделать объединение множественнам
один-к-одному.
not-null
(необязательный): позволяет DDL генерировать ограничение на null для столбцов внешнего ключа.
optimistic-lock
(необязательный. по умолчанию true): указывает, что обновления этого свойства выполняют
или не требуют приобретения оптимистичной блокировки. Другими словами, он определяет, будет ли
приращение версии происходить, когда это свойство загрязнено.
lazy
(необязательный. по умолчанию proxy): по умолчанию проксируются одноточечные ассоциации.
lazy="no-proxy" указывает, что выборка свойства должна быть ленивой,
когда к переменной экземпляра произойдёт первое обращение. Для этого требуется инструментарий
байт-кода времени сборки. lazy="false" указывает, что выборка ассоциации всегда будет
нетерпеливой (eagerly fetched).
not-found
(необязательный. по умолчанию exception): указывает, как будут обрабатываться внешние
ключи, которые ссылаются на отсутствующие записи в базе. ignore будет обрабатывать
недостающую записть как ассоциацию null.
entity-name
(необязательный): имя сущности ассоциированного класса.
formula
(необязательный): выражение SQL, которое определяет значение для вычисленного внешнего ключа.
Установка значения атрибута cascade для любого многозначительного значения, отличного
от none, будет распространять определённые операции на связанный объект. Многозначительные значения
делятся на три категории. Во-первых, основные операции, которые включают:
persist, merge, delete, save-update,
evict, replicate, lock, refresh
; во-вторых, специальные значения: delete-orphan; и в-третьих,
все комбинации имен операций с запятыми: cascade="persist, merge, evict"
или cascade="all, delete-orphan". См.
Раздел 11.11. Переходное постоянство)
для полного объяснения. Обратите внимание, что однозначные ассоциации многие-к-одному и один-к-одному
не поддерживают удаление сирот (orphan).
Вот пример типичного объявления «многие-к-одному»:
Атрибут property-ref должен использоваться только для отображения устаревших данных, где внешний ключ
относится к уникальному ключу связанной таблицы, отличной от первичного ключа. Это сложная и запутанная
реляционная модель. Например, если класс Product имел уникальный серийный номер, который не является
первичным ключом. Атрибут unique управляет генерацией DDL Hibernate с помощью инструмента SchemaExport.
5.1.7.2. Разделение первичного ключа с ассоциированным сущностью
Пример 5.4. Ассоциация один-к-одному
@Entity
public class Body {
@Id
public Long getId() { return id; }
@OneToOne(cascade = CascadeType.ALL)
@MapsId
public Heart getHeart() {
return heart;
}
...
}
@Entity
public class Heart {
@Id
public Long getId() { ...}
}
Заметка
Многие люди путали эти первичные ключи, основанные на ассоциации один-к-одному. Они могут быть только лениво
загружены, если Hibernate знает, что другая сторона ассоциации всегда присутствует. Чтобы указать Hibernate,
что это так, используйте @OneToOne(optional=false).
(необязательный. по умолчанию определяется тип свойства, определяемый отражением):
имя ассоциированного класса.
cascade
(необязательный): указывает, какие операции должны быть каскадированы от родительского объекта
к связанному объекту.
constrained
(необязательный): указывает ограничение внешнего ключа на первичный ключ отображенной таблицы
и ссылается на таблицу связанного класса. Эта опция влияет на порядок, в котором
save() и delete() каскадируются, и определяет, может ли ассоциация
быть проксированной. Он также используется инструментом экспорта схемы.
fetch
(необязательный. по умолчанию select): выбирает между выборкой outer-join
или последовательной выборкой select.
property-ref
(необязательный): имя свойства ассоциированного класса, которое связано с внешним ключом класса.
Если не указано, используется первичный ключ ассоциированного класса.
access
(необязательный. по умолчанию property): стратегия, которую Hibernate использует
для доступа к значению свойства.
formula
(необязательный): почти все ассоциации «один-к-одному» отображают на первичный ключ владеющего
объекта. Если это не так, вы можете указать другой столбец, столбцы или выражение
для объединения с использованием формулы SQL.
См. пример org.hibernate.test.onetooneformula.
lazy
(необязательный. по умолчанию proxy): по умолчанию проксируются одноточечные ассоциации.
lazy="no-proxy" указывает, что выборка свойства должна быть ленивой,
когда к переменной экземпляра произойдёт первое обращение. Для этого требуется инструментарий
байт-кода времени сборки. lazy="false" указывает, что выборка ассоциации всегда будет
нетерпеливой (eagerly fetched).
Обратите внимание, что если constrained="false",
проксирование невозможно, и Hibernate и произойдёт нетерпеливая выборка ассоциации.
Для ассоциаций первичных ключей не требуется дополнительный столбец таблицы. Если две записи связаны ассоциацией,
то две записи таблицы имеют одно и то же значение первичного ключа. Чтобы связать два объекта с помощью
ассоциации первичного ключа, убедитесь, что им присвоено одинаковое значение идентификатора.
Для первичной ассоциации ключей добавьте следующие отображение для Employee и Person
соответственно:
Убедитесь, что первичные ключи связанных записей в таблицах PERSON и EMPLOYEE равны. Вы используете
специальную стратегию генерации идентификатора гибернации, называемую foreign:
Вновь сохранённому экземпляру Person присваивается то же значение первичного ключа,
что и экземпляру Employee, ссылка на который в свойстве employee
этого Person.
5.1.8. Естественный идентификатор (Natural-id)
Хотя мы рекомендуем использовать суррогатные ключи в качестве первичных ключей, вы должны попытаться определить
естественные ключи для всех объектов. Естественным ключом является свойство или комбинация свойств, которые являются
уникальными и не равными null. Он также неизменен. Отобразите свойства натурального ключа
как @NaturalId или сопоставьте их внутри элемента <natural-id>. Hibernate сгенерирует
необходимые уникальные ключи и ограничения на null, и, как результат, ваше отображение будет более
самодокументированным.
@Entity
public class Citizen {
@Id
@GeneratedValue
private Integer id;
private String firstname;
private String lastname;
@NaturalId
@ManyToOne
private State state;
@NaturalId
private String ssn;
...
}
// и позднее в запросе
List results = s.createCriteria( Citizen.class )
.add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", ste ) )
.list();
Рекомендуется использовать equals() и hashCode() для сравнения свойств естественного
ключа сущности.
Это отображение не предназначено для использования с объектами с естественными первичными ключами.
mutable (необязательный. по умолчанию false): по умолчанию свойства естественного
идентификатора считаются неизменяемыми (постоянными).
5.1.9. Any
Существует еще один тип отображения свойств. Отображение @Any определяет полиморфную связь
с классами из нескольких таблиц. Для такого типа отображения требуется более одного столбца. Первый столбец
содержит тип связанной сущности. Остальные столбцы содержат идентификатор. Невозможно указать ограничение внешнего ключа
для такого рода связей. Это не обычный способ отображения полиморфных ассоциаций, и вы должны использовать
его только в особых случаях. Например, для журналов аудита, данных сессии пользователя и т. д.
Аннотация @Any описывает столбец, содержащий информацию метаданных. Чтобы связать значение информации метаданных
и фактического типа объекта, используются аннотации @AnyDef и @AnyDefs. Атрибут
metaType позволяет приложению указывать настраиваемый тип, который отображает значения столбца базы данных
на постоянные классы, которые имеют свойства идентификатора типа, указанного idType. Вы должны указать
отображение значений metaType на имена классов.
@Any( metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER )
@AnyMetaDef(
idType = "integer",
metaType = "string",
metaValues = {
@MetaValue( value = "S", targetEntity = StringProperty.class ),
@MetaValue( value = "I", targetEntity = IntegerProperty.class )
}
)
@JoinColumn( name = "property_id" )
public Property getMainProperty() {
return mainProperty;
}
Обратите внимание, что @AnyDef может быть взаимозависимым и повторно использоваться. В этом
случае рекомендуется поместить его в виде метаданных пакета.
//в пакете
@AnyMetaDef( name="property"
idType = "integer",
metaType = "string",
metaValues = {
@MetaValue( value = "S", targetEntity = StringProperty.class ),
@MetaValue( value = "I", targetEntity = IntegerProperty.class )
}
)
package org.hibernate.test.annotations.any;
//в классе
@Any( metaDef="property", metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER )
@JoinColumn( name = "property_id" )
public Property getMainProperty() {
return mainProperty;
}
(необязательный. по умолчанию string): любой тип, допускаемый для отображения дискриминатора.
cascade
(необязательный. по умолчанию none): каскадный стиль.
access
(необязательный. по умолчанию property): стратегия Hibernate использует для доступа
к значению свойства.
optimistic-lock
(необязательный. по умолчанию true): указывает, что обновления этого свойства либо требуют,
либо не требуют приобретения оптимистичной блокировки. Он определяет, будет ли приращение версии
происходить, если это свойство загрязнено.
5.1.10. Свойства
Элемент <properties> позволяет определить именованную логическую группировку свойств класса. Наиболее важным
использованием конструкции является то, что она позволяет сочетанию свойств быть целью property-ref.
Это также удобный способ определения многоколоночного уникального ограничения. Например:
логическое имя группировки. Это не фактическое имя свойства.
insert
появятся ли отображаемые столбцы в SQL INSERT?
update
появятся ли отображаемые столбцы в SQL UPDATE?
optimistic-lock
(необязательный. по умолчанию true): указывает, что обновления этого свойства либо требуют,
либо не требуют приобретения оптимистичной блокировки. Он определяет, будет ли приращение версии
происходить, если это свойство загрязнено.
unique
(необязательный. по умолчанию false): указывает, что существует уникальное ограничение
для всех отображённых столбцов компонента.
Например, если мы имеем следующее отображение <properties>:
При использовании аннотаций в качестве стратегии отображения такая конструкция не требуется, поскольку
привязка между столбцом и связанным с ним другим столбцом в ассоциированной таблице выполняется
непосредственно
@Entity
class Person {
@Id Integer personNumber;
String firstName;
@Column(name="I")
String initial;
String lastName;
}
@Entity
class Home {
@ManyToOne
@JoinColumns({
@JoinColumn(name="first_name", referencedColumnName="firstName"),
@JoinColumn(name="init", referencedColumnName="I"),
@JoinColumn(name="last_name", referencedColumnName="lastName"),
})
Person owner
}
Использование этого вне контекста отображения устаревших данных не рекомендуется.
5.1.11. Некоторые особенности hbm.xml
Структура hbm.xml имеет некоторые особенности, естественно не присутствующие при использовании аннотаций, давайте
кратко опишем их.
5.1.11.1. Doctype
Все XML-отображения должны объявлять doctype. Фактическое DTD можно найти по указанному выше URL-адресу
в каталоге hibernate-x.x.x/src/org/hibernate или в hibernate3.jar. Hibernate всегда
будет искать DTD в своём пути к классам. Если вы испытываете подключение DTD с использованием
интернет-соединения, проверьте объявление DTD вместо содержимого вашего пути к классам.
5.1.11.1.1. EntityResolver
Hibernate сначала попытается найти DTD в своём пути к классам. Это делается путем регистрации пользовательской
реализации org.xml.sax.EntityResolver с помощью SAXReader, который он использует для чтения
в xml-файлах. Этот пользовательский EntityResolver распознает два разных пространства имен systemId:
hibernate namespace распознается всякий раз, когда resolver сталкивается с systemId,
начиная с http://www.hibernate.org/dtd/. Resolver пытается найти эти объекты через загрузчик классов,
который загружает классы Hibernate.
user namespace распознается всякий раз, когда resolver сталкивается с systemId, используя URL-протокол
classpath://. Resolver попытается найти эти объекты с помощью (1) текущего загрузчика классов
потоков и (2) загрузчика классов, который загрузил классы Hibernate.
Ниже приведен пример использования пространства имен пользователей:
Где types.xml является ресурсом в пакете your.domain и содержит пользовательский
typedef.
5.1.11.2. Hibernate-mapping
Этот элемент имеет несколько необязательных атрибутов. Атрибуты схемы и каталога определяют, что таблицы, упомянутые
в этом отображении, относятся к именованной схеме и/или каталогу. Если они указаны, имена таблиц будут
квалифицироваться по заданным именам схем и каталогов. Если они отсутствуют, имена таблиц будут неквалифицированы.
Атрибут default-cascade указывает, какой каскадный стиль следует использовать для свойств и коллекций,
которые не указывают атрибут cascade. По умолчанию атрибут auto-import позволяет
использовать неквалифицированные имена классов в языке запросов.
(необязательный. по умолчанию none): каскадный стиль по умолчанию.
default-access
(необязательный. по умолчанию property): стратегия, которую Hibernate должен использовать
для доступа ко всем свойствам. Это может быть специальная реализация PropertyAccessor.
default-lazy
(необязательный. по умолчанию true): значение по умолчанию для неуказанных
lazyатрибутов отображений классов и коллекций.
auto-import
(необязательный. по умолчанию true): указывает, можем ли мы использовать
неквалифицированные имена классов в этом отображении на языке запросов.
package
(необязательный): указывает префикс пакета для использования с неквалифицированными именами классов
в документе отображения.
Если у вас есть два постоянных класса с одинаковым неквалифицированным именем, вы должны установить
auto-import="false". Если вы попытаетесь присвоить два класса
одному и тому же «импортированному» имени, то результатом будет исключение.
Элемент hibernate-mapping позволяет вам отображать несколько постоянных отображений <class>,
как показано выше. Однако хорошая практика (и ожидаемая некоторыми инструментами) отображать только один постоянный
класс или одну иерархию классов в один файл отображения и именовать его после постоянного суперкласса.
Например, Cat.hbm.xml, Dog.hbm.xml или если используется наследование Animal.hbm.xml.
5.1.11.3. Key
Элемент <key> показан в этом руководстве несколько раз. Он появляется везде, где родительский элемент
отображения определяет объединение с новой таблицей, которая ссылается на первичный ключ исходной таблицы.
Он также определяет внешний ключ в объединённой таблице:
(необязательный): имя столбца внешнего ключа. Его также можно указать вложенными элементами <column>.
on-delete
(необязательный. по умолчанию noaction): указывает, включено ли ограничение внешнего
ключа на каскадное удаление на уровне базы данных.
property-ref
(необязательный): указывает, что внешний ключ относится к столбцам, которые не являются первичным
ключом исходной таблицы. Он предоставляется для устаревших данных.
not-null
(необязательный): указывает, что столбцы внешнего ключа не имеют значения NULL. Это подразумевается
всякий раз, когда внешний ключ также является частью первичного ключа.
update
(необязательный): указывает, что внешний ключ никогда не должен обновляться. Это подразумевается всякий
раз, когда внешний ключ также является частью первичного ключа.
unique
(необязательный): указывает, что внешний ключ должен иметь ограничение на уникальность.
Это подразумевается всякий раз, когда внешний ключ также является первичным ключом.
Для систем, в которых важна эффективность удаления, мы рекомендуем, чтобы все ключи были определены
on-delete="cascade". Hibernate использует ограничение ON CASCADE DELETE на уровне базы
данных вместо многих отдельных операторов DELETE. Имейте в виду, что эта функция обходит обычную
оптимистичную стратегию блокировки Hibernate для данных с версией.
5.1.11.4. Import
Если ваше приложение имеет два постоянных класса с одинаковым именем, и вы не хотите указывать полное
имя пакета в запросах Hibernate, классы можно «импортированы» явно, а не полагаться
на auto-import="true". Вы также можете импортировать классы и интерфейсы, которые явно
не отображаются:
Большинство атрибутов в column предоставляют средства для настройки DDL во время генерации
автоматической схемы. Атрибуты read и write позволяют указать пользовательский SQL,
который Hibernate будет использовать для доступа к значению столбца. Подробнее
об этом см. в обсуждении выражений чтения и записи столбцов.
Элементы column и formula могут даже объединяться в одно и то же свойство
или отображение ассоциаций для выражения, например, условий экзотического соединения (join).
В отношении службы персистентности объекты уровня языка Java подразделяются на две группы:
Сущность существует независимо от любых других объектов, содержащих ссылки на сущность. Сравните
это с обычной Java-моделью, где разыменованный объект сборирается сборщиком мусора. Сущности должны быть явно
сохранены и удалены. Однако сохранение и удаление могут быть каскадированы из родительского объекта
в дочерние. Это отличается от ODMG-модели персистентности объекта по достижимости и более точно
соответствует тому, как приложения обычно используются в больших системах. Сущности поддерживают круговые
и разделяемые ссылки. Они также могут быть версифицированы.
Постоянное состояние сущности состоит из ссылок на другие сущности и экземпляры типов значений.
Значения — это примитивы: коллекции (не то, что находится внутри коллекции), компоненты и некоторые
неизменяемые объекты. В отличие от сущностей, значения в конкретных коллекциях и компонентах
сохраняются и удаляются по достижинии. Поскольку объекты со значениями и примитивы сохраняются
и удаляются вместе с их содержащей сущностью, они не могут быть независимо версированы. Значения
не имеют независимой идентификации, поэтому они не могут быть разделены двумя сущностями или коллекциями.
До сих пор мы использовали термин «постоянный класс» для обозначения сущностей.
Мы будем продолжать это делать. Однако не все пользовательские классы с постоянным состоянием являются
сущностями. Компонент представляет собой пользовательский класс со значениями семантики. Java-свойство типа
java.lang.String также имеет семантику значений. Учитывая это определение, все типы (классы), предоставляемые
JDK, имеют семантику типов значений в Java, тогда как определяемые пользователем типы могут быть отображены
семантикой сущности или значения. Это решение зависит от разработчика приложения. Класс сущности в модели
домена обычно будет иметь общие ссылки на один экземпляр этого класса, тогда как композиция или агрегация
обычно преобразуются в тип значения.
Мы перейдем к рассмотрению обоих концепций в этом справочном руководстве.
Задача состоит в том, чтобы отобразить систему типов Java и сущностей, определённых разработчиком
и типов значений с системой типа SQL/база данных. Мост между обеими системами предоставляется Hibernate.
Для сущностей используются <class>, <subclass> и т. д. Для типов значений мы используем
<property>, <component> и т. д., которые обычно имеют атрибут типа. Значение этого
атрибута — это имя типа отображения Hibernate. Hibernate предоставляет ряд отображений
для стандартных типов значений JDK из коробки. Вы можете написать свои собственные типы отображений
и реализовать свои собственные стратегии конверсии.
С исключениями и коллекциями, все встроенные типы Hibernate поддерживают null-семантику.
5.2.2. Основные типы значений
Встроенные основные типы отображений могут быть категоризированы следующим образом:
Тип отображениий из Java-примитивов или классов-оболочек на соответствующие (специфичные для поставщика)
типы столбцов SQL. boolean, yes_no и true_false — все альтернативные
кодировки для Java boolean или java.lang.Boolean.
string
Тип отображения java.lang.String на VARCHAR (или Oracle VARCHAR2).
calendar, calendar_date
Тип отображениий java.util.Calendar на типы SQL TIMESTAMP и DATE
(или эквивалент).
big_decimal, big_integer
Тип отображениий java.math.BigDecimal и java.math.BigInteger
на NUMERIC (или Oracle NUMBER).
locale, timezone, currency
Тип отображениий java.util.Locale, java.util.TimeZone и java.util.Currency
в VARCHAR (или Oracle VARCHAR2). Экземпляры Locale и Locale
отображаются по их ISO-кодами. Экземпляры TimeZone отображаются по их ID.
class
Тип отображениия java.lang.Class на VARCHAR (или Oracle VARCHAR2).
Class отображается по его полному квалифицированному имени.
binary
Отображает массив байтов на соответствущий двоичный тип SQL.
text
Отображает длинные строки Java в SQL LONGVARCHAR или TEXT.
image
Отображает массивы типа long byte на SQL LONGVARBINARY.
serializable
Отображает сериализуемые типы Java на соответствующий двоичный тип SQL. Вы также можете указать тип Hibernate
serializable с именем сериализуемого Java-класса или интерфейса, который по умолчанию
не базового типа.
clob, blob
Тип отображаний для классов JDBC java.sql.Clob и java.sql.Blob. Эти типы могут быть
неудобными для некоторых приложений, поскольку объект blob или clob нельзя использовать повторно за пределами
транзакции. Поддержка драйверов неоднозначна и непоследовательна.
materialized_clob
Отображает длинные строки Java на типу SQL CLOB. При чтении значение CLOB немедленно
преобразуется в строку Java. Некоторые драйверы требуют, чтобы значение CLOB читалось в транзакции.
После материализации строка Java доступна вне транзакции.
materialized_blob
Настраивает Java-массивы типа long byte yf тип SQL BLOB. При чтении значение BLOB
немедленно возвращается в массив байтов. Некоторым драйверам требуется считывать значение BLOB
в транзакции. После материализации массив байтов доступен вне транзакции.
Тип отображениий для того, что считается неизменяемыми (mutable) типами Java. Здесь Hibernate делает определённые
оптимизации подходящими только для неизменяемых типов Java, и приложение рассматривает объект как неизменный.
Например, вы не должны вызывать Date.setTime() для экземпляра, отображённого
как imm_timestamp. Чтобы изменить значение свойства и сделать это изменение постоянным,
приложение должно назначить новый, неидентичный объект свойству.
Уникальные идентификаторы сущностей и коллекций могут быть любого базового типа, кроме binary,
blob и clob. Также допускаются составные идентификаторы. Смотрите ниже для получения
дополнительной информации.
Основные типы значений имеют соответствующие константы Type, определённые
в org.hibernate.Hibernate. Например, Hibernate.STRING представляет тип string.
5.2.3. Пользовательские типы значений
Разработчикам относительно легко создавать собственные типы значений. Например, вы можете захотеть сделать постоянным
свойства типа java.lang.BigInteger для столбцов VARCHAR. Для этого Hibernate
не предоставляет встроенный тип. Пользовательские типы не ограничиваются отображением свойства или элемента
коллекции на один столбец таблицы. Так, например, у вас может быть свойство Java getName()/setName()
типа java.lang.String, которое сохраняется в столбцах FIRST_NAME, INITIAL,
SURNAME.
Чтобы реализовать пользовательский тип, реализоуйте либо org.hibernate.UserType, либо
org.hibernate.CompositeUserType и объявите свойства, используя полное квалифицированное имя класса.
Просмотрите org.hibernate.test.DoubleStringType, чтобы увидеть примеры.
Обратите внимание на использование тегов <column> для сопоставления свойства нескольким столбцам.
Интерфейсы CompositeUserType, EnhancedUserType, UserCollectionType
и UserVersionType обеспечивают поддержку более специализированного использования.
Вы даже можете указать параметры в UserType в файле отображения. Для этого ваш
UserType должен реализовать интерфейс org.hibernate.usertype.ParameterizedType. Чтобы указать
параметры для вашего настраиваемого типа, вы можете использовать элемент <type> в ваших файлах
отображений.
UserType теперь может извлекать значение для параметра с именем default из объекта
Properties, переданного ему.
Если вы регулярно используете определённый UserType, полезно определить более короткое имя для него.
Вы можете сделать это, используя элемент <typedef>. Typedef присваивает имя настраиваемому типу и может также
содержать список значений параметров по умолчанию, если тип параметризуется.
Также возможно переопределить параметры, заданные в typedef, в каждом конкретном случае, используя параметры
типа в отображении свойств.
Несмотря на то, что богатый набор встроенных типов Hibernate и поддержка компонентов означает, что вам редко
придется использовать настраиваемый тип, считается хорошей практикой использовать пользовательские типы для не-сущностных
классов, которые часто встречаются в вашем приложении. Например, класс MonetaryAmount является хорошим
кандидатом для CompositeUserType, хотя он может быть отображен как компонент. Одной из причин
этого является абстракция. С помощью настраиваемого типа ваши файлы отображений будут защищены от изменений
способа представления денежных значений.
5.3. Отображение класса более одного раза
Можно предоставить несколько отображений для определённого постоянного класса. В этом случае вы должны указать
имя объекта для устранения неоднозначности между экземплярами двух отображённых сущностей. По умолчанию имя сущности
такое же как имя класса. Hibernate позволяет указать имя сущности при работе с постоянными объектами,
при записи запросов или при отображении ассоциаций с указанным объектом.
Теперь ассоциации задаются с использованием entity-name вместо class.
Заметка
Эта функция не поддерживается в аннотациях
5.4. Идентификаторы, цитируемые SQL
Вы можете заставить Hibernate указывать идентификатор в сгенерированном SQL, введя имя таблицы или столбца
в обратные ссылки в документе отображения. Hibernate будет использовать правильный стиль цитаты для SQL
Dialect. Обычно это двойные кавычки, но SQL Server использует скобки, а MySQL использует обратные
апострофы.
Сгенерированные свойства — это свойства, которые имеют свои значения, сгенерированные базой данных.
Как правило, приложениям Hibernate необходимо делать объектам refresh, которые содержат любые свойства,
для которых база данных генерировала значения. Отметка свойств как сгенерированных, однако, позволяют делегировать
эту ответственность Hibernate. Когда Hibernate выдает SQL INSERT или UPDATE для сущности
с определёнными сгенерированными свойствами, он сразу же выдает выбор для получения сгенерированных значений.
Свойства, помеченные как сгенерированные, должны быть дополнительно не вставляемыми (non-insertable)
и не обновляемыми (non-updateable). Только версии, временные метки (timestamps) и простые свойства могут быть
помечены как сгенерированные.
never (по умолчанию): данное значение свойства не создается в базе данных.
insert: данное значение свойства генерируется при вставке, но не восстанавливается
при последующих обновлениях. В эту категорию попадают такие свойства, как дата создания. Несмотря на то,
что свойства version и timestamp могут быть отмечены как сгенерированные, эта опция недоступна.
always: значение свойства генерируется как при вставке, так и при обновлении.
Чтобы пометить свойство как сгенерированное, используйте @Generated.
5.6. Преобразователи столбцов: выражения для чтения и записи
Hibernate позволяет настраивать SQL, который он использует, для чтения и записи значений столбцов,
отображённых на простые свойства. Например, если ваша база данных предоставляет набор функций шифрования данных,
вы можете вызвать их для отдельных столбцов следующим образом:
@Entity
class CreditCard {
@Column(name="credit_card_num")
@ColumnTransformer(
read="decrypt(credit_card_num)",
write="encrypt(?)")
public String getCreditCardNumber() { return creditCardNumber; }
public void setCreditCardNumber(String number) { this.creditCardNumber = number; }
private String creditCardNumber;
}
Вы можете использовать множественную форму @ColumnTransformers, если более чем одному столбцу необходимо
определить любое из этих правил.
Если свойство использует более одного столбца, вы должны использовать атрибут forColumn для указания
того, в каком столбце используются таргетинг.
@Entity
class User {
@Type(type="com.acme.type.CreditCardType")
@Columns( {
@Column(name="credit_card_num"),
@Column(name="exp_date") } )
@ColumnTransformer(
forColumn="credit_card_num",
read="decrypt(credit_card_num)",
write="encrypt(?)")
public CreditCard getCreditCard() { return creditCard; }
public void setCreditCard(CreditCard card) { this.creditCard = card; }
private CreditCard creditCard;
}
Hibernate автоматически применяет настраиваемые выражения, когда свойство ссылается на запрос. Эта функциональность
аналогична производному свойству formula с двумя отличиями:
Свойство поддерживается одним или несколькими столбцами, которые экспортируются как часть автоматической
генерации схемы.
Свойство — доступно для чтения и записи, а не только для чтения.
5.7. Вспомогательные объекты базы данных
Вспомогательные объекты базы данных допускают CREATE и DROP произвольных объектов базы данных. В сочетании
с инструментами эволюции схемы Hibernate они имеют возможность полностью определять пользовательскую схему в файлах
отображений Hibernate. Хотя они разработаны специально для создания и удаления таких вещей, как триггеры
или хранимые процедуры, любая команда SQL, которая может быть запущена с помощью метода
java.sql.Statement.execute(), валидна (например, ALTER, INSERT и т. д.). Существует два режима
определения вспомогательных объектов базы данных:
Первый режим — явно указать команды CREATE и DROP в файле сопоставления:
Второй режим — предоставить настраиваемый класс, который создает команды CREATE и DROP. Этот пользовательский
класс должен реализовать интерфейс org.hibernate.mapping.AuxiliaryDatabaseObject.
Кроме того, этим объектам базы данных могет быть (необязательно) ограничена область применения так, чтобы они применялись
только при использовании определённых диалектов.