Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 1차원 DP
- 2차원 dp
- 99클럽
- @BeforeAll
- @BeforeEach
- @Builder
- @Entity
- @GeneratedValue
- @GenericGenerator
- @NoargsConstructor
- @Query
- @Table
- @Transactional
- Actions
- Amazon EFS
- amazon fsx
- Android Studio
- ANSI SQL
- api gateway 설계
- api gateway 필터
- ApplicationEvent
- assertThat
- async/await
- AVG
- AWS
- aws eks
- AWS 프리티어
- Azure
- bind
- bitnami kafka
Archives
- Today
- Total
기록
Hibernate "unsaved transient instance" 오류 해결 방법 : CascadeType.ALL 본문
Web/Spring
Hibernate "unsaved transient instance" 오류 해결 방법 : CascadeType.ALL
zyin 2025. 3. 16. 16:041. 오류 상황
Spring Boot + JPA/Hibernate 환경에서 아래와 같은 오류를 만났다.
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.TransientObjectException: object references an unsaved transient instance -
save the transient instance before flushing: com.example.model.AnswerHistoryEntity
이 오류는 영속화되지 않은(transient) 엔티티를 다른 엔티티와 함께 저장하려고 할 때 발생한다.
예제 코드
@Transactional
fun createQuizChallenge(req: ChallengeQuizReq, authReq: AuthReq): CreateChallengeResp {
val quizList = req.answers.map { it.quizId }.distinct()
val quizMap = quizRepository.findAllById(quizList).associateBy { it.quizId }
val challenge = Challenge(
status = ChallengeStatus.UNDER_REVIEW,
questId = req.questId,
customerId = authReq.userId,
answers = req.answers.map {
val quiz = quizMap[it.quizId] ?: throw IllegalArgumentException("퀴즈를 찾을 수 없습니다.")
AnswerHistoryEntity(content = it.answer, quiz = quiz)
}.toMutableList()
)
val savedChallenge = challengeRepository.save(challenge)
return CreateChallengeResp(savedChallenge)
}
이 코드에서 AnswerHistoryEntity는 아직 DB에 저장되지 않은 상태에서 Challenge에 추가되었기 때문에 Hibernate에서 오류가 발생한다.
2. Hibernate의 엔티티 생명주기
JPA에서 엔티티는 다음 4가지 상태를 가진다.
- Transient(비영속): 아직 영속성 컨텍스트에 저장되지 않은 상태
- Persistent(영속): EntityManager.persist() 또는 save()를 호출하여 저장된 상태
- Detached(준영속): 영속성 컨텍스트에서 분리된 상태
- Removed(삭제됨): delete() 등을 호출하여 삭제된 상태
오류의 원인은 AnswerHistoryEntity가 Transient 상태이기 때문이다.
Hibernate는 save(challenge)를 실행할 때 answers에 있는 AnswerHistoryEntity도 저장하려 하지만, 이 엔티티가 아직 영속 상태가 아니므로 오류가 발생한다.
3. 해결 방법
방법 1: AnswerHistoryEntity를 먼저 저장 후 Challenge에 추가
@Transactional
fun createQuizChallenge(req: ChallengeQuizReq, authReq: AuthReq): CreateChallengeResp {
val quizList = req.answers.map { it.quizId }.distinct()
val quizMap = quizRepository.findAllById(quizList).associateBy { it.quizId }
val challenge = Challenge(
status = ChallengeStatus.UNDER_REVIEW,
questId = req.questId,
customerId = authReq.userId,
answers = mutableListOf()
)
req.answers.forEach {
val quiz = quizMap[it.quizId] ?: throw IllegalArgumentException("퀴즈를 찾을 수 없습니다.")
val answerHistory = AnswerHistoryEntity(content = it.answer, quiz = quiz)
// 먼저 answerHistoryEntity를 저장
val savedAnswerHistory = answerRepository.save(answerHistory)
challenge.answers.add(savedAnswerHistory)
}
val savedChallenge = challengeRepository.save(challenge)
return CreateChallengeResp(savedChallenge)
}
✅ 변경 사항
- AnswerHistoryEntity를 먼저 저장한 후, Challenge에 추가하여 영속화.
- 이렇게 하면 Challenge 저장 시 answers가 이미 DB에 존재하기 때문에 오류가 발생하지 않는다.
방법 2: CascadeType.ALL을 설정하여 Challenge 저장 시 AnswerHistoryEntity도 자동 저장
@Entity
@Table(name = "tb_challenge_history")
data class Challenge(
@Id
@UuidGenerator
var challengeId: String? = null,
@Enumerated(EnumType.STRING)
var status: ChallengeStatus = ChallengeStatus.UNDER_REVIEW,
var questId: String? = null,
var customerId: String? = null,
@OneToMany(mappedBy = "challenge", cascade = [CascadeType.ALL], orphanRemoval = true)
var answers: MutableList<AnswerHistoryEntity> = mutableListOf()
)
이제 CascadeType.ALL을 추가했기 때문에 Challenge를 저장하면 answers도 함께 저장된다.
✅ 변경 사항
- CascadeType.ALL을 추가하여 Challenge 저장 시 AnswerHistoryEntity도 자동으로 저장되도록 변경.
- answerRepository.save()를 따로 호출하지 않아도 됨.
4. 해결 방법 비교
해결 방법 설명 장점 단점
방법 1: 명시적 저장 | AnswerHistoryEntity를 먼저 save() 후 Challenge에 추가 | 안정적, 필요한 경우만 저장 | 추가적인 save() 호출 필요 |
방법 2: Cascade 설정 | Challenge 저장 시 answers도 자동 저장 | 코드가 간결함 | 불필요한 곳에서도 자동 저장될 가능성 있음 |
✅ 추천 방법
- AnswerHistoryEntity가 독립적으로 저장될 필요가 있다면 방법 1 추천
- AnswerHistoryEntity가 Challenge의 일부로만 사용된다면 방법 2 추천
5. 결론
- Hibernate의 "unsaved transient instance" 오류는 영속화되지 않은 객체가 다른 엔티티와 함께 저장될 때 발생한다.
- 해결 방법은 객체를 먼저 저장하거나, Cascade 설정을 추가하는 것이다.
- 애플리케이션의 요구사항에 따라 적절한 방법을 선택하자.
'Web > Spring' 카테고리의 다른 글
QueryDSL로 중첩 DTO를 조회하는 방법 (0) | 2025.03.27 |
---|---|
Spring에서 multipart/form-data로 리스트(List<T>) 데이터를 받는 방법 (0) | 2025.03.23 |
assertThat의 주요 메서드와 기본 사용법 (0) | 2025.02.21 |
@Builder 사용 시 주의사항 – Dto에 기본 생성자를 추가해야 한다 (0) | 2025.02.07 |
QueryDSL에서 DTO 반환 방식: Projections.constructor vs. new QUserResponse (0) | 2025.01.26 |
Comments