Spring/[인프런 김영한 자바 ORM 표준 JPA 프로그래밍 - 기본편]

[인프런 김영한 자바 ORM 표준 JPA 프로그래밍 - 기본편] 객체지향 쿼리 언어 - 기본 문법

h2boom 2024. 8. 28. 00:22

객체지향 쿼리 언어

  • JPA에서 지원하는 다양한 쿼리 방법
    • JPQL
    • JPA Criteria
    • QueryDSL
    • 네이티브 SQL
    • JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용

JPQL (Java Persistence Query Language)

// JPQL 사용 예시
List<Member> result = em.createQuery(
	"select m from Member m where m.name like '%kim%'"
	, Member.class).getResultList();

for (Member member : result) {
	System.out.println("member = " + member);
}
  • JPA를 사용하면 엔티티 객체를 중심으로 개발하고 검색한다.
    • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능하다.
    • 필요한 데이터만 DB에서 불러오려면 검색 조건이 포함된 SQL이 필요하다.

 

  • JPQL : SQL을 추상화한 객체지향 쿼리 언어 (= 객체지향 SQL)

 

  • SQL은 데이터베이스 테이블을 대상으로 쿼리, JPQL은 엔티티 객체를 대상으로 쿼리
    • JPQL은 특정 데이터베이스 SQL에 의존하지 않는다.

 

  • JPQL은 단순 문자열이기에 동적 쿼리를 작성하기에 어려움이 있다.

 

  • JPQL 문법
    • select 문 => select 절 from 절 [where 절] [groupby 절] [having 절] [orderby 절]
    • update 문 => update 절 [where 절]
    • delete 문 => delete 절 [where 절]
    • 엔티티와 속성은 대소문자를 구분하고 JPQL 키워드는 대소문자를 구분하지 않는다.
    • 테이블 이름이 아닌 엔티티 이름을 사용한다.
    • 별칭은 필수 (as 생략 가능) ex) select m from Member m
    • 대부분 SQL과 문법이 동일하다.

 

  • TypeQuery : 반환 타입이 명확할 때 사용
    • ex) em.createQuery(select m from Member m, Member.class);
      반환 타입이 Member로 명확하다.
  • Query : 반환 타입이 명확하지 않을 때 사용
    • ex) em.createQuery(select m.username, m.id from Member m);
      반환 타입이 String과 Long이기에 명확하지 않다.

 

  • 결과 조회 메소드
    • query.getResultList() : 결과가 하나 이상일 때 리스트 반환
      • 결과가 없는 경우 빈 리스트를 반환한다.
    • query.getSingleResult() : 결과가 정확히 하나일 때 단일 객체 반환
      • 결과가 없으면 NoResultException 예외 발생
      • 결과가 둘 이상이면 NonUniqueResultException 예외 발생

 

// 파라미터 바인딩 예시
// 이름 기준
em.createQuery("select m from Member m where m.username = :username", Member.class)
	.setParameter("username",usernameParam);
	.getSingleResult();
    
// 위치 기준
em.createQuery("select m from Member m where m.username = ?1", Member.class)
	.setParameter(1,usernameParam);
	.getSingleResult();
  • 파라미터 바인딩
    • 이름 기준
    • 위치 기준
  • 위치 기준 파라미터 바인딩은 위치가 바뀌는 경우 장애로 이어지기에 사용하지 않는 것이 좋다.

 

//Query 타입으로 조회
List resultList = em.createQuery("select m.username, m.age from Member m")
                    .getResultList();

Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("result[0] = " + result[0]);

//Object[] 타입으로 조회
List<Object[]> resultList = em.createQuery("select m.username,m.age from Member m")
                    .getResultList();

Object[] result = resultList.get(0);
System.out.println("result[0] = " + result[0]);


//DTO로 조회
List<MemberDTO> result = em.createQuery("select new org.example.jpql.MemberDTO(m.username,m.age) 
	from Member m ", MemberDTO.class).getResultList();

MemberDTO memberDTO = result.get(0);
System.out.println("memberDTO = " + memberDTO.getUsername());
  • 프로젝션 : SELECT 절에 조회할 대상을 지정하는 것
    • 프로젝션 대상 - 엔티티, 임베디드 타입, 스칼라 타입 (숫자, 문자등 기본 데이터 타입)
    • 여러 값 조회 방법 ex) SELECT m.username, m.age FROM Member m
      • Query 타입으로 조회
      • Object[] 타입으로 조회
      • new 명령어로 조회 -> 단순 값을 DTO로 바로 조회
        • 엔티티로 등록된 클래스가 아닌 경우 select 다음 패키지명을 포함한 전체 클래스명을 입력해줘야 한다.
        • 순서와 타입이 일치하는 생성자가 필요하다.

 

