«1. Введение

Метод getReference() класса EntityManager был частью спецификации JPA с первой версии. Однако этот метод сбивает с толку некоторых разработчиков, поскольку его поведение зависит от базового поставщика сохраняемости.

В этом уроке мы объясним, как использовать метод getReference() в Hibernate EntityManager.

2. Операции выборки EntityManager

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

2.1. find()

find() является наиболее распространенным методом выборки сущностей:

Game game = entityManager.find(Game.class, 1L);

Этот метод инициализирует сущность, когда мы ее запрашиваем.

2.2. getReference()

Подобно методу find(), getReference() также является еще одним способом извлечения сущностей:

Game game = entityManager.getReference(Game.class, 1L);

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

Далее давайте посмотрим, как эти два метода ведут себя в различных сценариях.

3. Пример использования

Чтобы продемонстрировать операции выборки EntityManager, мы создадим две модели, Game и Player, в качестве нашей области, в которой многие игроки могут быть вовлечены в одну и ту же игру.

3.1. Модель домена

Во-первых, давайте определим объект под названием Game:

@Entity
public class Game {

    @Id
    private Long id;

    private String name;

    // standard constructors, getters, setters

}

Затем мы определим наш объект Player:

@Entity
public class Player {

    @Id
    private Long id;

    private String name;

    // standard constructors, getters, setters

}

3.2. Настройка отношений

Нам нужно настроить отношение @ManyToOne от игрока к игре. Итак, давайте добавим свойство игры к нашей сущности Player:

@ManyToOne
private Game game;

4. Тестовые примеры

Перед тем, как мы начнем писать наши тестовые методы, рекомендуется отдельно определить наши тестовые данные:

entityManager.getTransaction().begin();

entityManager.persist(new Game(1L, "Game 1"));
entityManager.persist(new Game(2L, "Game 2"));
entityManager.persist(new Player(1L,"Player 1"));
entityManager.persist(new Player(2L, "Player 2"));
entityManager.persist(new Player(3L, "Player 3"));

entityManager.getTransaction().commit();

Кроме того, чтобы изучить базовые SQL-запросы, мы должны настроить свойство Hibernate hibernate.show_sql в файле persistence.xml:

<property name="hibernate.show_sql" value="true"/>

4.1. Обновление полей сущности

Сначала мы проверим наиболее распространенный способ обновления сущности с помощью метода find().

Итак, давайте сначала напишем тестовый метод для извлечения объекта Game, а затем просто обновим его поле имени:

Game game1 = entityManager.find(Game.class, 1L);
game1.setName("Game Updated 1");

entityManager.persist(game1);

Запуск тестового метода показывает нам выполненные SQL-запросы:

Hibernate: select game0_.id as id1_0_0_, game0_.name as name2_0_0_ from Game game0_ where game0_.id=?
Hibernate: update Game set name=? where id=?

Как мы заметили , запрос SELECT в таком случае выглядит ненужным. Поскольку нам не нужно читать какое-либо поле сущности Game перед нашей операцией обновления, нам интересно, есть ли способ выполнить только запрос UPDATE.

Итак, давайте посмотрим, как ведет себя метод getReference() в том же сценарии:

Game game1 = entityManager.getReference(Game.class, 1L);
game1.setName("Game Updated 2");

entityManager.persist(game1);

Удивительно, но результат запущенного тестового метода остается прежним, и мы видим, что запрос SELECT остается.

Как мы видим, Hibernate выполняет запрос SELECT, когда мы используем getReference() для обновления поля сущности.

Таким образом, использование метода getReference() не позволяет избежать дополнительного запроса SELECT, если мы выполняем какой-либо установщик полей прокси-объекта.

4.2. Удаление объектов

Аналогичный сценарий может произойти, когда мы выполняем операции удаления.

Давайте определим еще два тестовых метода для удаления объекта Player:

Player player2 = entityManager.find(Player.class, 2L);
entityManager.remove(player2);
Player player3 = entityManager.getReference(Player.class, 3L);
entityManager.remove(player3);

Hibernate: select
    player0_.id as id1_1_0_,
    player0_.game_id as game_id3_1_0_,
    player0_.name as name2_1_0_,
    game1_.id as id1_0_1_,
    game1_.name as name2_0_1_ from Player player0_
    left outer join Game game1_ on player0_.game_id=game1_.id
    where player0_.id=?
Hibernate: delete from Player where id=?

Запуск этих тестовых методов показывает нам те же запросы:

Аналогично, для операций удаления результат аналогичен . Даже если мы не читаем ни одного поля сущности Player, Hibernate также выполняет дополнительный запрос SELECT.

