[JPA] TransientObjectException 문제와 해결 과정

·2025년 7월 29일

troubleshooting

목록 보기
4/9

1. 배경

이번에 큐레이션(Curation)과 축제(Festival), 맛집(DeliciousSpot) 간의 관계를 매핑하는 작업을 진행했다.

  • Curation – Festival, Curation – DeliciousSpot은 다대다(M:N) 관계
  • 중간 테이블을 사용해 매핑 (CurationFestival, CurationDeliciousSpot)
  • Curation을 수정(update)할 때, 기존 관계를 모두 제거한 뒤 새롭게 연결

2. 초기 접근

처음에는 단순히 CurationReqDto.toEntity()로 새 엔티티를 만들어 curation.update()를 호출했다.

// CurationService.java
curation.update(CurationReqDto.toEntity(curationReqDto, findCreator, deliciousSpots, festivals));
// Curation.java
public void update(Curation curation) {
        this.type = curation.type;
        this.curationDeliciousSpots = curation.curationDeliciousSpots;
        this.curationFestivals = curation.curationFestivals;
        this.creator = curation.creator;
        // ...
}

3. 발생한 문제

TransientObjectException: persistent instance references an unsaved transient instance of 
'com.culturefinder.songdodongnae.curation.domain.Curation'
(save the transient instance before flushing)
  • CurationReqDto.toEntity()가 새로운 Curation 엔티티를 생성하기 때문에,
    JPA가 보기에 아직 영속화되지 않은(transient) Curation을 중간 테이블(CurationFestival)에서 참조하게 된다.

  • 즉, 새로운 엔티티와 기존 엔티티 간의 관계를 강제로 바꿔 끼우면서 flush 시점에 오류가 발생한 것이다.



큰 아이디어는 "기존 영속 상태의 Curation 엔티티 안에서 연관관계 컬렉션만 수정한다."로 결정했다.

4. 처음 시도했던 방식과 변경 이유

처음에는 CurationDto 객체를 그대로 엔티티의 update() 메서드에 넘기고,
update() 안에서 DTO의 값들을 꺼내 엔티티 필드를 갱신하는 방식으로 접근하려고 했다.

public void update(CurationReqDto dto) {
    this.title = dto.getTitle();
    this.type = dto.getType();
    // ...
}

하지만 이 방식에는 두 가지 문제가 있었다.

  • Entity가 DTO에 종속적이게 된다는 점
    • 엔티티는 도메인 모델이어야 하고, 외부 계층(Web/Controller)과 무관해야 한다.
    • DTO를 직접 받게 되면 엔티티가 API 요청/응답 형태에 의존하게 된다.
  • 테스트와 유지보수 어려움
    • DTO 구조가 바뀌면 엔티티의 도메인 로직까지 영향을 받는다.

5. 최종 해결 방안

결국 DTO가 아니라 DTO에서 꺼낸 값만 넘겨주는 방식으로 변경했다.

// service.java
curation.update(
                curationReqDto.getTitle(),
                curationReqDto.getDescription(),
                curationReqDto.getType(),
                curationReqDto.getImageUrl(),
                findCreator,
                festivals,
                deliciousSpots
        );
        
//  Curation.java 엔티티
public void update(String title, String description, CurationType type, String imageUrl, Creator creator, List<Festival> festivals, List<DeliciousSpot> deliciousSpots) {
        this.type = type;
        this.title = title;

     	// 기존 연관관계 제거
        this.curationFestivals.clear();
        this.curationDeliciousSpots.clear();
		// 새로운 연관관계 매핑
        festivals.forEach(this::addFestival);
        deliciousSpots.forEach(this::addDeliciousSpot);
    }


postman으로 테스트해보면 성공 !







0개의 댓글