[JPA 프로그래밍 - 기본편] 객체지향 쿼리 언어1 - 기본 문법

지현·2022년 2월 11일
0

JPA

목록 보기
10/12

객체지향 쿼리 언어

  • JPA를 사용하면 엔티티 객체를 중심으로 개발
  • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색 해야함
  • 보통 JPQL이나 QueryDSL를 사용

JPQL

  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
  • 가장 단순한 조회 방법
  • 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
  • JPQL은 엔티티 객체를 대상으로 쿼리 생성, SQL은 데이터베이스 테이블을 대상으로 쿼리
  • 단순 스트링이기 때문에 동적 쿼리를 만들기는 어려움
//검색
 List<Member> result = 
 em.createQuery("select m From Member m where m.name like ‘%kim%'", Member.class)
 .getResultList();

Criteria

  • Criteria는 문자가 아닌 자바코드로 JPQL을 작성
  • 오타를 내면 컴파일 오류 발생시켜주고 동적 쿼리를 짜기 좋음
  • 너무 복잡하고 실용성이 없음
  • Criteria 대신에 QueryDSL 사용 권장

QueryDSL

  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있음
  • 컴파일 시점에 문법 오류를 찾을 수 있음
  • 동적쿼리 작성 편리함
  • 단순하고 쉬움

JPQL 기본 문법과 쿼리 API

JPQL

  • JPQL은 객체지향 쿼리 언어 > 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리함
  • JPQL은 SQL을 추상화해서 특정데이터베이스 SQL에 의존하지 않음
  • JPQL은 결국 SQL로 변환됨
  • 쿼리 한번으로 하나를 사용하는것이 아니라 여러개를 사용할 때 씀

JPQL 문법

select m from Member as m where m.age > 18

  • 엔티티와 속성은 대소문자 구분함
  • JPQL 키워드는 대소문자 구분하지 않음 (select, where 등..)
  • 테이블 이름이 아닌 엔티티 이름을 사용
  • 별칭은 필수 (as는 생략 가능)
  • 집합과 정렬 함수 제공
  • TypeQuery : 반환 타입이 명확할 때 사용
    Query : 반환 타입이 명확하지 않을 때 사용
  • query.getResultList() : 결과가 하나 이상일 때 리스트 반환, 결과가 없으면 빈 리스트 반환
    query.getSingleResult() : 결과가 정확히 하나, 단일 객체 반환, 결과가 없거나 둘 이상이면 예외 발생
  • 파라미터 바인딩
// 파라미터 바인딩 이름 기준일 때 (:username)
Member result = 
	em.createQuery("select m from Member m where m.username = :username", Member.class)
                    .setParameter("username", "member1")
                    .getSingleResult();

            System.out.println("result.getUsername() = " + result.getUsername());

위치기준(?1)도 제공하지만 위치 기반은 웬만하면 사용하지 않는것이 좋음


프로젝션(SELECT)

  • SELECT 절에 조회할 대상을 지정하는 것
  • 프로젝션 대상 : 엔티티 m m.team, 임베디드 타입 m.address, 스칼라 타입 m.username, m.age (숫자, 문자 등 기본 데이터 타입)
  • DISTINCT로 중복 제거
  • 프로젝션으로 가져온 엔티티들은 모두 영속성 컨텍스트에서 관리됨
  • SQL과 최대한 비슷하게 작성해야함 (어떤 쿼리가 나갈지 예측이 될 수 있도록)
List<Team> result = em.createQuery("select m.team from Member m", Team.class)
                    .getResultList();                    
// 해당 쿼리를 실행하면 join이 발생하기 때문에(Member에 있는 Team 참조)
                    
List<Team> result = em.createQuery("select t from Member m join m.team t", Team.class)
                    .getResultList();
// 그냥 처음부터 join을 넣어서 쿼리를 만들어주는 것이 좋음

여러 값 조회

SELECT m.username, m.age FROM Member m

  1. Query 타입으로 조회 (반환 타입이 명확하지 X)
List resultList = em.createQuery("select m.username, m.age FROM Member m")
					.getResultList();
 
Object o = resultList.get(0);
Object[] result = (Object[]) o;
  1. Object[] 타입으로 조회
List<Object[]> resultList = em.createQuery("select m.username, m.age FROM Member m")
								.getResultList();
 
Object[] result = resultList.get(0);
  1. new 명령어로 조회
    • DTO 클래스 생성
    • 패키지 명을 포함한 전체 클래스 명 입력
    • 순서와 타입이 일치하는 생성자 필요
public class MemberDTO{
	private String username;
    private int age;
    
    public MemberDTO(String username, int age){
    	this.username = username;
        this.age = age;
    }
    
    ...
	
}
em.createQuery(
	"select new jpql.MemberDTO(m.username, m.age) FROM Member m", MemberDTO.class)
	.getResultList();
 
 MemberDTO memberDTO = result.get(0);

페이징 API

  • JPA는 페이징을 다음 두 API로 추상화
  • setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
  • setMaxResults(int maxResult) : 조회할 데이터 수
//페이징 쿼리
 List<Member> resultList =
 	em.createQuery("select m from Member m order by m.age desc", Member.class)
 	.setFirstResult(1)
 	.setMaxResults(10)
 	.getResultList();

