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

EntityNotFoundException в Hibernate

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

1. Введение

В этом руководстве мы поговорим об исключении EntityNotFoundException из пакета javax.persistence . Мы рассмотрим случаи, когда может возникнуть это исключение, а затем напишем тесты для этих случаев.

2. Когда возникает исключение EntityNotFoundException ?

Документация Oracle для этого исключения определяет три ситуации, в которых поставщик сохраняемости может генерировать исключение EntityNotFoundException :

  • EntityManager.getReference для несуществующей сущности
  • EntityManager.refresh для объекта, которого нет в базе данных
  • EntityManager.lock с пессимистичной блокировкой сущности, не существующей в базе данных

Помимо этих трех вариантов использования, есть еще один, немного более неоднозначный. Это исключение также может возникать при работе с отношениями @ManyToOne и отложенной загрузкой .

Когда мы используем аннотацию @ManyToOne , объект, на который делается ссылка, должен существовать. Обычно это обеспечивается целостностью базы данных с использованием внешних ключей. Если мы не используем внешние ключи в нашей реляционной модели или наша база данных несовместима, мы можем увидеть исключение EntityNotFoundException при выборке сущностей. Мы проиллюстрируем это в следующем разделе на примере.

3. Исключение EntityNotFoundException на практике

Во-первых, давайте рассмотрим один более простой вариант использования. В предыдущем разделе мы упоминали метод getReference . Мы используем этот метод для получения прокси определенного объекта. Этот прокси имеет только инициализированное поле первичного ключа. Когда мы вызываем геттер для этой прокси-сущности, провайдер персистентности инициализирует остальные поля. Если сущность не существует в базе данных, мы получаем EntityNotFoundException :

@Test(expected = EntityNotFoundException.class)
public void givenNonExistingUserId_whenGetReferenceIsUsed_thenExceptionIsThrown() {
User user = entityManager.getReference(User.class, 1L);
user.getName();
}

Сущность пользователя элементарна. Он имеет только два поля и никаких отношений. Мы создаем прокси-объект со значением первичного ключа 1L в первой строке теста. После этого мы вызываем getter для этого прокси-объекта. Поставщик постоянства пытается получить объект по первичному ключу, и, поскольку запись не существует, выдается исключение EntityNotFoundException .

В следующем примере мы будем использовать разные объекты домена. Мы создадим объекты Item и Category с двунаправленной связью между ними:

@Entity
public class Item implements Serializable {
@Id
@Column(unique = true, nullable = false)
private long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Category category;
// getters and setters
}
@Entity
public class Category implements Serializable {
@Id
@Column(unique = true, nullable = false)
private long id;
private String name;
@OneToMany
@JoinColumn(name = "category_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private List<Item> items = new ArrayList<>();
// getters and setters
}

Обратите внимание, что мы используем ленивую выборку для аннотации @ManyToOne . Также мы используем @ForeignKey для удаления ограничения из базы данных:

@Test(expected = EntityNotFoundException.class)
public void givenItem_whenManyToOneEntityIsMissing_thenExceptionIsThrown() {
entityManager.createNativeQuery("Insert into Item (category_id, name, id) values (1, 'test', 1)").executeUpdate();
entityManager.flush();
Item item = entityManager.find(Item.class, 1L);
item.getCategory().getName();
}

В этом тесте мы получаем объект Item по идентификатору. Метод find вернет объект Item без полностью инициализированной категории , поскольку мы используем FetchType.LAZY (устанавливается только идентификатор , аналогично предыдущему примеру). Когда мы вызываем геттер для объекта категории , провайдер постоянства попытается извлечь объект из базы данных, и мы получим исключение, поскольку запись не существует.

Отношение @ManyToOne предполагает, что указанный объект существует. Внешние ключи и целостность базы данных гарантируют существование этих сущностей. Если это не так, существует обходной путь для игнорирования отсутствующей сущности.

Объединение @NotFound(action = NotFoundAction.IGNORE) с аннотацией @ManyToOne не позволит поставщику постоянства генерировать EntityNotFoundException , но нам придется обрабатывать отсутствующий объект вручную, чтобы избежать NullPointerException .

4. Вывод

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

Как обычно, мы можем найти код из этой статьи на GitHub .