JPQL
- SQL이 데이터베이스 테이블을 대상으로 하는 데이터 중심의 쿼리라면, JPQL은 엔티티 객체를 대상으로 하는 객체지향 쿼리이다.
- JPQL을 사용하면 JPA는 JPQL을 분석한 다음 적절한 SQL을 만들어서 데이터베이스를 조회하고, 조회한 결과로 엔티티 객체를 생성해서 반환한다.
- 문법은 SQL과 비슷하지만, SQL을 추상화했기 때문에 특정 데이터베이스 SQL에 의존하지 않는다.
SELECT
List<Member> members = em.createQuery("SELECT m FROM Member m WHERE m.username = :username", Member.class)
.setParameter("username", username)
.getResultList();
- JPQL 키워드는 대소문자를 구분하지 않지만, Member나 username 같은 엔티티와 속성은 대소문자를 구분한다.
- JPQL에서 사용한 Member는 클래스 명이 아니라 엔티티 명이며, 엔티티 명을 따로 지정하지 않으면 클래스 명을 기본값으로 사용한다.
- 별칭은 필수로 작성해야 하고, AS는 생략 가능하다.
쿼리 객체
- 작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 하며, 쿼리 객체에는 TypeQuery와 Query가 있다.
- 반환할 타입을 명확하게 지정할 수 있으면 TypeQuery를 사용하고, 반환 타입을 명확하게 지정할 수 없으면 Query를 사용한다.
- SELECT 절에서 여러 엔티티나 컬럼을 선택할 때는 반환할 타입이 명확하지 않으므로 Query 객체를 사용해야 한다.
- 이후, 생성된 쿼리 객체에 대해 getResultList()나 getSingleResult() 메서드를 호출하면 실제 쿼리를 실행해서 데이터베이스를 조회한다.
- getResultList(): 결과를 리스트로 반환한다. 만약 결과가 없으면 빈 컬렉션을 반환한다.
- getSingleResult(): 결과가 정확히 하나일 때 사용한다. 결과가 없거나 2개 이상이면 예외 발생
파라미터 바인딩
- JDBC는 위치 기준 파라미터 바인딩만 지원하지만 JPQL은 이름 기준 파라미터 바인딩도 지원한다.
- 만약 파라미터 바인딩 방식을 사용하지 않고 직접 문자를 더해서 넣으면 악의적인 사용자에 의해 SQL 인젝션 공격을 당할 수 있어 위험하다.
- 또한 파라미터 바인딩 방식을 사용하면 애플리케이션과 데이터베이스 모두 해당 쿼리의 파싱 결과를 재사용할 수 있어 전체 성능이 향상된다.
PROJECTION
- SELECT 절에 조회할 대상을 지정하는 것을 프로젝션이라고 하며, 프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입이 있다.
- 엔티티 프로젝션으로 조회한 엔티티는 영속성 컨텍스트에서 관리되지만, 임베디드 타입 프로젝션으로 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.
DTO로 반환받기
- SELECT 다음에 new 명령어를 사용하면 반환받을 클래스를 지정할 수 있는데, 이 클래스의 생성자에 JPQL 조회 결과를 넘겨줄 수 있다.
- new 명령어를 사용할 때는 패키지 명을 포함한 전체 클래스 명을 입력해야 하고, 순서와 타입이 일치하는 생성자가 필요하다.
TypedQuery<UserDto> query = em.createQuery("SELECT new 경로.UserDto(m.username, m.age) FROM Member m", UserDto.class);
List<UserDto> result = query.getResultList();
조인
- JPQL 조인의 가장 큰 특징은 연관 필드를 사용한다는 것으로, 연관 필드는 다른 엔티티와 연관관계를 가지기 위해 사용하는 필드를 말한다.
- 이러한 차이점을 제외하고는 JPQL의 내부 조인, 외부 조인은 SQL의 내부 조인, 외부 조인과 동일하다.
SELECT m
FROM Member m JOIN m.team t
WHERE t.name = :teamName
컬렉션 조인
- 일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하는 것을 컬렉션 조인이라고 한다.
- 예를 들어 팀과 팀이 보유한 회원목록을 컬렉션 값 연관 필드로 외부 조인하는 경우 아래와 같이 작성할 수 있다.
SELECT t, m FROM Team t LEFT JOIN t.members m
세타 조인
- 세타 조인을 사용하면 전혀 관계없는 엔티티도 조인할 수 있다.
- WHERE 절을 사용해서 조인하며, 내부 조인만 지원한다.
SELECT COUNT(m)
FROM Member m, Team t
WHERE m.username = t.name
JOIN ON절
- JPA 2.1부터 조인할 때 ON 절을 지원하며, ON절을 사용하면 조인 대상을 필터링하고 조인할 수 있다.
- 내부 조인의 ON 절은 WHERE 절을 사용할 때와 결과가 같으므로 보통 ON 절은 외부 조인에서 사용한다.
SELECT m, t
FROM Member m
LEFT JOIN m.team t ON t.name = 'A';
페치 조인
- 페치 조인은 SQL에서 사용하는 조인의 종류는 아니고, JPQL에서 성능 최적화를 위해 제공하는 기능이다.
- 페치 조인은 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능으로, JOIN FETCH 명령어로 사용할 수 있다.
엔티티 페치 조인
- 페치 조인을 사용해서 회원 엔티티를 조회하면서 연관된 팀 엔티티도 함께 조회하려면 아래와 같이 작성한다.
// JPQL
SELECT m
FROM Member m
JOIN FETCH m.team
// 실제 실행되는 SQL
SELECT M.*, T.*
FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID = T.ID
- 엔티티 페치 조인 JPQL에서 회원 엔티티만 선택해도, SQL 문에서는 회원과 연관된 팀도 함께 조회하므로 지연 로딩이 발생하지 않는다.
- 만약 회원과 팀을 지연 로딩으로 설정했더라도, 회원을 조회할 때 팀도 함께 조회했으므로 연관된 팀 엔티티는 프록시가 아닌 실제 엔티티이기 때문에 지연 로딩이 일어나지 않는다.
- 또한, 프록시가 아닌 실제 엔티티이므로 회원 엔티티가 영속성 컨텍스트에서 분리되어 준영속 상태가 되어도 연관된 팀을 조회할 수 있다.
컬렉션 페치 조인
- 팀을 조회하면서 페치 조인을 사용해서 연관된 회원 컬렉션도 함께 조회
SELECT t
FROM Team t JOIN FETCH t.members
WHERE t.name = '팀A'
※ 참고로 일대다 조인은 결과가 증가할 수 있지만 일대일, 다대일 조인은 결과가 증가하지 않는다.
DISTINCT
- JPQL의 DISTINCT 명령어는 SQL문에 DISTINCT를 추가해서 중복을 제거하고, 추가로 애플리케이션에서 한 번 더 중복을 제거한다.
- 위의 예제에서 DISTINCT를 추가하면 팀 엔티티의 중복을 제거해 주므로 아래와 같은 결과가 나온다.
페치 조인과 일반 조인
- JPQL은 결과를 반환할 때 연관관계까지 고려하지 않고, 단지 SELECT 절에 지정한 엔티티만 조회한다.
- 따라서 페치 조인을 사용하지 않고 일반 조인만 사용하면, 팀 엔티티만 조회하고 연관된 회원 컬렉션은 조회하지 않는다.
- 만약 회원 컬렉션을 지연 로딩으로 설정하면 프록시나 아직 초기화되지 않은 컬랙션 래퍼를 반환하고, 즉시 로딩으로 설정하면 회원 컬렉션을 즉시 로딩하기 위해 쿼리를 한 번 더 실행한다.
페치 조인의 한계
- 페치 조인을 사용하면 SQL 한 번으로 연관된 엔티티들을 함께 조회할 수 있으므로 SQL 호출 횟수가 줄어 성능을 최적화할 수 있다.
- 또한 페치 조인을 사용하면 연관된 엔티티를 쿼리 시점에 조회하므로 지연 로딩이 발생하지 않고, 준영속 상태에서도 객체 그래프를 탐색할 수 있다.
- 하지만 페치 조인은 다음과 같은 한계가 있다.
- 페치 조인 대상에는 별칭을 줄 수 없어 SELECT, WHERE 절, 서브 쿼리에 페치 조인 대상을 사용할 수 없다.
- 둘 이상의 컬렉션을 페치할 수 없다.
- 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
- 따라서 여러 테이블을 조인해서 엔티티가 가진 모양과 전혀 다른 결과를 내야 한다면 페치 조인을 사용하기보다는 여러 테이블에서 필요한 필드들만 조회해서 DTO로 반환하는 것이 더 효과적이다.
'Spring > JPA' 카테고리의 다른 글
영속성 컨텍스트와 연속성 관리 (0) | 2022.03.05 |
---|---|
N+1 문제와 해결 방법 (0) | 2022.03.05 |
JPA와 ORM (0) | 2022.02.23 |
QueryDSL (0) | 2022.02.04 |
스프링 데이터 JPA (0) | 2022.01.23 |