Java 아키텍처·Spring 용어 사전
Spring Data JPA쿼리 메서드 · PartTree · @Query

리포지토리 프록시

인터페이스만 선언해도 동작하는 이유. 메서드 이름을 파싱해 쿼리를 만들어 구현체를 생성한다.

구현 클래스를 쓰지 않았는데 동작하는 이유는 Spring이 런타임에 구현체(프록시)를 만들어 빈으로 등록하기 때문이다.

어떻게 만들어지나

① 인터페이스를 스캔한다 (JpaRepository 상속)
② 각 메서드를 분류한다
     · @Query 가 붙었나           → 그 JPQL/SQL 을 쓴다
     · 이름이 규칙에 맞나          → PartTree 파서가 쿼리를 만든다
     · 기본 제공 메서드인가         → SimpleJpaRepository 구현으로 위임
③ 이 셋을 라우팅하는 프록시를 만들어 빈으로 등록한다

메서드 이름이 곧 쿼리 — PartTree 파서

List<Order> findByMemberIdAndStatusOrderByCreatedAtDesc(Long memberId, String status);
//          └주제┘└──────── 조건 ─────────┘└──── 정렬 ────┘
// → SELECT o FROM Order o WHERE o.member.id = ?1 AND o.status = ?2 ORDER BY o.createdAt DESC

지원하는 키워드

  • And · Or · Between · LessThan · GreaterThanEqual · Like · StartingWith
  • In · IsNull · True · Not · Containing · IgnoreCase ...

이름이 규칙에 안 맞으면 기동 시점에 실패한다 — 오타를 런타임이 아니라 부팅에서 잡아 준다는 게 장점이다.

PropertyReferenceException: No property 'memberld' found for type 'Order'

이름이 길어지면 @Query로

@Query("select o from Order o join fetch o.member where o.status = :status")
List<Order> findWithMember(@Param("status") String status);

@Query(value = "SELECT * FROM orders WHERE ...", nativeQuery = true)
List<Order> findNative();

조건이 4개를 넘어가면 이름이 읽기 어려워진다 — 그 지점이 @Query나 QueryDSL로 넘어갈 신호다.

save()의 숨은 분기

// SimpleJpaRepository
public <S extends T> S save(S entity) {
    if (entityInformation.isNew(entity)) { em.persist(entity); return entity; }
    else { return em.merge(entity); }        // ← 여기가 함정
}

merge 는 '준영속 엔티티를 다시 붙이는' 연산이다

  • DB 를 한 번 SELECT 해서 현재 상태를 읽고
  • 넘긴 엔티티의 모든 필드로 덮어쓴다

결과: 일부 필드만 채워 넘기면 나머지가 null 로 덮인다

영속 상태의 엔티티는 save()를 부를 필요가 없다 — 변경 감지가 알아서 한다.

면접 함정

  • "Spring Data가 리플렉션으로 매번 쿼리를 만든다" → 기동 시점에 만들어 둔다.
  • "save()는 항상 INSERT/UPDATE" → 새 엔티티면 persist, 아니면 merge다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — Spring Data JPA — 리포지토리·페이징·프로젝션