Как работает Optional с методами findById и getOne в JPA
Что проверяют
Понимаете ли вы разницу между загрузкой сущности и получением ссылки на неё и знаете ли, что getOne давно устарел. Следом обычно просят показать, где ссылка экономит запрос, и объяснить, откуда берётся LazyInitializationException.
Как отвечать
Два метода делают принципиально разное:
findById(id): объявлен вCrudRepository, внутри вызываетEntityManager.find(). Сначала смотрит в контекст персистентности (кэш первого уровня), если сущности там нет, делаетSELECT. ВозвращаетOptional<T>: строка нашлась, будет значение, не нашлась, будетOptional.empty();getOne(id): изJpaRepository, внутриEntityManager.getReference(). Если сущности нет в контексте, базу не трогает и возвращает прокси, в котором заполнен только идентификатор. Запрос уйдёт при первом обращении к другому полю, и если строки нет, тогда и вылетитEntityNotFoundException.
getOne помечен устаревшим в Spring Data JPA 2.5, его сменил getById, который в 2.7 тоже устарел в пользу getReferenceById. Поведение у всех трёх одинаковое, актуальное имя getReferenceById.
Когда что брать. Нужны данные сущности: findById(id).orElseThrow(...), и вы сами решаете, что делать с отсутствием, например отдать 404. Нужно только связать сущности по внешнему ключу: order.setCustomer(customerRepository.getReferenceById(customerId)), и Hibernate запишет customer_id, не выбирая покупателя из базы.
Optional в репозиториях появился со Spring Data 2.0, до этого был findOne, который возвращал null.
Где спотыкаются
Пишут findById(id).get(). Это та же проверка на null, только упавшая с NoSuchElementException. Используйте orElseThrow с исключением, которое понятно вызывающему коду.
Считают, что getReferenceById проверяет существование. Прокси создаётся для любого ID, и ошибка всплывёт позже и в неожиданном месте, иногда уже при сериализации в JSON.
Возвращают прокси из сервиса наружу. После закрытия сессии обращение к его полям даёт LazyInitializationException, а Jackson спотыкается на служебных полях прокси Hibernate.