getOne 및 findOne 메소드를 사용하는 경우 Spring Data JPA


154

다음을 호출하는 유스 케이스가 있습니다.

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

(가) 관찰 @TransactionalPropagation.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);
}

간단한 감사 제어를 수행하기 때문에 두 방법 모두 전파입니다 .REQUIRES_NEW

JpaRepository 인터페이스에 getOne정의되어 있기 때문에 메소드를 사용 하고 내 Repository 인터페이스가 거기에서 확장 되므로 물론 JPA와 함께 작업하고 있습니다.

JpaRepository 인터페이스에서 확장 CrudRepository . 이 findOne(id)방법은에 정의되어 CrudRepository있습니다.

내 질문은 :

  1. getOne(id)방법이 실패 합니까?
  2. 언제 getOne(id)방법을 사용해야 합니까?

다른 리포지토리와 함께 작업하고 있으며 모든 getOne(id)방법을 사용하며 전파를 사용할 때만 제대로 작동합니다 .REQUIRES_NEW 실패합니다.

getOne API 에 따르면 :

주어진 식별자를 가진 엔터티에 대한 참조를 반환합니다.

findOne API 에 따르면 :

ID로 엔티티를 검색합니다.

3) 언제 findOne(id)방법을 사용해야 합니까?

4) 어떤 방법을 사용하도록 권장됩니까?

미리 감사드립니다.


getOne을 사용하면 항상 객체! = null을 얻는 반면 findOne은 null을 전달하므로 getBase ()를 사용하여 데이터베이스에 객체의 존재를 테스트하지 마십시오.
Uwe Allner

답변:


137

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()당신이 찾게 될 유일한 방법 CrudRepositoryQueryByExampleExecutor인터페이스 에서 다음 과 같이 정의 된 것입니다 :

<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) Optional<T> findById(ID id)javadoc은 다음과 같이 말합니다.

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)javadoc 상태 (강조는 내 것) :

주어진 식별자를 가진 엔터티에 대한 참조 를 반환합니다 .

실제로 참조 용어는 실제로 보드이며 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);

그리고 다행히도 EntityManagerjavadoc은 의도를 더 잘 정의했습니다 (강조는 내 것입니다).

상태가 느리게 페치 될 수있는 인스턴스를 가져옵니다 . 요청 된 인스턴스가 데이터베이스에 없으면 인스턴스 상태에 처음 액세스 할 때 EntityNotFoundException이 발생 합니다 . 지속성 제공자 런타임은 getReference가 호출 될 때 EntityNotFoundException을 발생 시킬 수 있습니다 . 애플리케이션은 엔티티 관리자가 열려있는 동안 애플리케이션이 액세스하지 않는 한 분리시 인스턴스 상태가 사용 가능할 것으로 예상해서는 안됩니다 .

따라서 호출 getOne()하면 느리게 가져온 엔티티를 리턴 할 수 있습니다.
여기서 게으른 인출은 엔터티의 관계가 아니라 엔터티 자체의 관계를 나타냅니다.

이는 우리가 호출 getOne()한 다음 지속성 컨텍스트가 닫히면 엔티티가로드되지 않아 결과를 실제로 예측할 수 없음을 의미합니다.
예를 들어 프록시 객체가 직렬화 된 null경우 직렬화 된 결과로 참조를 가져 오거나 프록시 객체에서 메소드가 호출 된 경우 예외 LazyInitializationException가 발생합니다.
따라서 이러한 상황에서는 엔터티가 존재하지 않는 동안 오류 상황이 수행되지 않을 수 있기 때문에 데이터베이스에 존재하지 않는 인스턴스를 처리하는 EntityNotFoundException데 사용되는 주된 이유 getOne()입니다.

어쨌든 로딩을 보장하려면 세션이 열린 상태에서 엔티티를 조작해야합니다. 엔티티에서 메소드를 호출하여이를 수행 할 수 있습니다.
또는 findById(ID id)대신 더 나은 대안 사용 .


왜 그렇게 불분명 한 API입니까?

마무리하기 위해 Spring-Data-JPA 개발자에게 두 가지 질문이 있습니다.

  • 더 명확한 문서가없는 이유는 getOne()무엇입니까? 엔티티 지연 로딩은 실제로 세부 사항이 아닙니다.

  • getOne()랩 을 소개해야 EM.getReference()합니까?
    왜 단순히 래핑 된 방법을 고수하지 getReference()않습니까? 이 EM 방법은 매우 특별하지만 getOne() 간단한 처리를 제공합니다.


3
getOne ()이 EntityNotFoundException을 발생시키지 않는 이유가 혼란 스러웠지만 "인스턴스 상태에 처음 액세스하면 EntityNotFoundException이 발생합니다"라는 개념을 설명했습니다. 감사합니다
TheCoder

이 답변의 요약 : getOne()지연 로딩을 사용하고 EntityNotFoundException항목이 없으면 if를 던집니다 . findById()즉시로드하고 찾지 못하면 null을 반환합니다. getOne ()에는 예측할 수없는 상황이 있으므로 대신 findById ()를 사용하는 것이 좋습니다.
Janac Meena

