JPA에서는 다양한 쿼리 방법을 지원한다. 하나씩 알아보자.
1. JPQL
- 정의 : 테이블, 칼럼 대신 엔티티와 필드 이름으로 쓰는 쿼리로 JPA가 자동으로 SQL로 바꿔서 DB에서 실행한다.
- 필요성 : 코드가 객체(엔티티) 중심이라 이해하기 쉽다. 연관관계도 자연스럽게 조인이 가능하다.
- 언제 사용하는가 : 일반적인 조회 전부
- 장점 : db에 덜 묶이고 연관관계 사용하기 쉽다.
- 단점 : 동적 조건이 많아지면 문자열 쿼리라 지저분해지기 쉽다.
public interface MemberRepository extends JpaRepository<Member, Long> {
// 팀 이름으로 회원 찾기
@Query("select m from Member m join m.team t where t.name = :team")
List<Member> findByTeamName(@Param("team") String teamName);
}
2. QueryDSL
- 정의 : 자바 코드 체인으로 JPQL을 만드는 라이브러리이다.
- 필요성 : 검색 조건이 바뀌는 동적 쿼리를 깔끔하고 안전하게 만들려면 문자열 대신 코드가 편하다.
- 언제 사용하는가 : 필터가 많은 목록, 검색 화면
- 장점 : 가독성이 좋다.
- 단점 : 처음에 Q타입 생성 설정이 필요하다.
public interface MemberQueryRepository {
List<MemberDto> search(String teamName, Integer minAge, String keyword);
}
@Repository
@RequiredArgsConstructor
public class MemberQueryRepositoryImpl implements MemberQueryRepository {
private final JPAQueryFactory qf;
QMember m = QMember.member;
QTeam t = QTeam.team;
@Override
public List<MemberDto> search(String teamName, Integer minAge, String keyword) {
return qf.select(Projections.constructor(MemberDto.class, m.id, m.name, t.name))
.from(m)
.join(m.team, t).fetchJoin()
.where(
teamName != null ? t.name.eq(teamName) : null,
minAge != null ? m.age.goe(minAge) : null,
keyword != null ? m.name.containsIgnoreCase(keyword) : null
)
.orderBy(m.id.desc())
.fetch();
}
}
3. Spring Data JPA 메소드 쿼리
- 정의 : 리파지토리 메소드 이름으로 간단한 쿼리를 만들거나, `@Query` 에 JPQL을 한 줄 적어서 바로 사용한다.
- 필요성 : 자주 쓰는 단순 조건을 빠르게 만들고, 쿼리를 리파지토리에 명시적으로 고정하기 좋다.
- 언제 사용하는가 : 단순 조회, 특정 화면에서 쓰는 고정 쿼리, 연관을 함께 가져오고 싶은 상황
- 장점 : 생산성이 높고 `@EntityGraph` 으로 연관을 함께 로딩하도록 선언도 가능하다.
- 단점 : 복잡하고 동적인 조건이 있는 경우 한계가 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
// 메소드 이름만으로
List<Member> findByAgeGreaterThanEqual(int age);
// 연관을 함께 로딩(화면에서 추가 쿼리 막기)
@EntityGraph(attributePaths = "team")
Optional<Member> findWithTeamById(Long id);
// JPQL 고정
@Query("select m from Member m where m.name like concat(:kw, '%')")
List<Member> searchByName(@Param("kw") String keyword);
}
4. 네이티브 SQL
- 정의 : JPA를 통해 SQL을 그대로 실행하는 방법이다.
- 필요성 : JPQL, QueryDSL로 표현하기 어려운 db 전용 기능을 사용하고 싶을 경우에 사용한다.
- 언제 사용하는가 : 리포트성 쿼리, 성능 튜닝 포인트가 필요한 경우
- 장점 : db 기능을 있는 그대로 활용할 수 있다.
- 단점 : db에 강하게 묶여 있고, 결과 매핑 코드를 직접 챙겨야 한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
// 엔티티로 매핑
@Query(value = "SELECT * FROM member WHERE team_id = :teamId ORDER BY id DESC", nativeQuery = true)
List<Member> findNativeByTeamId(@Param("teamId") Long teamId);
}
5. JDBC Template
- 정의 : 순수 SQL을 깔끔하게 실행하도록 도와주는 스프링 도구이다.
- 필요성 : 레거시 SQL 재사용, 대량 조회, 배치, 집계처럼 SQL이 더 빠르고 쉬운 구간이 있다.
- 언제 사용하는가 : 보고서나 통계
- 장점 : 가볍고 직관적이다.
- 단점 : JPA 기능이 없다. 그냥 SQL이다.
public List<MemberDto> report(Long teamId) {
String sql = "SELECT id, name FROM member WHERE team_id = ? ORDER BY id DESC";
return jdbc.query(sql,
(rs, i) -> new MemberDto(rs.getLong("id"), rs.getString("name")),
teamId);
}
6. MyBatis
- 정의 : SQL을 XML/어노테이션 매퍼로 관리하고 <if>, <where>와 같은 태그로 동적 SQL을 쉽게 만드는 프레임워크이다.
- 필요성 : 조건이 매우 다양한 검색 화면처럼, SQL 주도가 더 읽기 좋은 곳이 있다.
- 언제 사용하는가 : 복잡한 동적 WHERE, ORDER 조합
- 장점 : 동적 SQL에 강하다.
- 단점 : JPA 기능이 없다. 그냥 SQL이다.
'Spring' 카테고리의 다른 글
| [Spring] 외부 API 연동하기 (RestTemplate vs. WebClient vs. OpenFeign) (0) | 2025.10.05 |
|---|---|
| [Spring] N+1 문제 (0) | 2025.10.03 |
| [Spring] 즉시 로딩과 지연 로딩 (0) | 2025.09.21 |
| [Spring] Transactional 사용 시 자기 호출(Self-Invocation) 이슈 (0) | 2025.09.11 |
| [Spring] Spring Events 사용해 이벤트 발행하기 (0) | 2025.09.07 |
