Перейти к основному содержимому

Краткое руководство по EntityManager#getReference()

· 7 мин. чтения

1. Введение

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

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

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

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

2.1. найти()

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

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

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

2.2. получить ссылку()

Подобно методу 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 к Game . Итак, давайте добавим игровое свойство к нашей сущности 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. Обновление отношений сущностей

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

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

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

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

entityManager.persist(player1);

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

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=?

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

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

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

entityManager.persist(player1);

Надеюсь, запуск теста даст нам ожидаемое поведение:

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. Спящий режим кэша первого уровня

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

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

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();

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

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() по умолчанию, чтобы сохранить обращение к базе данных, когда это возможно . Соответственно, он не генерирует исключение, когда мы извлекаем прокси-объекты, даже если они не существуют в базе данных.

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

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

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

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

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

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

Как всегда, все примеры кода и другие тестовые примеры для этого руководства доступны на GitHub .