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 .