쌓고 쌓다
JPA(hibernate) INSERT, UPDATE, DELETE 순서 주의사항 본문
문제가 발생한 상황은 다음과 같다.
회원은 프로필 이미지를 하나만 가질 수 있는 상황이다.
Member
@Entity
@Data
public class Member {
...
@OneToOne(mappedBy = "member", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private MemberImage memberImg;
...
}
MemberImage
@Entity
@Data
@NoArgsConstructor
public class MemberImage {
...
@OneToOne
@JoinColumn(name = "member_id")
private Member member;
}
@OneToOne으로 회원과 프로필 이미지는 하나만 가지도록 했다.
그리고 프로필 이미지 변경을 위해
기존 프로필 이미지를 DELETE -> 새로운 이미지를 INSERT 하는 과정을 코드로 작성했다.
위의 코드를 실행시 다음과 같은 에러가 발생한다.
2024-02-22T17:23:06.253+09:00 ERROR 19152 --- [io-8080-exec-11] o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '2' for key 'member_image.UK_ho4xw7rdwutyabcwmwmg2ndu3'
중복된 키가 2개 존재해서 발생한 Unique 제약 에러 같다.
즉, member_image에 회원의 이미지는 하나만 존재해야하는데 2개가 존재해버리니 에러가 발생한 것이다.
원인은 hibernate의 쿼리 실행 순서이다.
JPA는 트랜잭션 내에서 동작한다.
쓰기 지연을 통해 트랜잭션을 커밋하기전까지 내부 쿼리 저장소에 모아놨다가
트랜잭션을 커밋할때 flush하여 모아둔 쿼리를 날린다.
쿼리의 우선 실행 순서는 다음과 같다.
- Inserts, in the order they were performed
- Updates
- Deletion of collection elements
- Insertion of collection elements
- Deletes, in the order they were performed
DELETE보다 INSERT가 우선이라 위의 @OneToOne의 UNIQUE 제약 조건에 걸리는 것이다.
DELETE후 INSERT를 하는 코드를 작성했더라도
쿼리 저장소에 모아놨다가 쿼리를 날리기에 우선순위에 따라 INSERT가 먼저 수행되어 에러가 발생한 것이다.
즉, 삭제 코드 후에 flush하여 삭제 쿼리를 먼저 날리도록 작성하고 INSERT 쿼리를 날리도록 하자.
위와 같이 repository의 flush() 메서드를 호출하여 쿼리를 미리 반영하자.
'프로그래밍 > JPA' 카테고리의 다른 글
flush와 clear의 차이 (테스트 코드에서 SELECT가 안나가는 이유) (0) | 2024.03.08 |
---|---|
Entity의 List 필드(컬렉션)는 초기화 해주자 (0) | 2024.03.08 |
JPA findBy로 특정 범위내 숫자 컬럼 검색하는법 (0) | 2023.12.26 |
JPA의 FindBy와 FindAllBy 차이점! (+NonUnique) (0) | 2023.11.30 |
JPA로 INSERT할때 MySQL 테이블 DEFAULT 값 넣기 (2) | 2023.11.20 |
Comments