[Spring] JPA save 호출 시 select 쿼리 수행 이슈

박성우·2023년 6월 18일
0

Spring

목록 보기
5/10

한 메소드에서 엔티티를 두 개 저장할 때, 다음과 같이 Room 엔티티는 Insert 쿼리가 반영되기 전에 Select 쿼리가 먼저 발생하는 이슈가 있었다.

Hibernate: 
    /* insert trillion9.studyarcade_be.roommember.RoomMember
        */ insert 
        into
            room_member
            (room_enter_time, member_id, room_master, room_token, session_id) 
        values
            (?, ?, ?, ?, ?)
 ----------------------------------------------------------------------------------------------------
           
Hibernate: 
    /* load trillion9.studyarcade_be.room.Room */ select
        room0_.session_id as session_1_1_0_,
        room0_.created_at as created_2_1_0_,
        ...
    from
        room room0_ 
    where
        room0_.session_id=?
Hibernate: 
    /* insert trillion9.studyarcade_be.room.Room
        */ insert 
        into
            room
            (created_at, room_creator_id, category, expiration_date, image_url, ... , session_id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

RoomMember 엔티티와 Room 엔티티의 차이는

@Getter
@Entity
@NoArgsConstructor
@AttributeOverride(name = "createdAt", column = @Column(name = "room_enter_time"))
public class RoomMember extends AuditingEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "room_member_id")
    private Long id;

-----------------------------------------------------------------------------------------------------

@Getter
@Entity
@NoArgsConstructor
@AttributeOverride(name = "memberId", column = @Column(name = "room_creator_id"))
public class Room extends AuditingEntity {
    // 세션 ID
    @Id
    private String sessionId;

위와 같이 식별자의 생성 전략 유무 때문에,

        Room room = Room.builder()
                .sessionId(newToken.getSessionId())
                ...

Room 엔티티를 생성할 때는 RoomMember와 다르게 식별자 값을 직접 넣어주는 차이가 있다.

김영한님의 자바 ORM 표준 JPA 프로그래밍에 다음과 같은 설명이 있다고 한다.

"save(S) 메소드는 엔티티에 식별자 값이 없으면(null이면) 새로운 엔티티로 판단해서 EntityManager.persist()를 호출하고 식별자 값이 있으면 이미 있는 엔티티로 판단해서 EntityManager.merge()를 호출한다."

내 상황에 대입해보면, RoomMember 엔티티를 저장할 경우, ID 값을 따로 지정해주지 않고 IDENTITY 생성 전략과 함께 새로운 엔티티로 판단이 되지만, Room 엔티티의 경우 엔티티를 생성할 때 직접 ID 값을 지정해주기 때문에 해당 ID 값을 가진 엔티티가 이미 존재하는지 확인하는 작업이 필요하다는 것이다.

결국, 식별자 값을 지정했기 때문에 EntityManager.merge()가 호출된 것이고 Insert 쿼리를 생성할 지 Update 쿼리를 생성할 지 결정하기 위해서 Select 쿼리가 우선적으로 필요한 것이였다.

해당 이슈를 해결하기 위해 Room 엔티티를 저장할 경우에는 save() 대신 EntityManager.persist() 를 직접적으로 호출해서 Insert 쿼리만 보내지게 수정하였다.
(Room 엔티티의 식별자는 중복되지 않는 값을 다른 곳으로부터 받아서 사용하고 있었기 때문에 무결성이 보장될 것으로 생각했다.)

다만, 항상 예외는 발생할 수 있기 때문에 지향하는 방법을 검색해보니 엔티티에 Persistable 인터페이스를 구현하고 엔티티의 상태를 판단하는 isNew() 를 오버라이딩 하는 것이 올바른 방법이라고 한다. 하지만 이것 또한 완벽하진 않으니 이 부분에 있어서 더 공부할 필요가 있어보인다.

profile
Backend Developer

0개의 댓글