При использовании методов getOne и findOne Spring Data JPA
у меня есть прецедент, когда он вызывает следующее:
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
return this.userControlRepository.getOne(id);
}
соблюдать @Transactional
и распространения.REQUIRES_NEW и репозиторий использует getOne. Когда я запускаю приложение, я получаю следующее сообщение об ошибке:
Exception in thread "main" org.hibernate.LazyInitializationException:
could not initialize proxy - no Session
...
но если я изменю getOne(id)
by findOne(id)
все работает нормально.
кстати, как раз перед использованием case вызывает getUserControlById метод, он уже назвал insertUserControl метод
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
return this.userControlRepository.save(userControl);
}
оба метода распространения.REQUIRES_NEW потому что я делаю простой аудит управление.
я использую getOne
метод, потому что он определен в JpaRepository интерфейс и мой интерфейс репозитория простирается оттуда, я работаю с JPA, конечно.
The JpaRepository интерфейс простирается от CrudRepository.
Элемент findOne(id)
метод определен в CrudRepository
.
мои вопросы:
- почему не
getOne(id)
способ? - когда я должен использовать
getOne(id)
способ?
Я работаю с другими репозиториями и все использовать getOne(id)
метод и все работает нормально, только когда я использую распространения.REQUIRES_NEW это не удается.
в соответствии с getOne API:
возвращает ссылку на объект с данным идентификатором.
в соответствии с findOne API:
возвращает объект по его ID.
3) Когда я должен использовать findOne(id)
способ?
4) Какой метод рекомендуется использовать?
спасибо заранее.
6 ответов:
1. Почему метод getOne (id) терпит неудачу?
раздел в документах. Переопределение уже существующей транзакции может вызвать проблему. Однако без дополнительной информации на этот вопрос трудно ответить.
2. Когда я должен использовать метод getOne (id)?
не копаясь во внутренние данные Spring JPA, разница, по-видимому, заключается в механизме, используемом для извлечения сущность.
если вы посмотрите на документация на
getOne(ID)
под См. Также:See Also: EntityManager.getReference(Class, Object)
похоже, что этот метод просто делегирует реализацию JPA entity manager.
на docs наfindOne(ID)
не упоминайте об этом.ключ также находится в названиях репозиториев.
JpaRepository
является специфичным для JPA и поэтому может делегировать вызовы диспетчеру сущностей, если это необходимо.CrudRepository
is агностик используемой технологии персистентности. посмотреть здесь. Он используется в качестве интерфейса маркера для нескольких технологий сохранения, таких как JPA, СУБД Neo4j etc.так что на самом деле нет "разницы" в двух методах для ваших случаев использования, это просто
findOne(ID)
является более общим, чем более специализированнымgetOne(ID)
. Какой из них вы используете, зависит от вас и вашего проекта, но я бы лично придерживалсяfindOne(ID)
поскольку это делает ваш код менее специфичным для реализации и открывает двери, чтобы перейти к таким вещам, как MongoDB и т. д. в будущем без слишком большого рефакторинга:)
основное различие заключается в том, что
getOne
лениво загружается иfindOne
нет.рассмотрим следующий пример:
public static String NON_EXISTING_ID = -1; ... MyEntity getEnt = myEntityRepository.getOne(NON_EXISTING_ID); MyEntity findEnt = myEntityRepository.findOne(NON_EXISTING_ID); if(findEnt != null) { findEnt.getText(); // findEnt is null - this code is not executed } if(getEnt != null) { getEnt.getText(); // Throws exception - no data found, BUT getEnt is not null!!! }
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()
overgetOne()
.
изменение API
по крайней мере, от
2.0
версияSpring-Data-Jpa
измененfindOne()
.
Ранее он был определен вCrudRepository
интерфейс :T findOne(ID primaryKey);
Итак, один
findOne()
метод, который вы найдете вCrudRepository
какой из них определен вQueryByExampleExecutor
интерфейс :<S extends T> Optional<S> findOne(Example<S> example);
это реализовано, наконец,
SimpleJpaRepository
реализация по умолчаниюCrudRepository
взаимодействие.
Этот метод представляет собой запрос по примеру поиска, и вы не хотите, чтобы это было заменой.фактически, метод с тем же поведением все еще существует в новом API, но имя метода изменилось.
Он был переименован изfindOne()
доfindById()
наCrudRepository
интерфейс :Optional<T> findById(ID id);
теперь он возвращает
Optional
. Что не так уж плохо предотвратитьNullPointerException
.Итак, фактический выбор теперь между
Optional<T> findById(ID id)
иT getOne(ID id)
.
два различных метода, которые полагаются на два различных метода извлечения JPA EntityManager
1) The
Optional<T> findById(ID id)
документация указано, что он :возвращает объект по его ID.
когда мы смотрим на реализацию, мы видим, что она опирается на
EntityManager.find()
выполнить поиска :public Optional<T> findById(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); Class<T> domainType = getDomainClass(); if (metadata == null) { return Optional.ofNullable(em.find(domainType, id)); } LockModeType type = metadata.getLockModeType(); Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap(); return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints)); }
и здесь
em.find()
этоEntityManager
метод, объявленный как :public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties);
его javadoc государств :
найти по первичному ключу, используя указанные свойства
Итак, получение загруженного объекта кажется ожидаемым.
2) в то время как
T getOne(ID id)
документация состояния (Курсив мой):возвращает a ссылка к сущности с заданным идентификатором.
в самом деле ссылка терминология действительно доска, и JPA API не указывает никаких
getOne()
метод.
Поэтому самое лучшее, что нужно сделать, чтобы понять, что делает Spring wrapper, - это изучить реализацию :@Override public T getOne(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); return em.getReference(getDomainClass(), id); }
здесь
em.getReference()
этоEntityManager
метод, объявленный как :public <T> T getReference(Class<T> entityClass, Object primaryKey);
и к счастью,
EntityManager
javadoc лучше определил свое намерение (акцент мой):получить экземпляр, состояние которого может быть лениво принесла. Если запрошенный экземпляра не существует в базе данных, EntityNotFoundException бросается при первом обращении к состоянию экземпляра. (Стойкость среда выполнения поставщика может создать исключение EntityNotFoundException когда вызывается getReference.)приложение не должно ожидать, что состояние экземпляра будет доступно при отсоединении, если только это не было доступ к приложению во время работы диспетчера сущностей открыть.
Итак, вызов
getOne()
может вернуть лениво извлеченный объект.
Здесь ленивая выборка относится не к отношениям сущности, а к самой сущности.это означает, что если мы вызываем
getOne()
и тогда контекст сохранения закрыт, сущность может никогда не загружаться, и поэтому результат действительно непредсказуем.
Например, если прокси-объект сериализован, вы можете получитьnull
ссылка как сериализованный результат или способ вызывается на прокси-объект, исключение, такое какLazyInitializationException
бросается.
Так что в такой ситуации, бросокEntityNotFoundException
это основная причина для использованияgetOne()
для обработки экземпляра, который не существует в базе данных, поскольку ситуация ошибки может никогда не выполняться, пока объект не существует.в любом случае, чтобы обеспечить его загрузку, вы должны манипулировать сущностью во время открытия сеанса. Вы можете сделать это, вызвав любой метод на сущности.
Или лучшее альтернативное использованиеfindById(ID id)
вместо.
почему так непонятно API ?
чтобы закончить, два вопроса для разработчиков Spring-Data-JPA:
почему бы не иметь более четкую документацию для
getOne()
? Сущность ленивая загрузка на самом деле не деталь.почему вы должны представить
getOne()
обертываниеEM.getReference()
?
Почему бы просто не придерживаться обернутого метода:getReference()
? Этот метод ЭМ действительно очень особенный покаgetOne()
передает такую простую обработку.
The
getOne
метод возвращает только ссылку из БД (ленивая загрузка). Так что в основном вы находитесь вне сделки (Transactional
вы были объявлены в классе обслуживания не считается), и ошибка возникает.
Я действительно нахожу очень трудным из приведенных выше ответов. С точки зрения отладки я почти потратил 8 часов, чтобы узнать глупую ошибку.
у меня есть тестирование spring+hibernate+dozer+Mysql project. Быть ясным.
у меня есть сущность пользователя, книга сущности. Вы делаете расчеты отображения.
были несколько книг, привязанных к одному пользователю. Но в UserServiceImpl я пытался найти его с помощью getOne (userId);
public UserDTO getById(int userId) throws Exception { final User user = userDao.getOne(userId); if (user == null) { throw new ServiceException("User not found", HttpStatus.NOT_FOUND); } userDto = mapEntityToDto.transformBO(user, UserDTO.class); return userDto; }
остальные результат
{ "collection": { "version": "1.0", "data": { "id": 1, "name": "TEST_ME", "bookList": null }, "error": null, "statusCode": 200 }, "booleanStatus": null
}
приведенный выше код не принес книги, которые читает пользователь Пусть говорят.
список книг всегда был null из-за getOne(ID). После изменения на findOne (ID). В результате получается
{ "collection": { "version": "1.0", "data": { "id": 0, "name": "Annama", "bookList": [ { "id": 2, "book_no": "The karma of searching", } ] }, "error": null, "statusCode": 200 }, "booleanStatus": null
}