스프링 JPA 강의 2회차 Ch 10,11

이진우·2023년 10월 11일
0

스프링 강의 요약

목록 보기
11/13

JPQL 개요

JPA가 지원하는 쿼리 방법은?

1)JPQL:표준 문법으로 거의 대부분 해결
2)JPA Criteria,QueryDSL: 자바 코드로 짜서 JPQL을 빌드 해주는 제너레이터 클래스의 모음
3)Native SQL:표준 SQL 벗어날떄 Native 쿼리 직접 날린다(Oracle의 connectBY같이)
4)JDBC API 직접 사용

JPQL

필요성

테이블이 아닌 엔티티 객체를 대상으로 검색할때 유용하다
모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능하고
필요한 데이터만 DB에서 가져오려면 검색 조건이 포함된 SQL 필요

JPQL 사용예

List<Member> result=em.createQuery("select m from Member m where m.name like '%kim%'".getResultList();

Criteria

동적쿼리가 어려워서 자바 표준이 지원하는 문법이지만 복잡하고 실용성이 없어서 QueryDSL 사용 권장

NativeSQL

사용예

List<Member> resultList=em.createNativeQuery("select ID,AGE FROM MEMBER WHERE NAME='kim'",Member.class).getResultList());

참고

JPQL 날릴 떄 em.persist()한 것을 미리 저장을 해놓는다. JPA와 관련된 것은 가능이다
그러나 DB 커넥션을 획득하고 그것으로 쿼리를 날리면(JPA랑 관련 X) 미리 저장을 하지 않음

JPQL-기본

update문?

JPA는 트랜잭션 내에서 값을 바꾸면 자동으로 update 쳐주지만, 한건 한건만 바꿀 수 있다.
이 경우 한방에 여러개를 update 치기 위해서(예를 들어 전사원 연봉 10프로 증가) update 문을 가진 JPQL을 사용할 수 있다.

TypedQuery VS Query

TypeQuery는 반환타입이 명확할때 사용, Query는 반환 타입이 명확하지 않을 때 사용
TypedQuery는 select m From member m 같은 경우
Query는 select m.username, m.age 같은 경우

파라미터바인딩의 예

