"org.hibernate.TransientPropertyValueException: object references an unsaved transient instance 오류

김민우·2023년 2월 28일
0

잡동사니

목록 보기
12/21

오류 사항

컨트롤러에서 요청 메시지 바디에 있는 정보를 통해 엔티티 컬럼을 추가하는 과정에서 다음 오류가 발생했다.

{
    "timestamp": "2023-02-28T21:45:06.40209",
    "message": "org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.scascanner.studycafe.domain.entity.StudyCafe.owner -> com.scascanner.studycafe.domain.entity.Owner; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.scascanner.studycafe.domain.entity.StudyCafe.owner -> com.scascanner.studycafe.domain.entity.Owner",
    "details": "uri=/studycafe/new"
}

컨트롤러에서 호출된 메소드와 요청 메시지 바디에 담긴 정보는 다음과 같다.

컨트롤러에서 호출된 메소드

@PostMapping("/studycafe/new")
public StudyCafe add(@Validated @RequestBody StudyCafeAddFormDto studyCafeAddFormDto) {
    StudyCafe studyCafe = studyCafeAddFormDto.toEntity();
    studyCafeService.addStudyCafe(studyCafe);

    return studyCafe;
}

요청 메시지 바디

{
    "owner": {
        "email": "owner_test@gmail.com",
        "password": "12345678",
        "nickname": "신규테스터오너별명",
        "name": "신규테스터오너",
        "birthday": "1980-01-01"
    },
    "name": "test_name",
    "minUsingTime": 10,
    "openTime": "10:00:00",
    "closeTime": "22:00:00",
    "address": "주소,
    "comment": "상세 설명"
}

여기서 사용되는 엔티티들은 다음과 같다.

StudyCafe 엔티티

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class StudyCafe {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "study_cafe_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "owner_id")
    private Owner owner;

    private String name;
    private Integer minUsingTime;
    private LocalTime openTime;
    private LocalTime closeTime;
    private String address;
    private String comment;

    @Builder
    public StudyCafe(Long id, Owner owner, String name, Integer minUsingTime, LocalTime openTime, LocalTime closeTime, String address, String comment) {
        this.id = id;
        this.owner = owner;
        this.name = name;
        this.minUsingTime = minUsingTime;
        this.openTime = openTime;
        this.closeTime = closeTime;
        this.address = address;
        this.comment = comment;
    }
}
  • Owner 엔티티와 다대일 관계이다.
  • FK를 가진다.

Owner 엔티티

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Owner {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "owner_id")
    private Long id;
    private String email;
    private String password;
    private String nickname;
    private String name;
    private LocalDate birthday;

    @Builder
    public Owner(Long id, String email, String password, String nickname, String name, LocalDate birthday) {
        this.id = id;
        this.email = email;
        this.password = password;
        this.nickname = nickname;
        this.name = name;
        this.birthday = birthday;
    }
}
  • StudyCafe 엔티티와 일대다 관계이다.

원인


일대다 연관관계를 맺고 있는 두 엔티티 중 FK를 가지고 있는 엔티티(StudyCafe)를 추가할 때 Owner 엔티티가 먼저 추가가 된 후 StudyCafe 를 추가해야 하는데 StudyCafe 를 먼저 추가하여 오류가 발생한 것이다.

요악하면 PK를 가진 엔티티를 먼저 생성한 후 FK를 가진 엔티티를 생성 하여 PK를 통해 매핑을 해야하는데 이 과정이 반대로 되어 오류가 발생한 것이다.

해결


다음과 같이 FK를 가지는 StudyCafe 엔티티에서 Owner 필드(PK를 갖는 엔티티)의 @ManyToOne 어노테이션에 cascade = CascadeType.PERSIST 속성을 추가하면 된다.

StudyCafe 엔티티

...

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "owner_id")
private Owner owner;

...

cascade는 영속성 전이라 하는데, 이는 특정 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화해준다. 이 속성은 다음 두 가지 값을 가질 수 있다.

  • cascade = CascadeType.PERSIST : 저장할 때만 cascade 적용
  • cascade = CascadeType.ALL : 모든 상황에서 cascade 적용

연관관계 어노테이션인 @XXXToXXXcascade 속성을 활용하면 연관된 엔티티도 함께 관리되어 편리하게 사용할 수 있다.

0개의 댓글