При использовании методов 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.

Мои вопросы:

  • Почему не удается выполнить метод getOne(id)?
  • Когда я должен использовать метод getOne(id)?

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

В соответствии с API getOne:

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

В соответствии с API findOne:

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

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

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

Спасибо заранее.

Ответ 1

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 и т.д. В будущем без чрезмерного рефакторинга: )

Ответ 2

Основное отличие состоит в том, что 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!!!
}

Ответ 3

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 как:

T findOne(ID primaryKey);

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

<S extends T> Optional<S> findOne(Example<S> example);

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

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

Optional<T> findById(ID id); 

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

Таким образом, фактический выбор теперь находится между Optional<T> findById(ID id) и T getOne(ID id).


Два разных метода, основанные на двух разных методах поиска JPA EntityManager

1) Optional<T> findById(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) заявляет (акцент мой):

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

Фактически, эталонная терминология действительно является платой, и JPA API не определяет какой-либо метод getOne().
Поэтому лучше всего понять, что делает оболочка 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> T getReference(Class<T> entityClass,
                              Object primaryKey);

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

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

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

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

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


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

В завершение два вопроса для разработчиков Spring-Data-JPA:

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

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

Ответ 4

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

Ответ 5

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

У меня есть тестирование spring + hibernate + dozer + Mysql project. Быть ясным.

У меня есть 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

}

Ответ 6

Хотя spring.jpa.open-in-view было true, у меня не было никаких проблем с getOne, но после установки его в false я получил LazyInitializationException. Тогда проблема была решена путем замены на findById.
Хотя есть и другое решение без замены метода getOne, это @Transactional для метода, который вызывает repository.getOne(id). Таким образом, транзакция будет существовать, и сессия не будет закрыта в вашем методе, и при использовании объекта не будет никакого исключения LazyInitializationException.

Ответ 7

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

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

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