10. 객체 지향 쿼리 언어 2- 중급 문법

1. 경로 표현식

  • .을 찍어서 객체 그래프를 탐색
  • 상태 필드 >> 단순히 값을 저장하기 위한 필드(m.username)
    • 경로 탐색의 끝, 탐색 x
String query = "select m.username from Member m";
  • 연관 필드 >> 연관관계를 위한 필드
    • 단일 값 연관 필드 >> @ManyToOne, @OneToOne, 대상이 엔티티
    • 묵시적 내부 조인 발생, 탐색 O -> 쿼리 튜닝이 어렵다
String query = "select m.team.name from Member m";

>> team에서 타고 타고 값을 찾을 수 있다.
  • 컬렉션 값 연관 필드 >> @ManyToOne, @OneToOne, 대상이 엔티티
    • 묵시적 내부 조인 발생, 탐색 x
String query = "select t.members from Team t";
  • 묵시적 조인을 사용하지 말고 명시적 조인을 사용할 것

2. 페치 조인

  • 연관된 엔티티나 컬렉션을 sql 한 번에 함께 조회하는 기능
  • 지연로딩보다 페치 조인이 우선
  • select m from Member m join fetch m.team
  • select m., t. from member m inner join team t on m.team_id = t.id
  • 차이점
String query = "select m from Member m";
List<Member> result = em.createQuery(query, Member.class)
        .getResultList();
for (Member member : result) {
    System.out.println("member = " + member.getUsername()+", "+ member.getTeam().getName());
		//회원1, 팀A(SQL)
		//회원2, 팀A(1차 캐시)
		//회원3, 팀B(SQL)
}

>> 위 코드는 getTeam().getName()을 호출할 때 Team 조인 쿼리를 실행
>> 회원 100명을 조회하면 조인 쿼리가 100번까지 나갈 수 있음 -> N+1 문제

String query = "select m from Member m join fetch m.team";

>> 한번의 조인 쿼리로 연관된 데이터를 가져온다.
  • 컬렉션 패치 조인
    • 일대다 쿼리로 인해 데이터가 늘어난다.
      • JPQL의 District -> 같은 식별자를 가진 데이터를 제거해준다.
  • 페치 조인과 일반 조인의 차이
    • 일반 조인 실행 시 연관된 엔티티를 함께 조회하지는 않음
    • 페치 조인은 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
  • 페치 조인의 한계
    • 페치 조인 대상에는 별칭을 줄 수 없다(하이버네이트는 가능하지만 가급적 사용 x)
      - 단 페치 조인 대상에 이중 조인을 걸 떄는 사용
    • 둘 이상의 컬렉션은 페치 조인 불가
    • 컬렉션을 페치 조인하면 페이징 api 사용 불가
      - 하이버 네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험)
      => 해결 방안: 일대 다 관계를 다대일 관계로 뒤집어서 해결
      => 해결 방안: 페치 조인을 과감하게 뺴고 페이징 실행 -> @BatchSize 사용 여러개
      => 해결 방안: DTO를 생성해서 새로운 객체를 뽑아 온다.
@BatchSize(size = 100)
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private List<Member> members = new ArrayList<>();

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

===> 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야하면 페치 조인 보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적인다.

다형성 쿼리

  • 조회 대상을 특정 자식으로 한정
    • SELECT I FROM ITEM I WHERE TYPE(I) IN (BOOK, MOVIE)
  • TREAT
    • 자바의 타입 캐스팅과 유사
    • 상속 구조에서 부모 타입을 특정 자식 타입으로 변환
    • SELECT I FROM ITEM I WHERE TREAT(I AS BOOK).author = 'kim'

4. 엔티티 직접 사용

  • 기본 키 값
    • String query = "select m from Member m where m =:member";
      -> 알아서 식별자를 통해 WHERE 문으로 비교한다.
      -> M.TEAM :TEAM --> 외래키도 알아서 비교 가능하다.

5. Named 쿼리

  • 어노테이션으로 미리 쿼리를 선언하고 이름 부여
  • 정적 쿼리만 가능
  • 어노테이션 xml 정의
  • 애플리케이션 로딩 시점에 초기화 후 재사용
  • 애플리케이션 로딩 시점에 검증
@Entity
@NamedQuery(
        name ="Member.findByUsername",
        query = "select m from Member m where m.username = :username"
)

-----------------------------------------------------------------------

List<Member> memberList = em.createNamedQuery("Member.findByUsername", Member.class)
          .setParameter("username", member1.getUsername())
          .getResultList();
  • 쿼리가 문자열인데도 불구하고 컴파일에러가 발생한다,

6. 벌크 연산

  • 쿼리 한번으로 여러 테이블의 로우 변경
int count = em.createQuery("update Member m set m.age = 20")
                    .executeUpdate();

- 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리 -> 업데이트를 해도 1차 캐시는 수정이 되어있지 않는다.
-> 벌크 연산을 먼저 수행 후 영속성 컨텍스트 초기화 -> em.clear() 후 db 다시 조회

  • createQuery를 실행하므로 자동 플러시 수행

좋은 웹페이지 즐겨찾기