Spring/인프런_JPA

[Spring]자바 ORM 표준 JPA 프로그래밍 - 객체지향 쿼리 언어

빙응이 2024. 2. 23. 17:45

📝JPA의 다양한 쿼리 방법

JPA는 여러개의 쿼리 방법을 지원한다.

  • JPQL
  • JPQ Criteria(너무 복잡해서 설명 X)
  • QueryDSL
  • 네이티브 SQL
  • JDBC API ( MyBatis, SpringJdbcTemplate 등)

 

📝JPQL 소개

JPQL은 JPA에서 엔티티 객체를 대상으로 하는 쿼리 언어이다.

JPA는 객체 지향 프로그래밍에서 사용되는 엔티티 객체를 데이터베이스에 저장하고 관리하기 위한 자바 표준 기술이며, JPQL은 이를 활용하여 엔티티 객체에 대한 검색 및 조작을 수행하는 데 사용된다..

 

JPQL이 등장한 이유 중 하나는 객체 지향적인 관리를 위해 SQL 대신에 엔티티 객체를 대상으로 하는 쿼리가 필요했기 때문이다. 객체 지향 프로그래밍에서는 테이블과 컬럼보다는 객체와 속성에 중점을 두는데, 이런 객체 지향적인 모델을 유지하면서도 데이터베이스 검색이나 조작이 필요했기 때문에 JPQL이 도입되었다고 한다.

 

특징은 다음과 같다.

  • SQL과 문법 유사
  • JPQL은 엔티티 객체를 대상으로 쿼리 
    • 즉 JPQL로 객체 중심 쿼리를 짜면 JPA가 SQL로 변환해준다.
 //검색
 String jpql = "select m From Member m where m.name like ‘%hello%'";
 List<Member> result = em.createQuery(jpql, Member.class)
 .getResultList();

 

 

📝QueryDSL 소개

JPQL에는 큰 문제가 있다. 바로 쿼리문을 작성할 때 문자열 형태로 띄어쓰기를 신경쓰며 작성해야한다.

이 문제를 해결하기 위해 QueryDSL을 사용할 수 있다. 

이것은 자바 코드로 JPQL을 작성할 수 있다. 

 

특징은 다음과 같다.

  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있다
  • JPQL 빌더 역할
  • 컴파일 시점에 문법 오류를 찾음
  • 동적 쿼리 작성이 편리
  • 단순하고 쉽다
  • 실무 사용 권장
 JPAFactoryQuery query = new JPAQueryFactory(em);
 QMember m = QMember.member;
 List<Member> list = 
 query.selectFrom(m)
 .where(m.age.gt(18))
 .orderBy(m.name.desc())
 .fetch();

 

📝네이티브 SQL 

네이티브 SQL은 JPA가 제공하는 SQL을 직접 사용할 수 있다.

 

사용 시점은 JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능을 사용할 때 사용하면 된다.

String sql = “SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = ‘kim’"; 
List<Member> resultList = 
 em.createNativeQuery(sql, Member.class).getResultList();

 

 

📝JPQL 문법과 쿼리 API

JPQL는 앞에서 보았듯이 객체지향 쿼리 언어이며, 엔티티 객체를 대상으로 쿼리한다. 

기본적인 SQL과 형태가 비슷하나 엔티티가 객체이기에 차이점이 있다.

  • 엔티티와 속성은 대소문자 구분을 한다. 
  • JPQL 키워드는 대소문자 구분 X(SELECT,FROM,WHERE)
  • 엔티티 이름 사용, 테이블 이름이 아니다.
  • 별칭은 필수이다. (m)
TypeQuery, Query
  • TypeQuery : 반환 타입이 명확할 때 사용한다.
  • Query : 반환 타입이 명확하지 않을 때 사용한다. 
TypedQuery<Member> query =
 em.createQuery("SELECT m FROM Member m", Member.class); 
Query query = 
 em.createQuery("SELECT m.username, m.age from Member m");

위 사진처럼 특정 항목만 조회할때는 Member형이 아니므로 타입을 연계 불가능해 Query를 사용한다.

 

 컬렉션 결과 반환
  • query.getResultList(): 결과가 하나 이상일 때, 리스트 반환
    • 결과가 없으면 빈 리스트를 반환한다.
  • query.getSingleResult() : 결과가 정확히 하나일 때 반환
    • 결과에 따라 오류가 생길 수 있다. 
파라미터 바인딩

파라미터 바인딩이란 쉽게 생각하면 함수라 생각하면 된다. 기본 틀을 만들고 값을 바인딩으로 넣어 도출하는 것이다.

  • 이름 기준 바인딩
    • (:)를 넣어서 만들 수 있다.
SELECT m FROM Member m where m.username=:username 
query.setParameter("username", usernameParam);
  • 위치 기준 바인딩
    • 안쓰는 것을 추천한다.
    • ?를 넣어서 만들 수 있다.
SELECT m FROM Member m where m.username=?1 
query.setParameter(1, usernameParam);

 

 

🚩SELECT(프로젝션)

SELECT는 조회하는 SQL 문이다.

프로젝션 대상은 여러가지로 대표적으론

  • 엔티티 프로젝션
    • SELECT m.team FROM Member m 
  • 임베디드 타입 프로젝션
    • SELECT m.address FROM Member m 
  • 스칼라 타입 프로젝션
    • SELECT m.username, m.age FROM Member m 

 

엔티티 프로젝션의 영속성 관리 

엔티티 프로젝션으로 조회를 하면 모두 영속성으로 관리하게 된다. 즉 SET을 이용해서 업데이트하면 쿼리로도 업데이트가 된다는 것이다. 이것에 주의하자!

스칼라 타입 프로젝션 

스칼라 타입을 얻는 방법은 크게 3가지이다.

SELECT m.username, m.age FROM Member m 

  • Query 타입으로 조회
  • Object[] 타입으로 조회 
  • DTO 사용 
    • SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
페이징 API

JPA는 페이징을 다음 두 API로 추상화 된다.

페이징은 특정 페이지 데이터만 가져오는 것을 말한다.

  • SetFirstResult(int startPosition) : 조회 시작 위치
  • 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();

 

조인

JPQL는 조인 쿼리도 지원해준다. 

  • 내부 조인
    • SELECT m FROM Member m [INNER] JOIN m.team t
  • 외부 조인
    • SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
  • 세타 조인 
    • select count(m) from Member m, Team t where m.username = t.name 
    • 세타 조인은 서로 관련 없는 것을 전체 조회해서 비교하는 것을 말한다. 비효율적이다.
      • 이것을 해결하기 위해 특정 버전 이상부터는 jpa에서 ON절을 활용한 외부 조인을 지원한다.
ON 절 
연관관계 없는 엔티티 외부 조인 
• 예) 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
• 예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name

 

서브 쿼리 

일반적으로 서브쿼리는 주 쿼리에서 실행 되기 전에 하는 하위 쿼리를 말한다.

  • 나이가 평균보다 많은 회원
    • 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

단점! 서브쿼리는 성능 이슈가 있다. 복잡한 서브쿼리보다 조인으로 풀어쓰는 것이 좋다.

 

📝JPQL의 타입 표현 

JPQL의 타입은 크게 문자, 숫자, 조건, ENUM, 엔티티 타입이 있다.

 

각각의 타입 표현 법은 다음과 같다.

  • 문자 : 'HELLO' 
  • 숫자 : 10L(long), 10D(Double), 10F(Float)
  • 조건 : TRUE, FALSE
  • ENUM : jpabook.MemberType.Admin(패키지명)
  • 엔티티 타입 : Type(m) =  Member (상속 관계에서 사용)