jpql_2

김강현·2023년 4월 2일
0

ORM-JPA

목록 보기
9/9

JPQL 타입표현

종류

문자 : 'HELLO', 'She''s'
숫자 : 10L, 10D, 10F
Boolean : TRUE, FALSE (소문자도 됨)
ENUM : ~~.ADMIN (패키지명 필요)
엔티티 타입 : TYPE(m) = Member (상속 관계에서)

이런식으로 JPQL 에서 쓰임

String query = "select m.username, 'HELLO', TRUE from Member m where m.type = org.example.MemberType.ADMIN";

JPQL 내에서 ENUM 접근하면 길어지니깐 이런식으로 파라미터 바인딩 하는 것을 추천

String query = "select m.username, 'HELLO', TRUE from Member m where m.type = :userType";

List<Object[]> result = em.createQuery(query)
    .setParameter("userType", MemberType.ADMIN)
    .getResultList();

특정 상속관계에 속해있는 것만 들고오고 싶을때

em.createQuery("select i from Item i where type(i) = Book", Item.class).getResultList();

조건식

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();
// result <= [ '학생요금' ]

coalesce

  • 하나씩 조회해서 null이 아니면 반환
member1.setName("changer");
member2.setName(null);
...
String query = "select coalesce(m.username, '이름 없는 회원') from Member m";
List<String> result = em.createQuery(query, String.class).getResultList();

// result -> ["changer", "이름 없는 회원"]

nullif

  • 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
member1.setName("changer");
member2.setName("admin");
...
String query = "select coalesce(m.username, 'admin') from Member m";
List<String> result = em.createQuery(query, String.class).getResultList();

// result -> ["changer", null]

JQPL 기본 함수

  • concat
  • substring
  • trim
  • lower, upper
  • length
  • locate
  • abs, sqrt, mod
  • size, index

이외에도 여러가지 사용자 정의 함수로 추가 가능
(하이버네이트에서 많이 추가 해놓은 것도 있음. 다만 이것은 DB에 종속되어 있는 함수들임)

사용자 정의 함수

  • 따로 H2Dialog (DB) 를 상속하는 MyDialog 생성
  • persistence.xml 파일 수정
  • 사용자 정의 함수 추가
  • 자세한 방법은 생략

중급 문법

경로 표현식

  • 점을 찍어 객체 그래프를 탐색하는 것
  • 상태 필드
    단순히 값을 저장하기 위한 필드
  • 연관 필드
    연관관계를 위한 필드
    [엔티티] 단일 값 연관 필드 : @ManyToOne, @OneToOne
    [컬렉션] 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany

상태필드 : 경로 탐색의 끝, 탐색 x
단일 값 연관 경로 : 묵시적 내부 조인(inner join) 발생, 탐색 o
컬렉션 값 연관 경로 : 묵시적 내부 조인 발생, 탐색 x

컬렉션 값에서 탐색을 하고 싶다면?

String query = "select t.members from Team t";
List<Collection> result = em.createQuery(query, Collection.class).getResultList();

--> 명시적 조인을 활용하여 명칭을 얻은 뒤에 탐색
String query = "select m.username from Team t join t.members m";
List<String> result = em.creatQuery(query, String.class).getResultList();

(김영한 개발자님) 쿼리 튜닝이 너무 어려워짐. 묵시적으로 조인이 되다는 것... 절대로 묵시적 조인 쓰지 말것!!
sql 문과 최대한 비슷하게 짜려고 함.

명시적 조인, 묵시적 조인

  • 명시적 조인 : join 키워드 직접 사용
  • 묵시적 조인 : 경로 표현식에 의해 묵시적으로 SQL 조인 발생

(실무 조언) 항상 명시적 조인을 사용하라!!

페치 조인 (fetch join)

  • sql 조인 종류 x
  • JPQL 에서 성능 최적화를 위해 제공하는 기능
  • 연관된 것들을 SQL 한 번에 함께 조회하는 기능
// lazy 옵션 -> 프록시 객체로 불러옴
String query = "select m from Member m join m.team";

// lazy 옵션이 있어도 -> 원 객체로 불러옴
String query = "select m from Member m join fetch m.team";

lazy 기능이 필요할때도 있으나,
실제로 많은 member 들이 team 정보를 쓰게 되면,
사실상 fetch 조인을 써서 한번에 둘다 받아오는게 성능상 유리!

@OneToMany 조회할때. 컬렉션 조회의 경우!

String query = "select t From Team t join t.members";
List<Team> result = em.createQuery(query, Team.class).getResultList();

이 경우 members는 프록시는 아니지만, 즉각적으로 포함되어 있는 멤버 데이터를 불러오지 않음.

