다음을 호출하는 유스 케이스가 있습니다.
@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);
}
간단한 감사 제어를 수행하기 때문에 두 방법 모두 전파입니다 .REQUIRES_NEW
JpaRepository 인터페이스에 getOne
정의되어 있기 때문에 메소드를 사용 하고 내 Repository 인터페이스가 거기에서 확장 되므로 물론 JPA와 함께 작업하고 있습니다.
JpaRepository 인터페이스에서 확장 CrudRepository . 이 findOne(id)
방법은에 정의되어 CrudRepository
있습니다.
내 질문은 :
- 왜
getOne(id)
방법이 실패 합니까? - 언제
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()
당신이 찾게 될 유일한 방법 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) 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);
그리고 다행히도 EntityManager
javadoc은 의도를 더 잘 정의했습니다 (강조는 내 것입니다).
상태가 느리게 페치 될 수있는 인스턴스를 가져옵니다 . 요청 된 인스턴스가 데이터베이스에 없으면 인스턴스 상태에 처음 액세스 할 때 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)
이 문제를 언급하지 않습니다.
단서는 리포지토리의 이름에도 있습니다.
JpaRepository
JPA에 따라 다르므로 필요한 경우 엔티티 관리자에게 호출을 위임 할 수 있습니다.
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에 대한 첫 번째 의견 일 것입니다.