Hibernate의 "Detached Entity Passed to Persist" 오류 이해와 해결

2023. 11. 8. 11:34Project 해축갤/테스트 코드

728x90

오류 이해하기

Detached(준영속) 상태란?

Hibernate 및 JPA에서 엔티티의 생명주기 중 하나인 'Detached' 상태는

영속석 컨텍스트가 더 이상 해당 객체를 추적하지 않을 때 발생합니다.

엔티티의 생명주기 그래프 (어디서 많이 본 듯한 갓영한의 그래프)

세션이 닫히거나(close), 클리어될 때(clear), 또는

수동으로 영속성 컨텍스트에서 제거(detach) 될 때 이 상태에 도달할 수 있습니다.

 

Entity의 상태 코드 EntityState enum을 보면 

public enum EntityState {
	PERSISTENT, TRANSIENT, DETACHED, DELETED;

	static final CoreMessageLogger LOG = CoreLogging.messageLogger( EntityState.class );

	/**
	 * Determine whether the entity is persistent, detached, or transient
	 */
	public static EntityState getEntityState(
			Object entity,
			String entityName,
			EntityEntry entry, //pass this as an argument only to avoid double looking
			SessionImplementor source,
			Boolean assumedUnsaved) {

		if ( entry != null ) { // the object is persistent <-- entry 에는 id 가 포함!!
			//...
		}

		if ( ForeignKeys.isTransient( entityName, entity, assumedUnsaved, source ) ) {
			//...
		}
		if ( LOG.isTraceEnabled() ) {
			//...
		}
		return DETACHED;
	}

}

 

중간에 if ( entry != null ) 파트가 있는 데, entry의 내부 코드에는 

id 가 있다!

id 가 존재합니다.

즉, 우리는 Hibernate 가 id가(Entity 기준 @Id 가 달린 필드의 값)

있다 없다를 기준으로 Detached 인지 아닌지를 가른다는 걸 알 수 있습니다.

Persist vs Merge

persist() 새로운 객체를 영속성 컨텍스트에 추가할 때 사용됩니다.

객체가 아직 영속성 컨텍스트에 저장되지 않았을 때 적합합니다.

 

merge()는 Detached 상태의 객체를 다시 영속 상태로 전환할 때 사용됩니다.

객체의 ID 필드를 기반으로 컨텍스트에 다시 연결합니다. (이 부분을 기억!!)

문제 상황

왜 이런 글을 쓰게 됐냐? 제가 겪었기 때문에,,,

아ㅏㅏㅏㅏ

저는 해축갤 게시판의 Member 엔티티의 단위 테스트를 작성 중이었습니다.

해당 테스트는 회원 정보가 정상적으로 저장되는지 확인하는 과정입니다.

그러나 테스트 실행 시 'Detached Entity Passed to Persist'라는 오류가 발생했습니다.

에러 원인 분석

코드는 아래와 같습니다.

Member member = Member.builder()
        .id(1L)
        .name("tester")
        .email("test@example.com")
        .password("password")
        .build();
em.persist(member);

 

이 코드에서 Member 객체는 id 값을 이미 가지고 있기 때문에

Hibernate는 이미 이 객체를 식별 가능한, 즉 detached 상태로 간주할 수 있는 객체로 인식합니다.

따라서 persist 메서드에서 위와 같은 오류가 발생하는 겁니다.

해결 방안

즉, id 가 있어서 생기는 문제이기에 2가지 접근 방법을 생각할 수 있습니다.

  1. persist를 사용하되, 객체 생성 시 'id'를 설정하지 않습니다.
  2. merge 메서드를 사용하여 Detached 상태의 객체를 다시 영속성 컨텍스트에 연결합니다.

저는 persist를 사용하는 첫 번째 방법을 선택하였습니다.

왜냐하면 아직 데이터베이스에 엔티티가 존재하지 않고,

새롭게 데이터베이스에 넣으려고 하는 상황이기 때문입니다.

 

merge 의 두 사용처는 영속성 컨텍스트로 다시 관리하겠다 입니다.

즉, 기존의 영속 엔티티와 메모리상(즉 코드레벨에 존재하는)의 엔티티와 결합할 때 사용되겠죠.

물론 영속성 컨텍스트에 merge 하고자 하는 엔티티가 존재하지 않으면 저장이 되겠다만은.

결론

Persist와 Merge 메서드 사용, 그리고

Hibernate 가 여러 기준 중 id 값을 토대로 Entity의 상태를 결정할 수 있다는 것을 알았습니다.

여러분은 이런 에러 만나시면 후딱 넘기시길!

참고

https://www.baeldung.com/hibernate-detached-entity-passed-to-persist

https://www.inflearn.com/course/ORM-JPA-Basic

728x90