[오류]object references an unsaved transient instance - save the transient instance before flushing
들어가면서
최근에 JPA를 공부하면서, 분명 책이랑 똑같이 하고 있는데도 특정 부분에서 데이터를 생성하면 에러가 나는 경우가 발생해서 진도를 더 못나가고 있었다.
원인
오류의 원인은 바로 이랬다. JPA를 사용하면서 @ManyToOne이나 @ManyToMany 같이 FK를 이용한 필드도 동시에 테이블에 저장이 되어야되는데 연쇄적으로 저장이 안되는 문제였다.
object references an unsaved transient instance - save the transient instance before flushing
일단 상황은 이랬다. board entity와 member entity 크게 두가지 테이블로 이루어진 상황에서, board 와 member는 1:N의 관계를 이룬다.
그러므로 board 안에 writer라는 필드로 Member와 Join을 하게 했는데, 데이터를 insert하는 부분에서 board 테이블에는 데이터가 들어가지만, writer 테이블에는 안들어가는 문제가 생겼다.
해결 방법
Join하는 쪽에 Cascade를 걸어주면 자연스럽게 해결이 가능하다.
package com.rumblekat.board.entity;
import lombok.*;
import javax.persistence.*;
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString(exclude = "writer") //@ToString은 항상 exclude 연관관계가 있는 것은 무조건 exclude작업을 진행한다.
public class Board extends BaseEntity{
/*
* 게시물의 수정은 필요한 부분만을 변경하고 BoardRepository의 save를 이용하여 처리
* */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long gno; //bno
private String title;
private String content;
//작성자는 아직 처리하지 않는다.
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Member writer; //연관관계
/*
* JPA는 FK쪽을 먼저 해석한다. board와 member읜 관계는 N:1(다대일)
* */
public void changeTitle(String title){
this.title = title;
}
public void changeContent(String content){
this.content = content;
}
}
DB 캡처
– 2022.01.14 추가 내용
org.hibernate.PersistentObjectException: detached entity passed to persist
이 오류의 원인은 종속성에 있다. 만약 A - B가 1:N의 관계에 있는 상태에서 B - C도 1:N의 관계일 때, C를 추가하는 작업에서 B가 한번 더 저장되는 문제가 발생한다.
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 적용
해결책
결국 아래 테이블의 Casecade 속성을 빼면서 해결이 가능했다. 구글링 해보니, 처음 Save를 할때 replyRepository에서 진행했었는데, 이를 참조하는 Board 객체가 detached 되었고, 다시 Persist가 불가능해져서 그렇다고 한다..
Casecade 속성을 MERGED, DETACHED로 변경해도 가능하다고 한다.
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString(exclude = "board") //@ToString에 주의(연관관계 대상 필드 exclude)
public class Reply extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long rno;
private String text;
private String replyer;
//TODO: Board와 연관관계 board의 writer까지 가져오면 모두 left outer join으로 가져오게된다.
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
}