При использовании методов getOne и findOne Spring Data JPA

У меня есть прецедент, где он вызывает следующее:

@Override @Transactional(propagation=Propagation.REQUIRES_NEW) public UserControl getUserControlById(Integer id){ return this.userControlRepository.getOne(id); } 

Обратите внимание, что @Transactional имеет Propagation.REQUIRES_NEW, и в репозитории используется getOne . Когда я запускаю приложение, я получаю следующее сообщение об ошибке:

 Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session ... 

Но если я меняю getOne(id) на findOne(id) все работает нормально.

BTW, как раз перед тем, как вариант использования вызывает метод getUserControlById , он уже вызвал метод insertUserControl

 @Override @Transactional(propagation=Propagation.REQUIRES_NEW) public UserControl insertUserControl(UserControl userControl) { return this.userControlRepository.save(userControl); } 

Оба метода – Propagation.REQUIRES_NEW, потому что я делаю простой контрольный контроль.

Я использую метод getOne потому что он определен в интерфейсе JpaRepository, и мой интерфейс репозитория простирается оттуда, я, конечно, работаю с JPA.

Интерфейс JpaRepository простирается от CrudRepository . Метод findOne(id) определен в CrudRepository .

Мои вопросы:

  1. Зачем отказываться от метода getOne(id) ?
  2. Когда я должен использовать метод getOne(id) ?

Я работаю с другими репозиториями, и все используют метод getOne(id) и все работает нормально, только когда я использую Propagation.REQUIRES_NEW, он терпит неудачу.

Согласно API getOne :

Возвращает ссылку на объект с данным идентификатором.

Согласно API findOne :

Получает объект по его идентификатору.

3) Когда я должен использовать метод findOne(id) ?

4) Какой метод рекомендуется использовать?

Заранее спасибо.

1. Почему метод getOne (id) терпит неудачу?

См. Этот раздел в документах . Вы можете переопределить уже существующую транзакцию. Однако, без дополнительной информации об этом трудно ответить.

2. Когда я должен использовать метод getOne (id)?

Не копаясь во внутренних деталях Spring Data JPA, разница, по-видимому, связана с механизмом, используемым для извлечения объекта.

Если вы посмотрите на JavaDoc для getOne(ID) разделе « См. Также :

 See Also: EntityManager.getReference(Class, Object) 

кажется, что этот метод просто делегирует реализацию менеджера сущностей JPA.

Однако в документах для findOne(ID) этого не упоминается.

Ключ также относится к именам репозиториев. JpaRepository специфичен для JPA и поэтому может делегировать вызовы диспетчеру сущности, если это необходимо. CrudRepository является агностиком используемой технологии персистентности. Послушайте . Он используется в качестве интерфейса маркера для нескольких технологий сохранения, таких как JPA, Neo4J и т. Д.

Таким образом, в двух методах для ваших случаев использования на самом деле нет «разницы», просто 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 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 следующим образом:

 T findOne(ID primaryKey); 

Теперь единственный findOne() который вы найдете в CrudRepository – это тот, который определен в интерфейсе QueryByExampleExecutor как:

  Optional findOne(Example example); 

Это реализовано, наконец, SimpleJpaRepository , по умолчанию реализация интерфейса CrudRepository .
Этот метод является поиском запроса, и вы не хотите его заменять.

Фактически, метод с таким же поведением все еще присутствует в новом API, но имя метода изменилось.
Он был переименован из findOne() в findById() в интерфейсе CrudRepository :

 Optional findById(ID id); 

Теперь он возвращает Optional . Что не так плохо, чтобы предотвратить NullPointerException .

Итак, фактический выбор теперь между Optional findById(ID id) и T getOne(ID id) .


Два разных метода, основанных на двух различных методах извлечения JPA EntityManager

1) Optional findById(ID id) javadoc утверждает, что он:

Получает объект по его идентификатору.

Когда мы изучаем реализацию, мы можем видеть, что он полагается на EntityManager.find() для выполнения поиска:

 public Optional findById(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); Class domainType = getDomainClass(); if (metadata == null) { return Optional.ofNullable(em.find(domainType, id)); } LockModeType type = metadata.getLockModeType(); Map 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 find(Class entityClass, Object primaryKey, Map properties); 

В его javadoc говорится:

Найти по первичному ключу, используя указанные свойства

Таким образом, ожидание загруженного объекта кажется ожидаемым.

2) В то время как javadoc T getOne(ID id) (акцент мой):

Возвращает ссылку на объект с данным идентификатором.

Фактически, эталонная терминология действительно является платой, и JPA API не указывает какой-либо метод getOne() .
Поэтому самое лучшее, что нужно сделать, чтобы понять, что делает shell Spring, – это посмотреть на реализацию:

 @Override public T getOne(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); return em.getReference(getDomainClass(), id); } 

Здесь em.getReference() – метод EntityManager объявленный как:

 public  T getReference(Class entityClass, Object primaryKey); 

И, к счастью, javadoc EntityManager лучше определил свое намерение (акцент мой):

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

Таким образом, вызов getOne() может возвращать лениво получаемую сущность.
Здесь ленивая выборка не относится к отношениям объекта, а сама сущность.

Это означает, что если мы вызываем getOne() а затем контекст Persistence закрываем, объект может никогда не загружаться, и поэтому результат действительно непредсказуем.
Например, если прокси-объект сериализуется, вы можете получить null ссылку как результат сериализации или если метод запускается на прокси-объекте, генерируется исключение, такое как LazyInitializationException .
Таким образом, в подобной ситуации бросок EntityNotFoundException который является основной причиной использования getOne() для обработки экземпляра, который не существует в базе данных, поскольку ситуация с ошибкой может никогда не выполняться, пока сущность не существует.

В любом случае, чтобы обеспечить его загрузку, вам нужно манипулировать объектом во время сеанса. Вы можете сделать это, вызвав любой метод для объекта.
Или лучше альтернативное использование findById(ID id) а не.


Почему так непонятный API?

Чтобы закончить, два вопроса для разработчиков Spring-Data-JPA:

  • почему бы не получить более четкую документацию для getOne() ? Entity ленивая загрузка действительно не деталь.

  • зачем вам вводить getOne() для EM.getReference() ?
    Почему бы просто не придерживаться обернутого метода: getReference() ? Этот метод EM действительно очень специфичен, в то время как getOne() передает столь простую обработку.

Методы getOne возвращают только ссылку из БД (ленивая загрузка). Таким образом, в основном вы находитесь за пределами транзакции ( Transactional вы объявили в classе обслуживания, не учитывается), и возникает ошибка.

Я действительно нахожу очень трудным из вышеупомянутых ответов. С точки зрения отладки я почти потратил 8 часов, чтобы узнать глупую ошибку.

У меня есть тест Spring + hibernate + dozer + Mysql. Быть ясным.

У меня есть User entity, Book Entity. Вы выполняете расчеты отображения.

Были множественные книги, привязанные к одному пользователю. Но в 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 

}

Вышеприведенный код не извлекал книги, которые читает пользователь, скажем.

Список bookList всегда был нулевым из-за 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 

}

У меня была аналогичная проблема, понимающая, почему JpaRespository.getOne (id) не работает и выдает ошибку.

Я пошел и перешел на JpaRespository.findById (id), который требует, чтобы вы вернули опцию.

Вероятно, это мой первый комментарий к StackOverflow.

Interesting Posts
Давайте будем гением компьютера.