Рубрики
Без рубрики

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

Узнайте, как метод EntityManager#getReference Hibernate работает в различных случаях использования.

Автор оригинала: Yavuz Tas.

1. введение

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

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

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

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

2.1. найти()

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

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

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

2.2. getReference()

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

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

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

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

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

Чтобы продемонстрировать операции EntityManager fetch, мы создадим две модели, 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 отношение из Игрок к Игре . Итак, давайте добавим свойство 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 property в нашем persistence.xml :


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

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

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

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

Запуск теста дает нам аналогичный результат еще раз, и мы все еще можем видеть запросы 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=?

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

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() на этот раз .

Таким образом, в этом случае, похоже, хорошей практикой является выбор get Reference () . Это потому, что прокси Game entity достаточно для создания отношения из Player entity — сущность 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 :


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

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

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

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