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 .