Spring Data JPA의 findByXXXId와 findByXXX
프로젝트에서 JPA를 사용하고 있는데 팀원분이 이런 질문을 남기셨다.
프론트로부터 Member 객체를 받아오는게 아니라 memberId만 전달받아오기 때문에 당연히 findbyMemberId로만 사용을 해오고 있었기 때문에 공부해볼만한 흥미로운 주제라고 생각했다.
우선 우리팀의 album entity는 이렇게 생겼다.
@Getter
@Entity
@Table(name = "album")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Album {
/**
* 앨범 정보 고유 id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "album_id")
private Long id;
/**
* 카드를 받는 사용자의 아이디
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
/**
* 카드의 고유 id
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "card_id", nullable = true)
private Card card;
/**
* 카드의 삭제 여부
*/
@Column(nullable = false)
private Boolean isDeleted;
...
}
member와 card를 외래키로 가지는 중계테이블의 형태를 하고 있다.
그리고 album을 memberId와 cardId로 검색할때 아래와 같은 코드로 조회하고 있다.
Album album = albumRepository.findByCardIdAndMemberId(req.getCardId(), req.getMemberId())
.orElseThrow(() -> new CardNotFoundException());
참고로 팀원분이 참고한 블로그글은 아래의 글인데
findByXXXId로 할 경우 조인이 일어난다는 글이다.
그래서 우리 프로젝트의 코드에서
과연 findByCardAndMember로 하는것이 더 효율적일지 테스트를 해보았다.
System.out.println("This is album 1");
Album album1 = albumRepository.findByCardIdAndMemberId(req.getCardId(), req.getMemberId())
.orElseThrow(() -> new CardNotFoundException());
System.out.println("----------------------------------------");
System.out.println("This is album 2");
Album album2 = albumRepository.findByCardAndMember(card, member)
.orElseThrow(() -> new CardNotFoundException());
앞부분에서 card객체와 member객체를 준비 해두었다.
이렇게 코드를 작성하고 콘솔창을 살펴보았다.
그런데 두 코드의 쿼리가 join쿼리 없이 완전히 동일하게 나오는 것이다.
조인이 일어날거라고 생각한 예상과 완전히 달랐다.
여러가지로 검색해본 결과
컬럼명의 차이때문에 이런일이 발생한다는 점을 알게 되었다.
우리의 member entity의 경우
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity(name="member")
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
...
}
java 내부에서 사용하는 변수명의 경우 id이지만 실제 DB에 들어가는 컬럼명의 경우 member_id이고
album에서 생성되는 컬럼명 또한 member_id이다.
따라서 byMember를 하든 byMemberId를 하든 실제로 참조하는 컬럼은 member_id임이 동일하기 때문에 별도의 join없이 쿼리가 실행되는 것이었다.
jpa 공식 문서를 살펴보면 쿼리문이 생성되는 과정은 이렇다는거 같다.
findBy하고 뒤에 나오는 것을 컬럼명으로 인식 하고 생성을 하게 되는 것인데
우리 프로젝트의 경우 이 컬럼명이 동일하게 존재하고 있기 때문에 Member와 Card테이블을 별로도 조인하지 않는 것으로 판단하게 되었다.
결론
컬럼명이 메서드 명과 동일하다면 두 메서드의 차이가 발생하지 않는 것으로 추정된다.
JPA는 알면 알수록 어렵도다.