티스토리 뷰

반응형

오늘은 간단하게 JPA에서 연관관계에 있는 엔티티를 입력받으려 할 떄 에러가 난 증상을 간단히 기술해보고자 합니다.

케이스는 아래와 같습니다.

 

A class

@Entity
@Data
...
public class Aclass {
   
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;
   
   
   private String col1;
   
   private String col2;
   
   private String col3;
   
   private LocalDateTime createdAt;
   
}

B class

@Entity
@Data
...
public class Bclass {
   
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;
   
   @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
   private Aclass aclass;
   
   private String col1;
   
   private String col2;
   
   private String col3;
   
   private LocalDateTime createdAt;
   
}

즉 B class 는 A class 에 1:1 연관관계에 놓여있고, b 라는 테이블에는 A를 참조한 추가 데이터가 들어있는 구조입니다. A class에는 있을 수 있지만 B class에서 필요 없는 내용이 있는 경우입니다.

이렇게 한 후 bclassRepository.save(bclass) 를 하면 아래와 같은 에러가 발생합니다.

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.project.domain.Aclass; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.project.domain.Aclass
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:319)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
	at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
	at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
	at com.sun.proxy.$Proxy232.saveAll(Unknown Source)

가만히 생각해보니 연관관계가 다른 것 같습니다. cascade = CascadeType.ALL 에는 A class 에 있는 모든 내용을 적용한다는 내용인데 지금과 같은 상황에서는 사용하면 안될 것 같습니다. 

영속성 전이인 cascade 옵션의 설명은 찾아보니 다음과 같습니다. 

CascadeType의 종류

  • CascadeType.RESIST: 엔티티를 생성하고, 연관 엔티티를 추가하였을 때 persist() 를 수행하면 연관 엔티티도 함께 persist()가 수행된다. 만약 연관 엔티티가 DB에 등록된 키값을 가지고 있다면 detached entity passed to persist Exception이 발생한다.
  • CascadeType.MERGE: 트랜잭션이 종료되고 detach 상태에서 연관 엔티티를 추가하거나 변경된 이후에 부모 엔티티가 merge()를 수행하게 되면 변경사항이 적용된다.(연관 엔티티의 추가 및 수정 모두 반영됨)
  • CascadeType.REMOVE: 삭제 시 연관된 엔티티도 같이 삭제됨
  • CascadeType.DETACH: 부모 엔티티가 detach()를 수행하게 되면, 연관된 엔티티도 detach() 상태가 되어 변경사항이 반영되지 않는다.
  • CascadeType.ALL: 모든 Cascade 적용

 

JPA Life Cycle은 특이하게 Cascade.ALL 옵션을 준 경우 save를 수행하면 자식 객체에서 참조하고 있는 부모객체가 detach 되는 구조로 라이프 사이클이 이루어지고 있습니다. 그래서 CascadeType.MERGED 를 적용하면 A class 내용이 변질될 가능성이 있어 위험요소가 있습니다. 

그래서 저는 CascadeType.DETACH 옵션을 사용하여 적용하였습니다. 

 

또한 CascadeType은 @OneToOne 뿐만 아닌 @ManyToOne 관계에서도 적용되니 참고해서 개발하면 좋을 것 같습니다.

반응형
댓글
공지사항