124

기본적인 차이점은 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!!!
}

1
지연로드 안 됨은 엔티티를 사용할 때만로드됨을 의미합니까? 그래서 getEnt는 null이 될 것이고 두 번째 코드는 실행되지 않을 것으로 예상 할 수 있습니다. 감사!
Doug

CompletableFuture <> 웹 서비스에 싸여 있다면 게으른 구현 때문에 findOne () 대 getOne ()을 사용하고 싶다는 것을 알았습니다.
Fratt

76

1. 왜 getOne (id) 메소드가 실패합니까?

문서 에서이 섹션 참조하십시오 . 이미 진행중인 트랜잭션을 재정의하면 문제가 발생할 수 있습니다. 그러나 더 많은 정보가 없으면 대답하기가 어렵습니다.

2. getOne (id) 메소드는 언제 사용해야합니까?

Spring Data JPA의 내부를 파헤 치지 않으면 차이점은 엔티티를 검색하는 데 사용되는 메커니즘에있는 것으로 보입니다.

당신은 보면 JavaDoc을 위한 getOne(ID)아래 참고 항목 :

See Also:
EntityManager.getReference(Class, Object)

이 방법은 JPA 엔티티 관리자의 구현에 위임하는 것 같습니다.

그러나, 문서 에 대한이 findOne(ID)이 문제를 언급하지 않습니다.

단서는 리포지토리의 이름에도 있습니다. JpaRepositoryJPA에 따라 다르므로 필요한 경우 엔티티 관리자에게 호출을 위임 할 수 있습니다. CrudRepository사용 된 지속성 기술을 무시합니다. 여기를보십시오 . JPA, Neo4J 등과 같은 다중 지속성 기술의 마커 인터페이스로 사용됩니다 .

따라서 사용 사례에 대한 두 가지 방법에는 실제로 '차이'가 없으며 findOne(ID)더 전문화 된 것보다 더 일반적입니다 getOne(ID). 어느 것을 사용 하느냐는 귀하와 귀하의 프로젝트에 달려 있지만 개인적으로 findOne(ID)코드를 구현에 덜 구체화하고 너무 많은 리팩토링없이 MongoDB와 같은 것으로 이동할 수있는 문을 열어줍니다. :)


당신에게 Donovan 감사합니다, 당신의 대답을 감지했습니다.
Manuel Jordan

20
there's not really a 'difference' in the two methods엔터티를 검색하는 방법과 메서드가 반환 될 것으로 예상되는 방법에 큰 차이가 있기 때문에 여기서 말하는 것은 매우 잘못된 생각 입니다. @davidxxx의 답변은 이것을 잘 강조 표시하며 Spring Data JPA를 사용하는 모든 사람이 이것을 알고 있어야한다고 생각합니다. 그렇지 않으면 상당히 두통이 생길 수 있습니다.
fridberg

16

getOne메소드는 DB에서 참조 만 반환합니다 (게으른로드). 따라서 기본적으로 트랜잭션 외부에 있습니다 ( Transactional서비스 클래스에서 선언 된 것은 고려되지 않음). 오류가 발생합니다.


EntityManager.getReference (Class, Object)는 새로운 트랜잭션 범위에 있으므로 "nothing"을 반환합니다.
Manuel Jordan

2

위의 답변에서 실제로 매우 어렵습니다. 디버깅 관점에서 나는 바보 같은 실수를 아는 데 거의 8 시간이 걸렸습니다.

spring + hibernate + dozer + Mysql 프로젝트를 테스트했습니다. 확실하게.

사용자 엔터티, Book 엔터티가 있습니다. 매핑 계산을 수행합니다.

여러 권의 책이 한 명의 사용자와 연결되어있었습니다. 그러나 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

}

위의 코드는 사용자가 읽은 책을 가져 오지 않았습니다.

getOne (ID) 때문에 bookList가 항상 널이었습니다. 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

}


-1

spring.jpa.open-in-view가 true 인 동안 getOne에 아무런 문제가 없었지만 false로 설정하면 LazyInitializationException이 발생했습니다. 그런 다음 findById로 대체하여 문제를 해결했습니다.
getOne 메소드를 대체하지 않고 다른 솔루션이 있지만 repository.getOne (id)를 호출하는 메소드에 @Transactional이 있습니다. 이런 식으로 트랜잭션이 존재하고 메소드에서 세션이 닫히지 않으며 엔티티를 사용하는 동안 LazyInitializationException이 발생하지 않습니다.


-2

JpaRespository.getOne (id)가 작동하지 않고 오류가 발생하는 이유를 이해하는 데 비슷한 문제가 있습니다.

가서 JpaRespository.findById (id)로 변경하여 Optional을 반환해야합니다.

이것은 아마도 StackOverflow에 대한 첫 번째 의견 일 것입니다.


불행히도 이것은 질문에 대한 답변을 제공하지 않으며 기존 답변을 개선하지도 않습니다.
JSTL

나는 문제 없다고 본다.
akshaymittal143
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.