У меня есть случай использования, когда он вызывает следующее:
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
return this.userControlRepository.getOne(id);
}
Обратите внимание на @Transactional
has Propagation.REQUIRES_NEW, а хранилище использует getOne . Когда я запускаю приложение, я получаю следующее сообщение об ошибке:
Exception in thread "main" org.hibernate.LazyInitializationException:
could not initialize proxy - no Session
...
Но если я изменю getOne(id)
на findOne(id)
все работает отлично.
Кстати, перед тем, как сценарий использования вызывает метод getUserControlById , он уже вызвал метод insertUserControl
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
return this.userControlRepository.save(userControl);
}
Оба метода являются Propagation.REQUIRES_NEW, потому что я делаю простой контроль аудита .
Я использую getOne
метод, потому что он определен в интерфейсе JpaRepository, и мой интерфейс Repository расширяется оттуда, я, конечно, работаю с JPA.
Интерфейс JpaRepository происходит от CrudRepository . findOne(id)
Метод определен в CrudRepository
.
Мои вопросы:
- Почему неудачный
getOne(id)
метод? - Когда я должен использовать
getOne(id)
метод?
Я работаю с другими репозиториями, и все используют getOne(id)
метод, и все работают нормально, только когда я использую Propagation.REQUIRES_NEW, это не удается.
Согласно API getOne :
Возвращает ссылку на сущность с заданным идентификатором.
Согласно API findOne :
Извлекает объект по его идентификатору.
3) Когда я должен использовать findOne(id)
метод?
4) Какой метод рекомендуется использовать?
Заранее спасибо.
источник
Ответы:
TL; DR
T findOne(ID id)
(имя в старом API) /Optional<T> findById(ID id)
(имя в новом API) зависит от того,EntityManager.find()
что выполняет загрузку объекта .T getOne(ID id)
полагается наEntityManager.getReference()
то, что объект выполняет ленивую загрузку . Таким образом, чтобы обеспечить эффективную загрузку объекта, необходимо вызвать метод для него.findOne()/findById()
действительно более понятный и простой в использовании, чемgetOne()
.Так что в большинстве случаев благосклонность
findOne()/findById()
законченаgetOne()
.Изменение API
По крайней мере,
2.0
версия,Spring-Data-Jpa
модифицированнаяfindOne()
.Ранее это было определено в
CrudRepository
интерфейсе как:Теперь единственный
findOne()
метод, который вы найдете вCrudRepository
том, который определен вQueryByExampleExecutor
интерфейсе как:Наконец
SimpleJpaRepository
, это реализовано реализациейCrudRepository
интерфейса по умолчанию .Этот метод является запросом по примеру поиска, и вы не хотите использовать его в качестве замены.
Фактически, метод с таким же поведением все еще присутствует в новом API, но имя метода изменилось.
Он был переименован из
findOne()
кfindById()
вCrudRepository
интерфейсе:Теперь он возвращает
Optional
. Что не так плохо, чтобы предотвратитьNullPointerException
.Итак, фактический выбор теперь между
Optional<T> findById(ID id)
иT getOne(ID id)
.Два разных метода, основанные на двух разных методах поиска JPA EntityManager
1)
Optional<T> findById(ID id)
Javadoc утверждает, что это:Когда мы посмотрим на реализацию, мы увидим, что она зависит от
EntityManager.find()
выполнения поиска:И здесь
em.find()
приведенEntityManager
метод , объявленный как:Его Javadoc утверждает:
Таким образом, получение загруженного объекта кажется ожидаемым.
2) В то время как
T getOne(ID id)
Javadoc утверждает (акцент мой):Фактически, эталонная терминология действительно является платой, и JPA API не определяет какой-либо
getOne()
метод.Поэтому лучше всего понять, что делает оболочка Spring, - посмотреть на реализацию:
Здесь
em.getReference()
приведенEntityManager
метод , объявленный как:И, к счастью,
EntityManager
Javadoc лучше определил свое намерение (акцент мой):Таким образом, вызов
getOne()
может вернуть лениво извлеченную сущность.Здесь ленивое извлечение относится не к отношениям сущности, а к самой сущности.
Это означает, что если мы вызываем,
getOne()
а затем контекст постоянства закрывается, сущность может никогда не загрузиться, и поэтому результат действительно непредсказуем.Например, если прокси-объект сериализуется, вы можете получить
null
ссылку как сериализованный результат или, если метод прокси-объекта вызывается, исключение, такое какLazyInitializationException
выдается.Таким образом, в такой ситуации выбрасывание
EntityNotFoundException
этого является основной причиной использованияgetOne()
для обработки экземпляра, который не существует в базе данных, поскольку ситуация ошибки может никогда не выполняться, пока объект не существует.В любом случае, чтобы гарантировать его загрузку, вы должны манипулировать сущностью во время открытия сеанса. Вы можете сделать это, вызвав любой метод на объекте.
Или лучшее альтернативное использование
findById(ID id)
вместо.Почему такой неясный API?
В заключение два вопроса для разработчиков Spring-Data-JPA:
почему нет более четкой документации для
getOne()
? Сущность ленивой загрузки действительно не является деталью.зачем вводить,
getOne()
чтобы обернутьEM.getReference()
?Почему бы не просто придерживаться обернутой методы:
getReference()
? Этот EM-метод действительно очень специфичен, хотя иgetOne()
обеспечивает очень простую обработку.источник
getOne()
использует ленивую загрузку и выбрасывает,EntityNotFoundException
если элемент не найден.findById()
загружается сразу и возвращает ноль, если не найден. Поскольку есть некоторые непредсказуемые ситуации с getOne (), рекомендуется вместо этого использовать findById ().Основное отличие заключается в том, что
getOne
лениво загружен иfindOne
нет.Рассмотрим следующий пример:
источник
1. Почему метод getOne (id) не работает?
Смотрите этот раздел в документации . Вы можете переопределить уже существующую транзакцию, что может быть причиной проблемы. Однако без дополнительной информации на этот вопрос трудно ответить.
2. Когда мне следует использовать метод getOne (id)?
Без углубления во внутреннюю среду Spring Data JPA, похоже, различие заключается в механизме, используемом для извлечения сущности.
Если вы посмотрите на JavaDoc для
getOne(ID)
под Престолом Также :похоже, что этот метод просто делегирует реализацию менеджера сущностей JPA.
Тем не менее, документы для
findOne(ID)
этого не упоминают.Подсказка также в названиях хранилищ.
JpaRepository
является специфическим для JPA и, следовательно, может делегировать вызовы менеджеру сущностей, если это необходимо.CrudRepository
не зависит от используемой технологии персистентности. Посмотрите здесь . Он используется в качестве интерфейса маркера для нескольких постоянных технологий, таких как JPA, Neo4J и т. Д.Так что на самом деле нет разницы в этих двух методах для ваших вариантов использования, просто это
findOne(ID)
более общий характер, чем более специализированныйgetOne(ID)
. Какой из них вы используете, зависит от вас и вашего проекта, но я лично буду придерживаться этого,findOne(ID)
поскольку это сделает ваш код менее специфичным для реализации и откроет двери для перехода к таким вещам, как MongoDB и т. Д. В будущем без чрезмерного рефакторинга :)источник
there's not really a 'difference' in the two methods
, потому что действительно существует большая разница в том, как извлекается сущность и что вы должны ожидать от метода. Ответ ниже @davidxxx очень хорошо это подчеркивает, и я думаю, что все, кто использует Spring Data JPA, должны знать об этом. В противном случае это может привести к головной боли.Эти
getOne
методы возвращают только ссылки из БДА (отложенная загрузка). Таким образом, в основном вы находитесь за пределами транзакции (то, чтоTransactional
вы были объявлены в классе обслуживания, не рассматривается), и возникает ошибка.источник
Я действительно нахожу очень сложным из приведенных выше ответов. С точки зрения отладки я почти потратил 8 часов, чтобы понять глупую ошибку.
У меня есть тестирование Spring + Hibernate + Dozer + Mysql проекта. Чтобы было ясно.
У меня есть сущность пользователя, сущность книги. Вы делаете расчеты картирования.
Были ли несколько книг привязаны к одному пользователю. Но в UserServiceImpl я пытался найти его с помощью getOne (userId);
Результат отдыха
}
Приведенный выше код не получил книги, которые читал пользователь, скажем.
BookList всегда был нулевым из-за getOne (ID). После изменения найти один (ID). Результат
}
источник
Хотя spring.jpa.open-in-view было истинным, у меня не было никаких проблем с getOne, но после установки в false я получил LazyInitializationException. Тогда проблема была решена путем замены на findById.
Хотя есть и другое решение без замены метода getOne, это @Transactional для метода, который вызывает repository.getOne (id). Таким образом, транзакция будет существовать, и сессия не будет закрыта в вашем методе, и при использовании объекта не будет никакого исключения LazyInitializationException.
источник
У меня была похожая проблема с пониманием, почему JpaRespository.getOne (id) не работает и выдает ошибку.
Я пошел и изменить на JpaRespository.findById (id), который требует, чтобы вы вернули Optional.
Вероятно, это мой первый комментарий к StackOverflow.
источник