List<Member> resultList=em.createQuery("select m from Member m 
where m.username=:username",Member.class).
setParameter("username","leee").getResultList();

결과조회 방법

  • getResultList
  • 결과가 하나 이상일 때, NullpointerException 걱정 안해도 됨
  • getSingleResult
  • 결과가 정확히 하나일때만 사용,나머지 경우에 예외 터진다.

    프로젝션

    프로젝션이란 select 절에 조회할 대상을 지정하는 것을 뜻한다

    연관된 엔티티를 프로젝션할때

    select m.team from Member m 보다는
    select t from Member m join m.team t가 join이 잘 보이기에 좋다

    distinct 중복 제거 예제

    select distinct m.username,m.age from Member m

    여러값을 조회할때

    QueryType,Object[]타입으로 조회가능하지만
    DTO로 조회하는 것이 훨씬 좋다.

    사용예

    List<MemberDTO> result=em.createQuery("select 
    new JPQL.MemberDTO(m.USERNAME,m.age) from Member m,MemberDTO.class)
    .getResultList();

    주의점

    꼭 Dto 클래스에 생성자 쓰기!

    페이징 사용법

    사용예
    List<Member> result=em.createQuery("select m from Member m
     order by m.age desc",Member.class)
    .setParameter(1)//0부터 시작하는 startPosition
    .setMaxResults(20)//조회할 데이터 수
    .getresultList();

    조인

    종류

  • 내부조인: 조인조건을 만족하는 두 테이블의 공통된 값을 기반으로 join을 수행하는 것을 내부 join이라고 하며 멤버는 있고 팀이 없다면 출력하지 않는다
  • 외부 조인: 조인 조건을 만족하지 않는 레코드도 결과에 포함한다. 외부 join은 팀이 없어도 멤버는 조인이 가능하다
  • 세타조인: 연관관계 전혀 없는지 비교할때 사용한다
  • ON절 활용하여

    1)조인대상 필터링
    2)연관관계 없는 엔티티를 외부조인이 가능하다

    서브쿼리

    select m from Member m where m.age>(select avg(m2.age) from Member m2)

    이 경우 m2 를 새로 정의 해서 성능이 잘 나온다. 하지만

    select m from Member m where (
    select count(o) from Order o where m=o.member)>0

    이 경우 m을 긁어오기에 성능 안나올수 있다.

    서브쿼리 지원함수

  • EXISTS
  • 서브쿼리에 결과가 존재하면 참.
    All: 모두 만족시 참
    ANY,SOME:같은 의미
  • IN
  • 서브 쿼리의 결과 중 하나라도 같은 것이 있으면 참

    사용예

    select m from Member m where 
    exists (select t from m.team t where t.name='팀A')
    
    select o from Order o
    where o.orderAmount>ALL(select p.stockAmount from Product p)
    
    select m from Member m
    where m.team=ANY(select t from Team t)

    JPQL타입을 표현할때는

    문자는 ''사이에 숫자는 10L,10D,10F Boolean:TRUE,FALSE로

    사용예

    String query="select m.username,'hello', true FROM Member m";
    List<Object[]> result=...
    Object[0]:유저이름,Object[1]:Hello,Object[2]:true 들어간다

    ENUMTYPE사용예

    where m.type=jpql(클래스).MemberType(클래스명).Admin;

    상속관계에서 엔티티 타입 구분 시 예제

    "select i from Item i where type(i)=Book",Item.class)

    type이 book인 것만 추출한다

    조건식-CASE식

    예제

    select 
        case when m.age <= 10 them '학생요금'
             when m.age >= 60 then '경로요금'
             else '일반요금'
        end
    from Member m
  • COALESE:하나씩 조회해서 NULL이 아니면 반환
  • 예제

    select coalesce(m.username,'이름 없는 회원') from Member m

    username 없으면 이름 없는 회원으로 나온다.

  • NULLIF:두 값이 같으면 NULL반환, 다르면 첫번째 값
  • 예제

    select NULLIF(m.username,'관리자') from Member m

    JPQL함수

    기본함수와 사용자정의함수로 나누어져 있으며
    기본함수에는
    문자 두개 더하는 concat
    문자 자르는 SUBSTRING
    공백 제거하는 TRIM
    대소문자로 전환하는 LOWER,UPPER
    문자길이 찾는 LENGTH
    문자열에서 문자열 위치 찾는 LOCATE
    ABS,SQRT,MOD 같은 수학함수
    콜렉션에서 사이즈 계산할 수 있는 size함수
    등이 있다.
    사용자 정의함수는 교재 참고

    JPQL-경로표현식

    상태필드: 단순히 값 저장 위한 필드(ex.m.username)
    연관필드:1)단일값 연관 필드 2)컬렉션 값 연관 필드
    연관필드들은 묵시적 내부 조인이 발생할 수 있다.
    단일값 연관 경로는 select m.teamname 처럼 탐색이 가능하지만
    컬렉션 값 연관 경로는 묵시적 join으로는 탐색이 불가능 하니(t.members.size가 최선) 명시적 join 으로 해결가능

    참고

    컬렉션 값 연관 경로 가져올떄 List result로 받는다

    결론

    명시적 join쓰자

    페치조인

    페치조인의 필요성

    일단 기본적으로 type을 LAZY 로 하는 이유는 조회할때 한 방에 다른 엔티티까지 쿼리가 나가고 그거에 연관된 엔티티까지 쿼리가 나가면 비효율 적이다.

    그래서 LAZY 를 쓰는데 조회할때 다른 Entity 의 값도 가져오려면 LAZY 타입이니 쿼리가 많이 나가서 fetch JOIN 으로 한 쿼리로 연결 시킨다.

    그러면 EAGER 바꾸면 될 것같지만, EAGER 는 예측할 수 없는 문제도 있고, 다른곳에서 조회할때는 또 다른 엔티티의 값이 필요가 없을 수도 있기 때문에 기본은 LAZY로 해두고 다른 엔티티의 값이 필요한 특수한 경우에만 fetch JOIN 을 한다.


    즉 객체 그래프를 동적으로 명시적으로 가지고 오고 싶을 때 fetch join 을 쓴다

    N+1문제를 fetch join으로 해결가능하다

    1대 다 관계에서 fetch join

    데이터가 뻥튀기 될 수 있다.
    select t from Team t join fetch t.members 

    이러면 데이터베이스 테이블을 따라서 memberID랑 Name만 다른 것을 다른 컬럼으로 본다



    같은 식별자를 가진 Team 엔티티를 제거하려면 distinct를 써주면 된다

    페치조인VS일반조인

    일반 조인은 실행 시에 연관된 엔티티를 함께 퍼올리지 않는다

    페치조인 한계

    1)페치조인 대상 별칭 불가->fetch join 을 여러개 쓸때만 가능
    2)둘이상의 컬렉션 페치 조인 불가->다대다 데이터 뻥튀기
    3)컬렉션을 페치 조인시 페이징 API 사용 불가:DB에서 데이터를 다 끌고 와서 메모리에서 페이징 하기 때문에 장애나기 매우 좋다
    사용을 원한다면?
    @OneToMany위에 @Batchsize(size=100) 혹은 global setting 으로 배치 size를 이용해서 해결

    페치조인보다는 일반조인

    여러 테이블 조인해서 엔티티가 가진 모양이 아닌 다른 결과를 할때 일반 조인을 사용해서 필요한 데이터만 DTO로 반환하는 것이 효과적

    다형성 쿼리

    조회 대상을 특정 자식으로 한정할떄
    select i from Item i where type(i) IN (BOOk,Movie)//Item 중 Book,Movie를 조회해라

    상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 떄

    select i from Item i where treat(i as Book).author='kim'

    엔티티를 직접 사용

    기본키값 외래 키값 모두 JPQL에서 엔티티를 직접 사용하면 SQL에서는 해당 엔티티의 기본 값을 사용

    Named 쿼리

    미리 정의해서 이름을 부여해두고 사용하는 JPQL인데
    정적 쿼리만 가능하기 떄문에
    장점 1) 문법 오류를 잡아줌
    장점 2) SQL로 파싱 후 캐시 하기 때문에 파싱 비용이 별로 없다

    어떻게 사용

    1)어노테이션 2)XML에 정의 3)Spring data JPA 활용

    벌크연산

    PK로 찍어서 update,delete 제외한 나머지 모든 SQL 의 UPDate 문이나 delete 문
    JPA의 한건한건의 변경감지 대신 쿼리 한번으로 update 치기 가능

    예제

    int resultCount=em.createQuery("update Member m st m.age=20").executeUpdate();

    주의점

    영속성 컨텍스트를 무시하고 db에 직접 쿼리 날리므로
    DB에는 update 되어 6000인데 영속성 컨텍스트에는 원래 값이 들어가 있는 경우가 있을 수 있다

    1)벌크 연산을 먼저 실행하거나

    2)벌크 연산 수행 후 영속성 컨텍스트를 초기화한다

    profile
    기록을 통해 실력을 쌓아가자

    0개의 댓글