조인

  • 내부 조인 :
	  //inner는 생략 가능
      String query="select m from Member m inner join m.team t";
            List<Member> result = em.createQuery(query, Member.class)
                    .getResultList();
  • 외부 조인
			//outer는 생략 가능
            String query="select m from Member m left outer join m.team t";
            List<Member> result = em.createQuery(query, Member.class)
                    .getResultList();
  • 세타 조인 : 연관관계 없는 것을 비교해보고 싶을 때
			//cross join
            String query="select m from Member m,Team t where m.username=t.name";
            List<Member> result = em.createQuery(query, Member.class)
                    .getResultList();

ON절을 활용한 조인

  • on : 조인 할 때 조건
  1. 조인 대상 필터링
        String query="select m from Member m left join m.team t on t.name='teamA'";
        List<Member> result = em.createQuery(query, Member.class)
                    .getResultList();
  1. 연관관계 없는 엔티티 외부 조인 (내부 조인은 원래 가능했음)
        String query="select m from Member m left join Team t on m.username=t.name";
        List<Member> result = em.createQuery(query, Member.class)
                    .getResultList();

서브 쿼리

  • 쿼리 안에서 서브로 쿼리를 생성
  • 메인쿼리와 서브쿼리가 서로 관계가 없어야 성능이 잘 나옴
    select m from Member m where m.age > (select avg(m2.age) from Member m2)
  • 메인쿼리에 있는 대상을 서브쿼리로 끌고 오면 성능이 잘 나오지 않음
    select m from Member m where (select count(o) from Order o where m = o.member) > 0
  • 일반적인 SQL에서 지원하는 서브쿼리가 되는 것

서브 쿼리 지원 함수

  • [NOT] EXISTS (subquery) : 서브쿼리에 결과가 존재하면 참
  • {ALL | ANY | SOME} (subquery)
    • ALL : 모두 만족하면 참
    • ANY, SOME : 조건을 하나라도 만족하면 참
  • [NOT] IN (subquery) : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참

한계

  • WHERE, HAVING 절(JPA에서 지원), SELECT 절(하이버네이트에서 지원)에서만 서브 쿼리 사용 가능
  • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능 > 조인으로 풀 수 있으면 풀어서 해결, 그래도 안되는 경우에는 네이티브 쿼리 또는 쿼리를 두번 날리는 방식으로 해결

JPQL 타입 표현과 기타식

JPQL 타입 표현

  • 문자 : ' ' 사용
  • 숫자: 10L(Long), 10D(Double), 10F(Float), 자바와 비슷
  • Boolean : TRUE, FALSE (대소문자 구분 X)
  • ENUM : 패키지명 포함
        String query="select m.username, 'HELLO', true from Member m 
            								where m.type=jpql.MemberType.ADMIN";
        List<Member> result = em.createQuery(query, Member.class)
                    .getResultList();
		//파라미터 바인딩으로 하면 복잡하지 않음
        String query="select m.username, 'HELLO', true from Member m 
            								where m.type=:userType";
        List<Member> result = em.createQuery(query, Member.class)
        			.setParameter("userType",MemberType.ADMIN)
                    .getResultList();
  • 엔티티 타입 : 상속 관계에서 사용 (type(i) = Book)
		em.createQuery("select i from Item i where type(i) = Book", Item.class)
        				.getResultList();

기타

  • 표준 SQL은 다 지원
  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <>
  • BETWEEN, LIKE, IS NULL

조건식(CASE 등등)

  • 기본 CASE 식
        String query=
                "select " +
                        "case when m.age <= 10 then '학생요금' " +
                        "     when m.age >= 60 then '경로요금' " +
                        "     else '일반요금' " +
                        "end " +
                        "from Member m";
        List<String> result = em.createQuery(query, String.class)
                    .getResultList();
  • 단순 CASE 식
        String query=
                "select " +
                        "case t.name "+ 
                        "     when '팀A' then '인센티브110%' " +
                        "     when '팀B' then '인센티브120%' " +
                        "     else '인센티브105%' " +
                        "end " +
                        "from Team t";
        List<String> result = em.createQuery(query, String.class)
                    .getResultList();
  • COALESCE : 하나씩 조회해서 null이 아니면 반환
	    // 사용자 이름이 없으면 이름 없는 회원을 반환
        String query= "select coalesce(m.username, '이름 없는 회원') from Member m";
        List<String> result = em.createQuery(query, String.class)
                    .getResultList();
  • NULLIF : 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
		// 사용자 이름이 ‘관리자’면 null을 반환하고 나머지는 본인의 이름을 반환
        String query= "select nullif(m.username, '관리자') from Member m";
        List<String> result = em.createQuery(query, String.class)
                    .getResultList();

JPQL 함수

  • JPQL이 제공하는 표준 함수들은 데이터베이스에 관계 없이 그냥 사용하면 됨 (CONCAT, SUBSTRING, TRIM 등..)
  • 사용자 정의 함수는 사용전 데이터 베이스 방언에 추가해둬야함
// H2Dialect를 상속
public class MyH2Dialect extends H2Dialect {
    // 생성자에서 registerFunction해서 등록 (이름, 조건)
    public MyH2Dialect(){
        registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
    }
}
  • 하이버네이트는 이미 여러가지 함수들을 등록해서 제공해줌 (DB에 종속적이긴 함)


출처
[인프런] 자바 ORM 표준 JPA 프로그래밍 - 기본편

0개의 댓글