Spring Data JPA를 처음 접하고 @Query를 작성하다 보면, 기존에 알던 SQL과 쿼리 구조가 달라서 당황하는 순간이 옵니다. 특히 아래와 같은 코드를 마주했을 때 말이죠.
@Query("SELECT r FROM Region r WHERE r.name = :name AND r.parent.name = :parentName")
Optional<Region> findByNameAndParentName(
@Param("name") String name,
@Param("parentName") String parentName
);
SELECT * FROM 테이블이 아니라 SELECT r FROM Region r이라고요? 대체 r은 무엇이고, 빈 클래스에 데이터는 어떻게 담겨 오는 걸까요? 오늘은 이 쿼리의 동작 원리와 JPA의 데이터 흐름을 알기 쉽게 정리해 보겠습니다.
1. 일반 SQL과 JPQL의 결정적 차이
결론부터 말하자면, 위 코드에 작성된 것은 일반적인 데이터베이스 쿼리(SQL)가 아니라 객체 지향 쿼리(JPQL)입니다.
- 일반 SQL: 데이터베이스의 '테이블'과 '컬럼'을 대상으로 쿼리를 날립니다. (SELECT * FROM region_table)
- JPQL: 자바의 '클래스(Entity)'와 '객체'를 대상으로 쿼리를 작성합니다.
SELECT r FROM Region r이라는 문장을 해석하면 다음과 같습니다.
- Region: 테이블 이름이 아니라 자바 클래스(Entity)의 이름입니다.
- FROM Region r: "Region 클래스를 탐색할 건데, 그 안에서 나오는 각각의 데이터(객체)를 r이라는 변수명(별칭)으로 부를게!"라는 뜻입니다.
- SELECT r: "조건에 맞는 그 r 객체를 통째로 줘!"라는 의미입니다. 일반 SQL의 SELECT *와 같은 역할을 합니다.
💡 왜 굳이 r이라는 별칭(Alias)을 쓸까요? 일반 SQL과 달리, JPQL에서는 엔티티를 조회할 때 반드시 별칭을 지정해야 하는 문법적 규칙이 있습니다. 보통 엔티티 이름의 첫 글자를 따서 r처럼 직관적이고 짧게 짓습니다. (reg나 a로 지어도 선언만 맞춰주면 동일하게 동작합니다.)
2. 메서드의 구성 요소 뜯어보기
쿼리의 세부적인 조건과 메서드의 형태를 한 줄씩 살펴보겠습니다.
- 검색 조건 (WHERE r.name = :name AND r.parent.name = :parentName) 현재 지역의 이름(r.name)과 부모 지역의 이름(r.parent.name)이 모두 일치하는 데이터를 찾습니다. r.parent를 통해 데이터가 부모-자식 계층 구조로 연결되어 있음을 알 수 있습니다.
- 파라미터 바인딩 (@Param) 메서드로 전달받은 인자(name, parentName)를 쿼리 안의 :name, :parentName 자리에 정확하게 꽂아주는 역할을 합니다.
- 안전한 반환 (Optional<Region>) 검색 조건에 맞는 데이터가 없을 때 null이 반환되어 NullPointerException이 터지는 것을 방지하기 위해, 데이터를 안전한 Optional 객체로 감싸서 반환합니다.
3. 쿼리 실행 시 데이터 플로우
그렇다면 쿼리가 실행될 때, 빈 클래스는 어떻게 데이터를 품은 완성된 객체가 되어 돌아오는 걸까요? 내부적으로는 4단계의 플로우가 발생합니다.
- 쿼리 번역: JPA가 우리가 작성한 JPQL을 읽고, 실제 데이터베이스가 알아들을 수 있는 진짜 SQL로 몰래 번역합니다. (번역된 SQL: SELECT * FROM region WHERE name = '중구' AND parent_name = '서울특별시')
- DB 검색: 번역된 SQL이 데이터베이스로 날아가 조건에 맞는 데이터 한 줄(Row)을 찾아냅니다.
- 객체 매핑 (가장 중요한 단계): 데이터베이스에서 날것의 원시 데이터(Raw data)가 넘어오면, JPA가 메모리에 Region r = new Region(); 처럼 빈 객체를 하나 생성합니다. 그리고 가져온 데이터를 이 빈 객체의 필드에 알아서 쏙쏙 집어넣습니다. (이 과정을 객체 매핑 또는 영속화라고 부릅니다.)
- 완성된 객체 반환: 데이터가 꽉 채워진 완벽한 Region 객체가 Optional 상자에 담겨 개발자에게 반환됩니다.
결과적으로 개발자는 데이터베이스의 텍스트 조각을 직접 파싱할 필요 없이, 메서드 한 번 호출로 즉시 사용할 수 있는 '완성된 자바 객체'를 얻게 됩니다. 이것이 우리가 Spring Data JPA를 사용하는 가장 큰 이유입니다.
'백엔드 > 🍃 SpringBoot' 카테고리의 다른 글
| [Spring Boot] JPA ddl-auto의 한계 극복! ApplicationRunner로 안전한 DB 스키마 자동화하기 (0) | 2026.09.20 |
|---|---|
| [iOS/Web] 앱 환경에서 소셜 로그인(OAuth) 연동 시 발생하는 리다이렉트 문제와 해결법 (0) | 2026.06.05 |
| boolean 필드 매핑 안될 때 (isTermsAgreed가 false만 나오는 이유) (0) | 2026.03.20 |
| [JPA] @OneToMany, @ManyToOne 초간단 정리 (0) | 2026.03.03 |
| 배포시 spring-boot-devtools 비활성화 (1) | 2025.06.13 |