우선 Match 대한 간략한 소개입니다 : https://velog.io/@kjh1232100/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B86-Match-%EC%A0%95%EB%B3%B4-%EA%B4%80%EB%A6%AC
SummonerInfo를 저장하기 위해 save()를 호출했는데 연관관계인 match를 select 하는 현상이 발생했습니다. 불필요한 쿼리를 해결하기 위해 원인을 파악하기로 했습니다.
save()
메소드와 select
쿼리 발생 원인CrudRepository의 save를 SimpleRepository에서 구현한 코드이다.
JPA에서 save()
메소드는 내부적으로 EntityManager.merge()
를 호출하여 엔티티를 저장한다. merge()
과정에서 영속상태였던 객체의 @Id가 변경됐다면 해당 객체의 유효성을 검사하기 위해 select
쿼리를 실행하여 DB에서 객체를 불러온다. 이는 DB에 있는 엔티티의 현재 상태와 애플리케이션에서 제공된 엔티티 상태를 비교하기 위해 필요한 절차이다.
만약 영속상태에서 객체가 유효하다면 JPA는 merge()
를 통해 엔티티의 변경된 부분을 탐지하고 해당 변경사항만 데이터베이스에 반영한다. 이 과정을 '변경 감지'라고 하며, 영속성 컨텍스트가 엔티티의 모든 변경을 추적하여 데이터베이스와 동기화를 맞추는 역할을 한다.
isNew()
)EntityManager.merge()
호출 시, JPA는 엔티티가 새로운 것인지, 즉 데이터베이스에 이미 존재하는 엔티티인지를 판단한다. 이 판단은 주로 엔티티의 @Id
필드를 검사하여 결정되며, Id
가 할당되지 않았거나 초기값을 가지고 있다면, 새로운 엔티티로 간주하고 persist()
작업이 수행된다. 기존의 Id
를 가진 엔티티는 merge()
를 통해 기존 데이터와 합병된다.
JPA에서 save()
메소드는 영속성 컨텍스트를 사용하여 엔티티의 초기 상태를 스냅샷으로 저장하고, isNew()
를 통해 엔티티의 @Id
값이 null
이거나 초기값일 경우 true
를 반환하여 persist()
로 새로 저장한다. isNew()
가 false
를 반환하면, merge()
가 호출되어, @Id
의 변경 여부에 따라 데이터베이스에서 엔티티를 조회하거나, 필드 값 변경만 감지된 경우 '더티 체킹'을 통해 필요한 필드만 업데이트한다. 이 과정은 데이터의 일관성
과 무결성
을 유지하며 애플리케이션과 데이터베이스 간의 동기화를 보장한다.
앞서 언급했듯이 JPA는 객체를 @Id를 통해 관리한다. 이 ID가 변경됐다면 객체에 접근할 수가 없으니 문제가 된다.
public class Match implements Persistable<String> {
@Id
private String matchId;
private Long gameStartTimestamp;
private Long kills;
private Long deaths;
private Long assists;
private String kda;
}
내 Match 엔티티의 @Id는 실제 match의 Id 정보로 update를 하면 경기 정보가 변경되기 때문에 Id도 바꿔 이런 문제가 발생한 것이다.
public class Match implements Persistable<String> {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String matchId;
private Long gameStartTimestamp;
private Long kills;
private Long deaths;
}
기본키를 새롭게 만들었다. match 객체를 사용할 때 matchId가 필요하기 때문에 따로 기본키가 필요 없다고 생각했었는데 JPA를 적절하게 활용하기 위해서라도 기본키는 따로 유지하는게 맞는 것 같다.