Статьи

Естественные идентификаторы в Hibernate

Естественный идентификатор — это свойство или набор свойств, которые бы однозначно идентифицировали сущность. У нас может быть не более одного натурального идентификатора, определенного для сущности. Когда Hibernate видит тег natural-id в файле отображения сущностей, он автоматически создает уникальные и ненулевые ограничения для свойств, составляющих natural-id. Сначала давайте рассмотрим примеры простых и составных натуральных идентификаторов.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
<!-- Version 1 -->
<hibernate-mapping package="com.pramati.model">
    <class name="Person" table="PERSON">
        <id name="id" column="ID">
            <generator class="native"/>
        </id>
        <natural-id>
            <property name="voterId" type="string" column="VOTER_ID"/>
        </natural-id>
        <property name="name" type="string" column="NAME"/>
        <!-- Other properties -->
</class>
</hibernate-mapping>

Составной естественный идентификатор: номер телефона, то есть комбинация стандартного кода и номера стационарного телефона, может формировать естественный идентификатор для объекта лица.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<!-- Version 2 -->
<hibernate-mapping package="com.pramati.model">
    <class name="Person" table="PERSON">
        <id name="id" column="ID">
            <generator class="native"/>
        </id>
        <natural-id>
            <property name="stdCode" type="string" column="STD_CODE"/>
            <property name="landlineNumber" type="string" column="LANDLINE_NUMBER"/>
        </natural-id>
        <property name="name" type="string" column="NAME"/>
        <!-- Other properties -->
</class>
</hibernate-mapping>

Таким образом, Hibernate создает ненулевое ограничение для stdCode и landlineNumber. Эти свойства вместе должны быть уникальными для лица.

Естественные идентификаторы являются неизменяемыми по умолчанию. Поэтому предположим, что если вы попытаетесь загрузить сущность человека из базы данных и изменить какое-либо из свойств, составляющих natural-id, Hibernate выдаст исключение. Например, мы загрузили Person и попытались изменить его landlineNumber / stdcode в активном сеансе, вот исключение, которое мы получили бы:

1
2
org.hibernate.HibernateException:: An immutable natural identifier
of entity com.pramati.model.Person was altered from abc to xyz

В Hibernate 4.1 появилась функция загрузки сущностей по natural-id бина. До сих пор кеш сессии кеширует объекты, загруженные через get / load в текущем сеансе. Теперь объекты, загруженные с использованием natural-id, также кэшируются по умолчанию. Вот последние дополнения к Session API:

1
2
3
4
5
public NaturalIdLoadAccess byNaturalId(String entityName);
public NaturalIdLoadAccess byNaturalId(Class entityClass);
 
public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName);
public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass);

Мы можем загрузить экземпляры класса по естественному идентификатору следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// In case of version 1 defined above:
Person person = (Person)session.byNaturalId(Person.class )
                .using( "voterID", "ZAAXDFT435" )
                .load();
 
// For Version 1, this can be simplified as:
Person person = (Person)session.bySimpleNaturalId(Person.class )
                .load("ZAAXDFT435");
 
// In case of version 2 defined above:
Person person = (Person)session.byNaturalId(Person.class )
                .using("stdCode", "040")
                .using("landlineNumber","2345678")
                .load();

Обратите внимание, что сущность, возвращаемая загрузкой, является не просто прокси, а самой сущностью. Если мы хотим получить прокси, вместо load () мы должны использовать getReference () следующим образом:

1
2
3
4
session.byNaturalId(Person.class )
.using("stdCode", "040")
.using("landlineNumber","2345678")
.getReference();

Для согласованности новый подход был сделан доступным и для загрузки на основе идентификаторов.

1
2
public IdentifierLoadAccess byId(String entityName);
public IdentifierLoadAccess byId(Class entityClass);

Так что вместо session.load (Person.class, id) мы можем использовать session.byId (Person.class) .getReference (id). И вместо session.get (Person.class, id) мы можем использовать session.byId (Person.class) .load (id)

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

Сценарий-1:

1. HQL-запрос для загрузки лица A с использованием свойств в entity-id. Запрос также кэшируется, т.е. query.setCacheable (true)
2. Другой человек B вставлен в таблицу Person.
3. Теперь снова загрузите A, используя тот же запрос, который мы использовали в шаге 1.
Вопрос: на шаге 3 будет ли сделан новый вызов базы данных для извлечения A из таблицы Person. Да или нет?

Ответ — да. Что происходит, так это то, что Hibernate внутренне поддерживает кэш отметок времени. Этот кэш отметок времени записывает время, когда конкретная управляемая таблица Hibernate была изменена. Теперь на шаге (3) Hibernate видит, что это кешированный запрос. Но перед возвратом сущности, существующей в кеше, она проверяет, являются ли кэшированные результаты более старыми по отношению к времени модификации таблицы. Теперь, когда таблица изменилась после кэширования, Hibernate снова делает новый запрос.

Чтобы понять это подробнее, давайте рассмотрим следующий сценарий: давайте у нас будет только запись в таблице Person с именем Rama

Сценарий-2:

а. Выполните кэшированный запрос, чтобы получить список людей с именем, совпадающим с «Рама»: «от Персона, где имя человека =« Рама »»
б. Вставьте запись в Лицо, чье имя тоже «Рама». Это не проблема, так как имя не определено как уникальное свойство
с. Теперь выполните запрос на шаге (а) еще раз.

Первоначально на шаге (а) мы получаем только запись. Но на шаге (c) Hibernates снова ударил по DB, хотя результат кешируется. Это происходит из-за недействительности кэша меток времени. Hibernate просто проверьте, была ли таблица изменена или нет, прежде чем возвращать объект из кэша. Но это не беспокоит, как таблица обновлялась, будь то обновление, вставка или так далее.

Но в первом сценарии, который мы рассмотрели, эта проверка валидации кажется совершенно неактуальной, поскольку вставленная запись не имеет никакого отношения к загруженной сущности. Эту проверку можно обойти, если мы используем натуральные идентификаторы для извлечения сущности. Когда используется натуральный идентификатор, гарантируется, что результат не изменится даже после модификации базы данных. Ранее, когда у нас не было поддержки загрузки сущностей с использованием натуральных идентификаторов, мы имеем возможность использовать натуральные идентификаторы в Criteria API. Мы можем использовать следующее на шагах (1) и (3) Сценария-1

1
2
3
4
5
session.createCriteria(Person.class).add(
        Restrictions.naturalId().set("stdCode", person.getStdCode()).
        set("landlineNumber", person.getLandlineNumber())).
        setCacheable(true).
        uniqueResult();

Когда для выборки объекта используются естественные идентификаторы, проверка кэша меток времени будет обойдена. Так что теперь, если я заменю шаги (1) и (3) первого сценария этим критерием вместо запроса, база данных будет поражена только один раз. Вместо Restrictions.naturalId, если бы мы использовали Restrictions.eq, база данных была бы повреждена дважды. Также, если вы используете последние версии Hibernate, мы можем использовать новый API вместо построения критериев.

Ссылка: Natural Ids в Hibernate от нашего партнера JCG Prasanth G в блоге prasanthnath .