Следовательно, нет никакой разницы, выбираем ли мы метод getReference() или find() при удалении существующего объекта.

На данный момент нам интересно, имеет ли вообще значение getReference()? Давайте перейдем к отношениям сущностей и выясним.

4.3. Обновление отношений сущностей

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

Game game1 = entityManager.find(Game.class, 1L);

Player player1 = entityManager.find(Player.class, 1L);
player1.setGame(game1);

entityManager.persist(player1);

Давайте добавим еще один метод для демонстрации участия игрока в игре, просто обновив игровое свойство игрока:

Hibernate: select game0_.id as id1_0_0_, game0_.name as name2_0_0_ from Game game0_ where game0_.id=?
Hibernate: select
    player0_.id as id1_1_0_,
    player0_.game_id as game_id3_1_0_,
    player0_.name as name2_1_0_,
    game1_.id as id1_0_1_,
    game1_.name as name2_0_1_ from Player player0_
    left outer join Game game1_ on player0_.game_id=game1_.id
    where player0_.id=?
Hibernate: update Player set game_id=?, name=? where id=?

Запуск теста дает нам аналогичный результат еще раз, и мы можем см. запросы SELECT при использовании метода find():

Game game2 = entityManager.getReference(Game.class, 2L);

Player player1 = entityManager.find(Player.class, 1L);
player1.setGame(game2);

entityManager.persist(player1);

Теперь давайте определим еще один тест, чтобы увидеть, как метод getReference() работает в этом случае:

Hibernate: select
    player0_.id as id1_1_0_,
    player0_.game_id as game_id3_1_0_,
    player0_.name as name2_1_0_,
    game1_.id as id1_0_1_,
    game1_.name as name2_0_1_ from Player player0_
    left outer join Game game1_ on player0_.game_id=game1_.id
    where player0_.id=?
Hibernate: update Player set game_id=?, name=? where id=?

«

«Будем надеяться, что запуск теста даст нам ожидаемое поведение:

И мы видим, что Hibernate не выполняет запрос SELECT для объекта Game, когда мы используем getReference() на этот раз.

Итак, в данном случае рекомендуется выбрать getReference(). Это связано с тем, что прокси-объекта Game достаточно для создания отношения из объекта Player — объект Game не нужно инициализировать.

Следовательно, использование getReference() может устранить ненужные обращения к нашей базе данных при обновлении отношений сущностей.

5. Кэш первого уровня Hibernate

entityManager.getTransaction().begin();
entityManager.persist(new Game(1L, "Game 1"));
entityManager.persist(new Player(1L, "Player 1"));
entityManager.getTransaction().commit();

entityManager.getTransaction().begin();
Game game1 = entityManager.getReference(Game.class, 1L);

Player player1 = entityManager.find(Player.class, 1L);
player1.setGame(game1);

entityManager.persist(player1);
entityManager.getTransaction().commit();

Иногда может сбивать с толку тот факт, что оба метода find() и getReference() в некоторых случаях могут не выполнять запросы SELECT.

Hibernate: update Player set game_id=?, name=? where id=?

Давайте представим ситуацию, когда наши объекты уже загружены в контексте персистентности до нашей операции:

Запуск теста показывает, что выполняется только запрос на обновление:

В таком случае мы следует заметить, что мы не видим никаких запросов SELECT, используем ли мы find() или getReference(). Это связано с тем, что наши сущности кэшируются в кеше первого уровня Hibernate.

В результате, когда наши объекты хранятся в кеше первого уровня Hibernate, методы find() и getReference() действуют одинаково и не затрагивают нашу базу данных.

6. Различные реализации JPA

В качестве последнего напоминания, мы должны знать, что поведение метода getReference() зависит от основного поставщика постоянства.

В соответствии со спецификацией JPA 2 провайдеру постоянства разрешено генерировать исключение EntityNotFoundException при вызове метода getReference(). Таким образом, для других поставщиков постоянства это может отличаться, и мы можем столкнуться с EntityNotFoundException при использовании getReference().

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

<property name="hibernate.jpa.compliance.proxy" value="true"/>

В качестве альтернативы Hibernate предоставляет свойство конфигурации, предлагающее самоуверенный способ для тех, кто хочет следовать спецификации JPA.

В таком случае мы можем рассмотреть возможность установки для свойства hibernate.jpa.compliance.proxy значения true:

С этой настройкой Hibernate инициализирует прокси объекта в любом случае, что означает выполнение запроса SELECT. даже когда мы используем getReference().

7. Заключение