[JPA] 객체지향 쿼리 언어1 - 기본 문법

윤경·2021년 10월 21일
0

JPA

목록 보기
12/22
post-thumbnail

📌

ORM(Object-relational mapping)

: 객체 관계 매핑
객체는 객체대로 설계, 관계형 데이터베이스는 관계형 데이터베이스대로 설계.
ORM 프레임워크가 중간에서 매핑해준다.

JPA(Java Persistence API)

Hibernate: ORM 프레임워크, 오픈소스 소프트웨어

JPA: 현재 자바 진영의 ORM 기술 표준, 인터페이스 모음
즉, 실제로 동작하는 것은 아니며 JPA 인터페이스를 구현한 대표적 오픈소스가 Hibernate

왜 JPA를 쓰는가?

  • SQL 중심적인 개발에서 객체 중심으로 개발
  • 생산성 (즉, 간단한 CRUD)
    ➡️ 예를 들어
    저장: em.persist(member)
    조회: Member member = em.find(memberId)
    수정: member.setName("changeName")
    삭제: em.remove(member)
    특히나 수정이 굉장히 간단 → 객체를 변경하면 알아서 DB에 업데이트 쿼리가 전달
  • 유지보수

[1] 소개

JPA는 다양한 쿼리 방법을 지원한다.

➡️ JPQL, JPA Criteria(비추), QueryDSL, 네이티브 SQL, JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용

JPQL

: SQL과 굉장히 유사한 가장 단순한 조회 방법

Ex. EntityManager.find() 이 가장 단순한 방법임
그리고 필요하면 객체 그래프 탐색(a.getB().getC())를 사용하기도 한다.

  • JPA를 사용하면 엔티티 객체를 중심으로 개발할 수 있다.

  • 문제는 검색 쿼리인데 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 한다.

  • 모든 DB 데이터를 객체로 변환해 검색하는 것은 불가능하다.

  • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요하다.

  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다.

  • SQL과 문법이 유사 (SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원)

  • JPQL엔티티 객체를 대상으로 쿼리하며

  • SQL데이터베이스 테이블을 대상으로 쿼리한다는 점이 가장 큰 차이점이다.

JPQL은 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리이다.

  • SQL을 추상화해 특정 데이터베이스 SQL에 의존하지 않는다는 장점이 있다.
  • JPQL을 한마디로 정의하자면 객체 지향 SQL이라고 할 수 있다.

Criteria

선생님은 실무에서 절대 쓰지 않으신다고 이런게 있구나 알고 넘어가라고 하셨다,,^^

JPQL 빌더 역할을 한다.
동적 쿼리를 구현할 때 좋으며 오타내면 컴파일 시점 바로 잡아줄 수 있어 좋다.
하지만 너무 복잡하고 실용성이 없어 대신 QueryDSL을 사용하길 권장한다.

⭐️ QueryDSL

  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있다.
  • JPQL 빌더 역할
  • 컴파일 시점에 문법 오류를 찾을 수 있다.
  • 동적 쿼리 작성에 편리하다.
  • 단순하고 쉬워 실무 사용에 권장한다.
  • 재사용성

네이티브 SQL

  • JPA가 제공하는 SQL을 직접 사용하는 기능
  • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능
  • Ex. 오라클 CONNECT BY, 특정 DB만 사용하는 SQL 힌트
String sql =SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = ‘kim’";
List<Member> resultList =
            em.createNativeQuery(sql, Member.class).getResultList();

JDBC 직접 사용, SpringJdbcTemplate 등

  • JPA를 사용하며 JDBC 커넥션을 직접 사용하거나, SpringJdbcTemplate, 마이바티스등 함께 사용 가능
  • 단, 영속성 컨텍스트를 적절한 시점에 강제로 플러시 해줄 필요가 있다. (em.flush())
  • Ex. JPA를 우회해 SQL을 실행하기 직전 영속성 컨텍스트를 수동 플러시

[2] 기본 문법과 쿼리 API

프로젝트(hellojpa/jpql) 새로 생성

생성 후 pom.xml (다른 메이븐 프로젝트처럼) 세팅, resources/METE-INF/persistence.xml 세팅

참고로 사용 DB는 /~/test

소개

  • JPQL은 객체지향 쿼리 언어이다. 따라서 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
  • JPQL은 SQL을 추상화해 특정 데이터베이스 SQL에 의존하지 않는다.
  • JPQL은 결국 방언과 조합되어 SQL로 변환된다.

JPQL 문법

(SQL과 똑같음)

  • select m from Member as m where m.age > 18
  • 엔티티와 속성은 대소문자를 구분하므로 가지고 있는 property와 똑같이 써야한다. (Ex. Member, age)
  • JPQL 키워드는 대소문자를 구분하지 않는다. SELECT, from... 등은 어떻게 쓰든 상관 없다.
  • 테이블 이름이 아닌 엔티티 이름을 사용해야 한다. (지정해주지 않으면 클래스와 똑같이 맞춰줌)
  • 별칭은 필수이다. (m) (as는 생략 가능)

