쌓고 쌓다

JPA(hibernate) INSERT, UPDATE, DELETE 순서 주의사항 본문

프로그래밍/JPA

JPA(hibernate) INSERT, UPDATE, DELETE 순서 주의사항

승민아 2024. 2. 24. 17:27

문제가 발생한 상황은 다음과 같다.

회원은 프로필 이미지를 하나만 가질 수 있는 상황이다.

 

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하여 모아둔 쿼리를 날린다.

 

쿼리의 우선 실행 순서는 다음과 같다.

  1. Inserts, in the order they were performed
  2. Updates
  3. Deletion of collection elements
  4. Insertion of collection elements
  5. Deletes, in the order they were performed

DELETE보다 INSERT가 우선이라 위의 @OneToOne의 UNIQUE 제약 조건에 걸리는 것이다.

 

https://docs.jboss.org/hibernate/orm/6.1/javadocs/org/hibernate/event/internal/AbstractFlushingEventListener.html#performExecutions(org.hibernate.event.spi.EventSource)

 

AbstractFlushingEventListener (Hibernate JavaDocs)

Execute all SQL (and second-level cache updates) in a special order so that foreign-key constraints cannot be violated: Inserts, in the order they were performed Updates Deletion of collection elements Insertion of collection elements Deletes, in the order

docs.jboss.org

 

 

DELETE후 INSERT를 하는 코드를 작성했더라도

쿼리 저장소에 모아놨다가 쿼리를 날리기에 우선순위에 따라 INSERT가 먼저 수행되어 에러가 발생한 것이다.

 

즉, 삭제 코드 후에 flush하여 삭제 쿼리를 먼저 날리도록 작성하고 INSERT 쿼리를 날리도록 하자.

위와 같이 repository의 flush() 메서드를 호출하여 쿼리를 미리 반영하자.

Comments