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

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

@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) 어떤 방법을 사용하도록 권장됩니까?

미리 감사드립니다.



답변

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() 간단한 처리를 제공합니다.


답변

기본적인 차이점은 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. 왜 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와 같은 것으로 이동할 수있는 문을 열어줍니다. 🙂


답변

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


답변

위의 답변에서 실제로 매우 어렵습니다. 디버깅 관점에서 나는 바보 같은 실수를 아는 데 거의 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

}


답변

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


답변

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

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

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