TypeQuery, Query

        try {
            Member member = new Member();
            member.setUsername("member1");
            em.persist(member);

            TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);  // 타입정보를 명기했기 때문에 반환 타입이 TypedQuery
            Query query2 = em.createQuery("select m.username, m.age from Member m");  // 이와 같이 타입정보를 명기할 수 없는 경우도 있음
            
            tx.commit();
        } catch (Exception e){

TypeQuery: 반환 타입이 명확할 때 사용

Query: 반환 타입이 명확하지 않을 때 사용

결과 조회 API

  • query.getResultList(): 결과가 하나 이상일 때, 리스트 반환
    결과가 없으면 빈 리스트를 반환한다.

  • query.getSingleResult(): 결과가 정확히!! 하나일 때, 단일 객체를 반환
    결과가 없다면 javax.persistence.NoResultException 예외 발생
    둘 이상이라면 javax.persistence.NonUniqueResultException 예외 발생


[3] 프로젝션(SELECT)

: SELECT 절에 조회할 대상을 지정하는 것

프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입)

SELECT m FROM Member m: 엔티티 프로젝션(멤버 엔티티를 조회한다는 것)
SELECT m.team FROM Member m: 엔티티 프로젝션
SELECT m.address FROM Member m: 임베디드 타입 프로젝션 (예제에서 address가 임베디드 타입이므로)
SELECT m.username, m.age FROM Member m: 스칼라 타입 프로젝션

  • DISTINCT로 중복 제거

여러 값 조회

SELECT m.username, m.age FROM Member m

  1. Query 타입으로 조회

  2. Objectp[] 타입으로 조회

  3. new 명령어로 조회(제일 깔끔한 방법)

  • 단순 값을 DTO로 바로 조회
  • 패키지 명을 포함한 전체 클래스 명 입력
  • 순서와 타입이 일치하는 생성자 필요

[4] 페이징

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

[5] 조인

(sql 조인이랑 실행되는 건 똑같음)

📌 JOIN VS LEFT JOIN

JOIN (INNER JOIN) 내부조인

: Table A와 Table B의 교집합을 조회

LEFT JOIN (LEFT OUTER JOIN) 외부조인

: Table A와 Table B의 합집합을 조회

내부조인

SELECT m FROM Member m [INNER] JOIN m.team t
inner는 그냥 join으로 써도 됨

외부조인

SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
회원은 있고 팀이 없어도 팀 데이터 다 null이고 멤버 조회 가능 ➡️ 내부 조인과의 차이

세타조인

select count(m) from Member m, Team t where m.username = t.name
연관관계가 딱히 없는 걸 조인하고 싶을 때. 막조인(?)

ON 절

  • ON절을 활용한 조인(JPA 2.1부터 지원)
    - 1. 조인 대상 필터링
    - 2. 연관관계가 없는 엔티티도 외부조인이 가능해짐(하이버네이트 5.1부터)

1. 조인 대상 필터링

EX) 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인

2. 연관관계 없는 엔티티 외부 조인

EX) 회원의 이름과 팀의 이름이 같은 대상 외부 조인


[6] 서브 쿼리

: 일반적인 쿼리 안에서 또 쿼리를 만드는

Ex)

1. 나이가 평균보다 많은 회원

select m from Member m where m.age > (select avg(m2.age) from Member m2)
() 안에서 평균값을 만드는 쿼리

2. 한 건이라도 주문한 고객

select m from Member m where (select count(o) from Order o where m = o.member) > 0
(예제 1은 메인 쿼리랑 서브 쿼리랑 전혀 영향을 안 끼치지만 2 예제는 그와는 다르게 메인 쿼리를 끌고 왔다.)

서브쿼리 지원 함수

  • [NOT] EXITS(subquery): 서브쿼리에 결과가 존재하면 참
    - {ALL | ANY | SOME} (subquery) 이 세가지를 섞어 쓸 수 있음
    - ALL: 모두 만족하면 참
    - ANY, SOME: 같은 의미. 조건을 하나라도 만족하면 참

  • [NOT] IN(subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참

JPA 서브쿼리의 한계

  • JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
  • SELECT 절도 가능(하이버네이트에서 지원)
  • ⭐️ FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
    ➡️ 조인으로 풀 수 있으면 풀어서 해결해야 함

[7] JPQL 타입 표현과 기타식

JPQL 타입 표현

  • 문자: 'HELLO', 'She''s'
  • 숫자: 10L(Long), 10D(Double), 10F(Float)
  • Boolean: TRUE, FALSE
  • ENUM: jpabook.MemberType.Admin처럼 자바 패키지명을 모두 포함해야 하므로 주의
  • 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)

JPQL 기타

  • SQL과 문법이 같은 식
  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <>
  • BETWEEN, LIKE, IS NULL

[8] 조건식(CASE 등등)

조건식 - CASE 식

  • COALESCE: 하나씩 조회해 null이 아니면 반환
  • NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환

기본 CASE 식

select
	case when m.age <= 10 then '학생요금'
    	when m.age >= 60 then '경로요금'
	else '일반요금'
end
from Member m

단순CASE 식

select
	case t.name
		when '팀A' then '인센티브110%'
        	when '팀B' then '인센티브120%'
       		else '인센티브105%'
    	end
from Team t

[9] JPQL 함수 & 사용자 정의 함수

사용자 정의 함수 호출

  • 그냥 쓸 수 있는건 아니고 하이버네이트는 사용 전 방언에 추가해야 한다.
    - 사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.


참고

profile
개발 바보 이사 중

0개의 댓글