List<Member> result = em.createQuery("select m from Member m order by m.age desc ", Member.class)
                    .setFirstResult(0)
                    .setMaxResults(10)
                    .getResultList();
  • 페이징 : 데이터베이스에서 데이터를 읽어와서 화면에 출력할 때 한 번에 모든 데이터를 가져오기보다는 출력될 페이지의 데이터만 나눠서 가져오는 것을 의미한다.
  • 페이징 API
    • setFirstResult(int startPosition) : 조회 시작 위치
    • setMaxResults(int maxResult) : 조회할 데이터 수

 

  • 조인
    • 내부 조인
      • ex) select m from Member m [INNER] JOIN m.team t
    • 외부 조인
      • ex) select m from Member m LEFT [OUTER] JOIN m.team t
    • 세타 조인
      • ex) select count(m) from Member m, Team t where m.username = t.name

 

  • 조인 - ON절 활용
    • 조인 대상 필터링
      • ex) 회원 - 팀 조인 (팀 이름이 A인 팀만 조인)
        JPQL => SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
        SQL => SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID = t.id  and t.name = 'A'
    • 연관관계 없는 엔티티 외부 조인 (이전에는 내부 조인만 가능)
      • ex) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
        JPQL => SELECT m, t FROM Member m LEFT JOIN m.username = t.name

        SQL => SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name

 

  • 서브 쿼리
    • ex) 나이가 평균보다 많은 회원
      select m form Member m where m.age > (select avg(m2.age) from Member m2)
    • JPA에서는 SELECT, WHERE, HAVING 절에서만 서브 쿼리 사용 가능
      • FROM 절의 서브 쿼리는 불가능하다.
  • 서브 쿼리 지원 함수
    • [NOT] EXISTS (서브쿼리) : 서브 쿼리에 결과가 존재하면 참
      • {ALL / ANY / SOME} (서브쿼리) 로 사용할 수 있으며 조건과 비교해서 참인지 아닌지 결정
      • ALL : 모두 만족하면 참
      • ANY, SOME : 조건을 하나라도 만족하면 참
    • [NOT] IN (서브쿼리): 서브 쿼리의 결과 중 하나라도 같은 것이 있으면 참

 

  • JPQL 타입 표현
    • 문자 - 'HELLO', 'She''s'
    • 숫자 - 10L, 10D, 10F
    • Boolean - FALSE, TRUE
    • ENUM - 패키지를 포함한 전체 클래스명과 ENUM 변수를 적어줘야한다. ex) com.jpa.MemberType.ADMIN
    • 엔티티 타입 - ex) select i from Item i where type(i) = Book

 

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

//단순 CASE 식
select
	case when '팀A' then '인센티브110%'
		when '팀B' then '인센티브120%'
		else '인센티브105%'
	end
from Team t

//COALESCE => 사용자 이름이 없으면 이름없는 회원을 반환
select coalesce(m.username, '이름 없는 회원') from Member m

//NULLIF => 사용자 이름이 관리자면 null을 반환하고 나머지는 본인 이름을 반환
select NULLIF(m.username, '관리자') from Member m
  • 조건식 - CASE 식
    • 기본 CASE 식
    • 단순 CASE 식
  • COALESCE : 하나씩 조회해서 null이 아니면 반환
  • NULLIF : 두 값이 같으면 null 반환, 다르면 첫 번째 값 반환

 

  • JPQL 기본 함수
    • CONCAT
    • SUBSTRING
    • TRIM
    • LOWER, UPPER
    • LENGTH
    • LOCATE
    • ABS, SQRT, MOD
    • SIZE, INDEX

JPA Criteria

//Criteria 사용 예시
CriteriaBuilder cb = em.getCriteriaBuilder(); //Criteria 사용 준비
CriteriaQuery<Member> query = cb.createQuery(Member.class);

Root<Member> m = query.from(Member.class);

CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("name"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
  • JPA Criteria 특징
    • 자바 코드로 JPQL을 작성할 수 있다.
      • 오타가 생겨도 컴파일 오류 발생
    • 동적 쿼리를 작성할 때 편하다
    • SQL 같지 않기에 표준 스펙임에도 잘 사용하지 않는다.
      • 코드가 복잡하고 유지보수가 어렵다.

 

  • Criteria 대신 QueryDSL 사용 권장

QueryDSL

// QueryDSL 사용 예시
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
queryFactory
	.select(order)
	.from(order)
	.join(member, order.memeber)
	.where(...)
	.fetch();
  • QueryDSL 특징
    • 문자가 아닌 자바코드로 JPQL 작성 가능
    • 컴파일 시점에 문법적인 오류를 찾을 수 있다.
    • 동적 쿼리를 작성하기에 편리하다.
    • 단순하고 쉽기에 실무 사용 권장

네이티브 SQL

//네이티브 SQL
em.createNativeQuery("select MEMBER_ID, city, street, zipcode, USERNAME from MEMBER")
	.getResultList();
  • 네이티브 SQL : JPA가 제공하는 SQL을 직접 사용하는 기능
    • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능을 사용하기 위해서 사용한다.
      ex) 오라클 CONNECT BY ...

JDBC, SpringJdbcTemplate

  • JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나 SpringJdbcTemplate, MyBatis등을 함께 사용 가능하다.
    • 이 경우 영속성 컨텍스트를 적절한 시점에 강제로 플러시가 필요하다.
      • commit, query 시점에 flush가 되기에 다른 방법에서는 query를 날리는 시점에 flush가 된다.
        JDBC는 JPA와 관련이 없기 때문에 flush를 날리지 않기에 직접 flush를 해줘야한다.

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

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런

김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도

www.inflearn.com