TransientObjectException : save the transient instance before flushing 에러

김건우·2023년 2월 4일
0

Spring Data JPA

목록 보기
10/11
post-thumbnail

연관관계를 맺는 두 엔티티 간에 값을 넣어주는 요청이 있는데 save the transient instance before flushing 에러가 발생하였다. 연관관계를 갖는 엔티티의 값을 넣어줄 때 종종 TransientObjectException에러가 발생하였는데 에러가 발생한 상황가 해결방법을 설명하겠다.

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에러가 발생하였다.

StudyController

  1. 아래의 코드는 account(회원)의 인증을 한다.
  2. pathVarable로 들어오는 path로 해당 Study를 조회한다.
  3. tagform의 필드에는 tagTitle이라는 관심주제가 요청으로 들어온다.
   /**스터디 관심주제(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();
    }

StudyService(에러 원인)

🎉 핵심

  • 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에러가 발생했다.

첫번째 방법 : Cascade

연관 관계 매핑해줄 때 사용하는 @ManyToOne, @OneToOne, @OneToMany 어노테이션에 cascade 옵션을 설정해준다.

cascade 는 "영속성 전이" 라고 하는 개념인데 특정 엔티티를 영속화 할 때 연관된 엔티티도 함께 영속화한다.

저장할 때만 사용하려면 cascade = CascadeType.PERSIST 로 설정해주면 되며, 전체 적용인 CascadeType.ALL 로 설정해도 된다.

Study.entity

  • 아래와 같이 CascadeType.PERSIST를 설정해주어서 Study를 데이터베이스에서 조회해서 가져오면 영속화가된다. cascade설정을 해주어서 study를 조회할 때 연관관계인 Tag도 같이 영속화를 하여 저장한다.
@Entity
@Builder @AllArgsConstructor @NoArgsConstructor
public class Study {
...(생략)


    @ManyToMany(cascade = CascadeType.PERSIST)
    private Set<Tag> tags = new HashSet<>();
    
    
...(생략)

두번째 방법 : Tag 영속성 조회

위에 🎉핵심이라고 적어놓은 글이 있다. 다시 한번 말하자면 service단에서 태그를 조회하지않고 요청으로 들어온 TagForm을 Tag엔티티로 변환 시켜준 것이다. 즉 영속화 상태가 아닌 Detached 객체이다.

해결방법: Tag 객체를 DB로부터 직접 불러와서 사용(영속화)

studyController (해결방법)

  • Tag 객체를 DB로부터 직접 불러와서 Detached된 객체를 Persist(영속) 객체로 조회한다.
  • 영속화 된 study와 tag를 save() 한다.
 @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();
    }

findOrCreateNew() 메서드

@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
        (?, ?)
profile
Live the moment for the moment.

0개의 댓글