티스토리 뷰
JPA
[JPA-Error] org.hibernate.TransientObjectException: object references an unsaved transient instance
heemang.dev 2023. 9. 17. 14:29
Embedded 값 타입을 다루고 있는데 오류가 발생했다.
javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:81)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104)
at jpay.jpastudy.JpastudyApplication.main(JpastudyApplication.java:44)
Caused by: java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: jpay.jpastudy.PhoneServiceProvider
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:151)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1411)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:489)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3303)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2438)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:449)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
... 1 more
Caused by: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: jpay.jpastudy.PhoneServiceProvider
at org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:347)
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:508)
at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:370)
at org.hibernate.type.ComponentType.isDirty(ComponentType.java:280)
at org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:330)
at org.hibernate.persister.entity.AbstractEntityPersister.findDirty(AbstractEntityPersister.java:4768)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.dirtyCheck(DefaultFlushEntityEventListener.java:586)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:249)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:174)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:229)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:93)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1407)
... 9 more
2023-09-17 14:10:40.855 INFO 1896 --- [ main] org.hibernate.orm.connections.pooling : HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
Process finished with exit code 0
예외 원인을 읽어보니 unsaved trasient instance - save the trasient instance before flushing 문구가 있다. 해당 예외는 영속성 컨텍스트와 관련된 문제이다. Entity 객체를 생성하고 영속성 컨텍스트에 저장하지 않은 상태로 DB에 저장하려고 했기 때문에 문제가 발생했다.
오류가 발생한 이유
PhoneNumber 임베디드 타입에서 PhoneServiceProvider 엔티티를 참조하고 있다. 그리고 이 Embedded 타입을 Member 엔티티에서 다루고 있다. Member 엔티티에서 PhoneNumber 임베디드 타입에 값을 매핑해주고 영속성 컨텍스트에 persist를 했다. 여기서 예외가 발생하는데, 그 이유는 PhoneServiceProvider를 영속화시키지 않고 PhoneNumber 임베디드 타입에서 다루었고 이를 Member 엔티티에서 그대로 영속화시켰기 때문이다. 코드를 통해 알아보자.
Member - Entity
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String name;
private int age;
@Embedded
private Address address;
@Embedded
private Period period;
@Embedded
private PhoneNumber phoneNumber;
// Getter, Setter
}
PhoneServiceProvider - Entity
@Entity
public class PhoneServiceProvider {
@Id
@GeneratedValue
private Long id;
public Long getId() {
return id;
}
// Getter
}
PhoneNumber - Embedded 타입
@Embeddable
public class PhoneNumber {
private String areaCode;
private String localNumber;
@ManyToOne
private PhoneServiceProvider provider;
// Getter, 생성자
}
영속화 과정
PhoneNumber의 생성자를 보면 영속화 되지 않은 provider를 넘겨주었다. 이 PhoneNumber를 Member 엔티티에 저장하고 영속화시켰다. PhoneServiceProvider를 영속화시키지 않고 Member 엔티티를 영속화시키기 때문에 예외가 발생할 수 밖에 없다.
이를 해결하기 위해서 PhoneServiceProvider를 영속화 시켜주면 된다.
Address address = new Address("city1", "street1", "zipcode1");
Period period = new Period(new Date(), new Date());
PhoneServiceProvider provider = new PhoneServiceProvider();
// em.persist(provider);
PhoneNumber phoneNumber = new PhoneNumber("areaCode1", "localNumber1", provider);
Member member = new Member();
member.setAddress(address);
member.setPeriod(period);
member.setPhoneNumber(phoneNumber);
member.setName("사용자1");
member.setAge(99);
em.persist(member);
결론
Entity를 영속화 시키고 사용하자!