// fetch를 활용하여 바로 불러오기!!
String query = "select t From Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class).getResultList();

만약 한 팀에 여러명의 멤버가 있다면, RDBS 에서는 중복 그대로 가져온다.
영속성 컨텍스트에서는 이를 그대로 받아오기는 하지만, 겹치는 부분에 대해서는 같은 주소값을 사용하여 데이터 중복을 방지함!

return 값은 중복 그대로 들어옴!
(join 하면서 데이터가 오히려 뻥튀기 되는..!)

중복 안되게 받아오는 법은? DISTINCT

  • SQL 에 DISTINCT 를 추가
    sql의 경우 완전이 같아야, 중복이 되는데 join 상태로는 완전히 같은 경우가 없음.
  • 그래서 어플리케이션에서 엔티티 중복 제거
String query = "select distinct t From Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class).getResultList();

페치 조인 한계

  • 페치 조인 대상에는 별칭을 줄 수 없다. (가능한 구현체도 있지만, 가급적 사용 x)
  • 둘 이상의 컬렉션은 페치 조인 할 수 없다.
  • 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
    (데이터 뻥튀기로 인해 사이징에서 문제 발생할 수 있음.
    필요하면 별도의 쿼리를 만들어서 페이징 하는게 맞음)

컬렉션일때 페이징처럼 쓰고 싶으면?

<Team.java>
@Batchsize 를 활용하여 member를 n개씩 batch 로딩 한다.
(default 는 lazy 로딩 <- persistenceBag인가 뭔가)

@Entity
public class Team {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;

    @BatchSize(size = 100)
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

실무에서는 그냥 글로벌 세팅으로 넘겨버림.
<persistence.xml>

<property name="hibernate.default_batch_fetch_size" value="100">

페치 조인의 특징과 한계

  • 연관된 엔티티들을 SQL 한 번으로 조회 ==> 성능 최적화
  • 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선!
    (@OneToMany lazy 같은 것들)
  • 실무에서 글로벌 로딩 전략은 모두 지연 로딩
  • 최적화가 필요한 곳에만 페치 조인 적용!

성능을 이끌어 낼 수 있는 가장 주요한 파트 중에 하나이니 꼭 마스터 할 것!!!

다형성 쿼리

  • Type
select i from Item i
where type(i) IN (Book, Movie)
  • Treat
select i from Item i
where treat(i as Book).auther = 'kim'

엔티티 직접 사용

  • 기본키 값을 사용함. (PK)
Member m = new Member();
...
// 둘다 동일한 action
select count(m.id) from Member m
select count(m) from Member m

파라미터 바인딩으로도 넘길 수 있고 -> primary key 값으로 인식

  • 자동으로 외래키 인식 (FK)
Team teamA = new Team();
...
// 둘다 동일한 action
String query1 = "select m from Member m where m.team = :team";
em.createQuery(query1, Team.class)
    .setParameter("team", teamA)
    .getResultList();

String query2 = "select m from Member m where m.team.id = :teamId";
em.createQuery(query2, Team.class)
    .setParameter("teamId", teamA.id)
    .getResultList();
    
    

Named 쿼리

  • 쿼리를 불러와서 사용할 수 있는 것
  • 정적 쿼리
  • 어노테이션, xml 에 정의
  • 어플리케이션 로딩 시점에 초기화 후 재사용!!
    (계속 재활용, jpql -> sql 코스트가 한번만 일어남)
  • 어플리케이션 로딩 시점에 쿼리 검증!!

<Member.java>

@Entity
@NamedQuery(
        name="Member.findByUsername",
        query = "select m from Member m where m.username = :username"
)
public class Member {...}
em.createNamedQuery("Member.findByUsername", Member.class)
    .setParameter("username","changer")
    .getResultList();

Entity 어노테이션이던, xml 이던 실무에서는 Spring data JPA 에서 따로 빼내어 만들게 될 것이니 신경 안써도 됨!

  • Entity, xml이 너무 지저분해지는 단점

벌크 연산

  • 페치 조인 처럼, 한방에 여러 업데이트 쿼리들을 날릴 수는 없을까
// 기존
tx.commit(); // commit 시점에 각각의 Member Entity의 age를 20으로 바꾸는 쿼리를 n번 실행

// 한꺼번에 (영속성 컨텍스트 무시)
em.createQuery("update Member m set m.age = 20").executeUpdate();
  • 벌크 연산 수행과 영속성 컨텍스트가 겹친다면, 수행 이후에 영속성 컨텍스트 초기화 필수! em.clear() -> 추후 spring data jpa 에서 @Modify로 지원
  • 이전 처리는 신경안써도 됨. JPQL query가 날아갈때 기본적으로 flush 호출함
profile
this too shall pass

0개의 댓글