[JPA] JPQL(Java Persistence Query Language, 객체 지향 쿼리)

dooboocookie·2022년 12월 5일
0
post-thumbnail

JPQL

  • 객체 지향 쿼리
  • JPA와 같은 ORM 기술은 엔티티 객체로서 데이터를 다루게 된다.
  • 특정 상황에 맞춰 필요한 데이터를 DB에서 불러오기 위한 SQL문 즉, 질의문이 필요하다.
  • 검색 대상을 테이블이 아닌 객체로 하는 것이 JPQL이다.
  • 실제로 DB에서 실행되는 쿼리문은 JPQL을 SQL로 파싱한 것이다.
    • 특정 데이터베이스의 SQL에 의존적이지 않다.
  • JPQL 문법에 맞춰 원하는 SQL문이 실행되도록 잘 실행하는 것이 목적이다.

select 문

select 조회할 대상 ...
from 검색대상 객체 [as] 별칭(필수)
[where]
[groupby]
[having]
[orderby]
  • 기본적인 모양
select h
from Hospital h
where h.id

Query API

  • EntityManager 인터페이스
public interface EntityManager {
    // ...
    Query createQuery(String var1);
    // ...
    <T> TypedQuery<T> createQuery(String var1, Class<T> var2);
	// ...
}
  • createQuery() 메소드에 JPQL 쿼리문을 스트링으로 입력하여 결과 반환

  • TypeQuery< T> : 검색 대상이 명확하여 미리 지정할 수 있을 때 사용

    • select m form Member m
      • 검색 대상이 Member.class로 명확
      • em.createQuery(query, Member.class);
    • select m.username from Member m
      • 검색 대상이 String.class로 명확
      • em.createQuery(query, String.class);
  • Query : 검색 대상이 하나가 아니거나 명확하지 않아 미리 지정할 수 없을 때 사용

    • select m.username, m.age from Member m
      • em.createQuery(query);
      • 결과 반환 시 Object[]로 반환
  • TypeQuery나 Query의 결과를 반환하는 방법

    • .geResultList()
      • List< X> 리스트를 반환
      • 결과가 있으면, 그를 리스트로 반환하고
      • 결과가 없으면, 빈 리스트를 반환
    • .getSingleResult()
      • 결과가 딱 하나일 때, 타입에 맞춰 반환
      • 결과가 없으면, NoResultException 예외
      • 결과가 많으면, NonUniqueResultException 예외

from 절

  • 검색에 대상이 되는 엔티티 객체가 들어간다. (테이블 X)
  • SQL로 파싱이될 때 매핑된 테이블로 변환
  1. 엔티티 매핑
@Entity
@Table(name="MEMBER)
class Member {
	//...
}
  1. JPQL 실행 시
em.createQuery("select m from Member m", Member.class).getResultList();
  1. DB에서 호출되는 쿼리문
SELECT
	m.id, ...
FROM
	MEMBER m;

JOIN

  • SQL의 조인과 동일하게 동작
  • 엔티티 객체끼리의 연결

inner join

  • 내부 조인
    • 가장 기본이 되는 조인
select m
from Member m
[inner] join m.team t 
//m 즉 Member와 연관이 있는 Team으로 조인하겠다는 의미

outer join

  • 외부 조인
    • 해당되는 조인하는 쪽에 내용이 없어도 주가 되는 쪽에 내용은 모두 출력하고 없으면 null로 처리
select m
from Member m
left [outer] join m.team t 
// LEFT가 되는 m 즉 Member는 모두 가져오고 그에 대한 Team의 정보를 조인한다. 없으면 null

세타 join

  • 크로스 조인, 곱 조인
    • 연관성이 없는 정보를 카테시안 곱으로 모두 가져오는 조인
    • 예) 멤버1,2,3과 팀1,2,3 두 테이블을 조인하면, 9개의 결과가 출력된다.
select m.username, t.teamname
from Member m, Team t
// 멤버 1 | 팀 1
// 멤버 1 | 팀 2
// 멤버 1 | 팀 3
// 멤버 2 | 팀 1
// :
// 멤버 3 | 팀 3

select 절

  • 검색을 위한 데이터를 입력

프로젝션

  • 엔티티 프로젝션
    • 엔티티로 매핑된 엔티티 객체를 대상으로 조회하는 것
    • select m from Member m
    • select m.team from Member m
  • 스칼라 타입 프로젝션
    • 숫자, 문자 등 기본 값 타입을 조회하는 것
    • select m.username, m.age from Member m
  • 임베디드 타입 프로젝션
    • 임베디드 타입 객체를 대상으로 조회하는 것
    • select m.address from Member m

여러 값 조회

  • 하나의 값(엔티티, 스칼라 타입, 임베디드 타입)이 아닌 여러 값을 조회하기 위한 방법
    • Query createQuery(String var1).getResultList;Object[] 반환
    • DTO를 이용한 방법
      • select new 패키지경로.NameAgeDTO(m.username, m.age) from Member m

조건식

  • 조건에 해당되면 then 의 정보로 가져옴
select
	case when 조건1 then 내용1
    	 when 조건2 then 내용2
    	 else '내용3'
   	end
from Member m
  • 조건에 일치하면 정보를 가져옴
select
	case 비교대상
    	 when1 then 내용1
    	 when2 then 내용2
    	 else 내용3
   	end
from Member m

where 절

  • 검색 조건을 나타내는 절
  • 직접 검색조건으로 string으로 입력하거나, 상황에 따라 파라미터 바인딩을 사용

파라미터 바인딩

  • setParameter()를 통하여 이름이나 위치로 바인딩 할 수 있지만, 위치로 바인딩하게 되면 수정이 힘들어 이름으로 바인딩
