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 .