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

мои вопросы:

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

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

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

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

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

возвращает объект по его ID.

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

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

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

6 97

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() over 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 взаимодействие.
Этот метод представляет собой запрос по примеру поиска, и вы не хотите, чтобы это было заменой.

фактически, метод с тем же поведением все еще существует в новом 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

}

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

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

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