TypeQuery<Member> query = em.createQuery(
								"select m " + 
                                "from Member m "
                                "where m.age >= :userAge "
                                "and m.username = :userName", Member.class);
                                
List<Member> = query
	.setParameter("userAge", 18)
    .setParameter("userNmae", "김%")
    .getResultList();
 // 18세 이상의 김씨성을 가진 회원 정보 

서브쿼리

  • 다른 엔티티에서 EXISTS, ALL, ANY, SOME, IN 과 같은 정보를 통해서 조건을 주고싶을 때 사용.
  • SQL과 매우 유사
    • having 절에서도 사용 가능
    • from 절에서 인라인 뷰로 사용할 수 없다. → join을 통해서 해결

경로 표현식

  • 객체의 필드를 .으로 그래프 탐색하는 표현식
hopital.sigudong.parent.sigudongName
//hospital 엔티티 > sigudong (연관 필드) > 부모sigudong (연관 필드) > 이름 (상태 필드)

상태 필드

  • 값 타입의 필드로서 컬럼의 상태를 나타내는 값을 저장하는 필드
    • 이름, 나이, 날짜, ...
  • 값 타입에는 더 추가적으로 그래프 탐색할 수 있는 부분이 없으므로 추가적인 탐색이 불가하다.

연관 필드

엔티티

  • 연관관계로 이어진 특정 엔티티를 지칭하는 필드
    • 카테고리, 팀, ...
    • @XXXToOne
    • 실제 DB 테이블에 조인 컬럼에는 FK가 들어간다.
  • 해당 필드도 엔티티이므로 추가적인 탐색이 가능하다.

컬렉션

  • 연관관계로 이어진 엔티티들의 목록을 컬렉션으로 갖고있는 필드
    • 사진들, 팀원들, ...
    • @XXXToMany
    • 실제 DB에는 컬럼이 존재하지 않으며 해당 PK를 컬렉션에 있는 엔티티들이 FK로 갖고있다.
  • 해당 필드는 컬렉션 값이므로 JPQL에서 추가적인 그래프 탐색은 불가하다.

묵시적 조인

  • 검색 대상을 그래프 탐색으로 연관 필드를 select하면 묵시적인 조인이 발생한다.
//엔티티
select h.sigudong 
from Hospital h

//컬렉션
select h.hosImgs 
from Hospital h
select s.*
from Hospital h join Sigudong s
on h.sigudong_id = s.sigudong_id;

select i.* 
from Hospital h join HosImg i
on h.hospital_id = i.hospital_id;

함수

JPQL 함수

  • CONCAT, SUBSTRING, TRIM, LOWER, UPPER, LENGTH, LOCATE, ABS, SQRT, MOD, SIZE, INDEX 등이 있음

사용자 정의 함수

  • 연결해놓은 DB의 방언을 상속받은 클래스를 만들고 그 클래스를 방언으로 사용한다.
  • 그 방언에 사용자 정의 함수를 저장한다.
public class MyH2Dialect extends H2Dialect {
    public MyH2Dialect() {
        registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
    }
}
<property name="hibernate.dialect" value="dialect.MyH2Dialect"/>
  • 실제 org.hibernate.dialect에 있는 기본 JPQL 방언들도 위와 같이 등록 되어있다.

NamedQuery

  • 미리 정의한 쿼리를 애플리케이션 로딩 시점에 특정 이름에 초기화하고 사용하는 쿼리
  • 애플리케이션 로딩 시점에 해당 쿼리문이 잘못되었는지를 파악할 수 있으므로 애플리케이션 사용 중 해당 쿼리가 문제를 발생시키지 않음
  • Spring Data JPA@Query의 경우도 NamedQuery 로 등록하여 사용하는 것이다.
  • 등록
@Entity
@NamedQuery(
   name = "Member.findByUserName",
   query = "select m from Member m where m.username = :username")
public class Member {
	// ...
}
  • 호출
em.createQuery("Member.findByUserName", Member.class)
	.setParameter("username", "두부쿠키");
  • 정의 방법
    • XML로 정의 (우선 순위 높음)
      • 상황에 따라 다른 XML로 배포할 수 있음
    • 엔티티에 어노테이션으로 등록

벌크 연산 (update, delete, insert, ...)

  • JPA로 개발을 하다보면 더티체킹으로 update문을 날리는 경우가 많다.
  • 하지만 일괄적으로 데이터를 update해야되는 상황이 있다.
    • 예) 직원들의 급여를 일괄적으로 상향시켜야되는 경우...
int resultCount = em.createQuery("update Member m set m.salary = m.salary * 1.2")
                    .executeUpdate();
                    // 적용된 엔티티 객체 수(레코드 수) 반환

주의점

  • 벌크 연산 실행 시, flush()가 일어난다.
  • 벌크 연산 실행 후, 영속성 컨텍스트의 1차 캐시DB정보 불일치가 있을 수 있다.
int resultCount = em.createQuery("update Member m set m.age = 20")
        .executeUpdate();
System.out.println(resultCount);
// 이 변경 값이 DB에는 날라가서 적용되어있지만
// 현재 영속성 컨텍스트 내부에서는 변경이 안되어 있다.
em.clear(); // 이게 없냐 있냐에 따라 달라짐
List<Member> select_m_from_member_m_2 = em.createQuery("select m from Member m ", Member.class).getResultList();
  • 위에 clear()가 없으면, update 문 이전의 정보가 1차 캐시에 남아 있다.
    • 트랜잭션이 끝나거나, 영속성 컨텍스트가 초기화 되지 않는 이상 업데이트 되기 전 정보로 find 된다.
profile
1일 1산책 1커밋

0개의 댓글