컨트롤러에서 요청 메시지 바디에 있는 정보를 통해 엔티티 컬럼을 추가하는 과정에서 다음 오류가 발생했다.
{
"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": "상세 설명"
}
여기서 사용되는 엔티티들은 다음과 같다.
@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
를 가진다.@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
속성을 추가하면 된다.
...
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "owner_id")
private Owner owner;
...
cascade는 영속성 전이라 하는데, 이는 특정 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화해준다. 이 속성은 다음 두 가지 값을 가질 수 있다.
cascade = CascadeType.PERSIST
: 저장할 때만 cascade 적용cascade = CascadeType.ALL
: 모든 상황에서 cascade 적용연관관계 어노테이션인 @XXXToXXX
의 cascade
속성을 활용하면 연관된 엔티티도 함께 관리되어 편리하게 사용할 수 있다.