연관관계를 맺는 두 엔티티 간에 값을 넣어주는 요청이 있는데 save the transient instance before flushing 에러가 발생하였다. 연관관계를 갖는 엔티티의 값을 넣어줄 때 종종 TransientObjectException에러가 발생하였는데 에러가 발생한 상황가 해결방법을 설명하겠다.
영속성때문에 나는 오류다. FK로 쓰는 객체가 아직 저장이 안 되서 오류이다. 다르게 설명하자면 객체를 persist할 때 해당 객체의 FK로 사용되는 컬럼값이 없는 상태에서 데이터를 넣으려다 발생하는 에러이다.
객체간 @OneToMany, @ManyToOne같은걸 쓸 때 날 수 있다.
org.hibernate.TransientObjectException: object references an unsaved transient
instance - save the transient instance before flushing: com.studyhere.studyhere.domain.entity.Tag
우선 나는 이번 개발에서 Study(스터디)와 Tag(관심주제) 두 개의 엔티티를 @ManyToMany 관계로 연관관계를 설정해주었고, 스터디가 연관관계의 주인이다(tag_id를 fk로 갖는다.) 문제는 바로 스터디를 생성 후 해당 스터디의 관심주제를 넣어 줄 때 TransientObjectException : save the transient instance before flushing에러가 발생하였다.
/**스터디 관심주제(Tag) 등록**/
@PostMapping("/study/{path}/tags")
@ResponseBody
public ResponseEntity addTag(@CurrentUser Account account,@PathVariable String path
,@RequestBody TagForm tagForm) {
Study study = studyService.findStudyIfManager(account, path);
studyService.addTagOfStudy(study,tagForm);
return ResponseEntity.ok().build();
}
🎉 핵심
- Tag.of(tagForm) 메서드는 tagForm의 DTO를 Tag엔티티로 바꾸어준다
한번 더 생각해보면 Tag.of(tagForm)로 변환된 Tag객체는 Detached상태이다. persist상태가 아니다.
- addTag()로 study의 관심주제(tag) 값을 넣어준다.
@Service
@Transactional
@RequiredArgsConstructor
public class StudyService {
public void addTagOfStudy(Study study,TagForm tagForm) {
Tag tag = Tag.of(tagForm);
addTag(study,tag);
}
public void addTag(Study study,Tag tag) {
//study에 태그 넣기
study.getTags().add(tag);
}
}
바로 위의 설명한 코드로 스터디의 관심주제를 생성하려고했는데 TransientObjectException : save the transient instance before flushing에러가 발생했다.
연관 관계 매핑해줄 때 사용하는 @ManyToOne, @OneToOne, @OneToMany 어노테이션에 cascade 옵션을 설정해준다.
cascade 는 "영속성 전이" 라고 하는 개념인데 특정 엔티티를 영속화 할 때 연관된 엔티티도 함께 영속화한다.
저장할 때만 사용하려면 cascade = CascadeType.PERSIST 로 설정해주면 되며, 전체 적용인 CascadeType.ALL 로 설정해도 된다.
@Entity
@Builder @AllArgsConstructor @NoArgsConstructor
public class Study {
...(생략)
@ManyToMany(cascade = CascadeType.PERSIST)
private Set<Tag> tags = new HashSet<>();
...(생략)
위에 🎉핵심이라고 적어놓은 글이 있다. 다시 한번 말하자면 service단에서 태그를 조회하지않고 요청으로 들어온 TagForm을 Tag엔티티로 변환 시켜준 것이다. 즉 영속화 상태가 아닌 Detached 객체이다.
해결방법: Tag 객체를 DB로부터 직접 불러와서 사용(영속화)
@PostMapping("/tags/add")
@ResponseBody
public ResponseEntity addTag(@CurrentAccount Account account, @PathVariable String path,
@RequestBody TagForm tagForm) {
Study study = studyService.getStudyToUpdateTag(account, path);
Tag tag = tagService.findOrCreateNew(tagForm.getTagTitle());
studyService.addTag(study, tag);
return ResponseEntity.ok().build();
}
@Service
@Transactional
@RequiredArgsConstructor
public class TagService {
private final TagRepository tagRepository;
public Tag findOrCreateNew(String tagTitle) {
Tag tag = tagRepository.findByTitle(tagTitle);
if (tag == null) {
tag = tagRepository.save(Tag.builder().title(tagTitle).build());
}
return tag;
}
}
위에서 cascade = CascadeType.PERSIST를 직접 연관관계를 맺는 필드에 설정 해주거나, 객체를 영속화 시켜서 저장해준다면 아래와 같이 TransientObjectException : save the transient instance before flushing에러가 발생하지 않고 잘 저장 되는것을 볼 수 있다.
Hibernate:
insert
into
tag
(title, id)
values
(?, ?)
Hibernate:
insert
into
study_tags
(study_id, tags_id)
values
(?, ?)