JPA

객체 지향 쿼리 언어 - 2

스파이더웹 2022. 9. 17. 01:23
728x90
반응형

경로 표현식

.(점)을 찍어 그래프를 탐색하는 것

분류 

  • 상태 필드
  • 단일 값 연관 필드
  • 컬렉션 값 연관필드

상태 필드 : 단순히 값을 저장하기 위한 필드

ex) m.name

 

연관 필드 : 연관관계를 위한 필드

  • 단일 값 연관 필드
    • @ManyToOne, @OneToOne, 대상이 엔티티(m.team)
  • 컬렉션 값 연관 필드
    • @OneToMany, @ManyToMany, 대상이 컬렉션(m.orders)

 

상태 필드 : 경토 탐색의 끝, 탐색 x

ex) select m.name from Member m

단일 값 연관 경로 : 묵시적 내부조인 발생, 추가 탐색o

ex) select m.team(에서 끝나지않고, team 필드로 추가 탐색가능).name from Member m

컬렉션 값 연관 경로 : 묵시적 내부 조인 발생, 추가 탐색x

                                   FROM 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색 가능

 

묵시적 조인은 사용하지말자!

 

페치조인(제일중요!!!)

연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 기능 

위와 같은 그림에서 일반적으로 조회하는 경우를 살펴보자

try {
            Team teamA = new Team();
            teamA.setName("팀A");
            em.persist(teamA);

            Team teamB = new Team();
            teamB.setName("팀B");
            em.persist(teamB);


            Member member1 = new Member();
            member1.setUsername("member1");
            member1.setTeam(teamA);
            em.persist(member1);

            Member member2 = new Member();
            member2.setUsername("member2");
            member2.setTeam(teamA);
            em.persist(member2);

            Member member3 = new Member();
            member3.setUsername("member3");
            member3.setTeam(teamB);
            em.persist(member3);

            em.flush();
            em.clear();

            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());
            }


            tx.commit();
        }

그림과 같이 member1,2는 팀 a소속이고, member3는 팀 b소속이다 이러한 경우 지연로딩을 설정하였기에, member만 가져오고 for문안에 Team은 프록시로 있다가 getTeam을 하면 team을 DB에서 가져오는 쿼리가 실행이 될 것이다

Hibernate: 
    /* select
        m 
    from
        Member m */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.TEAM_ID as team_id4_0_,
            member0_.username as username3_0_ 
        from
            Member member0_
Hibernate: 
    select
        team0_.id as id1_3_0_,
        team0_.name as name2_3_0_ 
    from
        Team team0_ 
    where
        team0_.id=?
member.getUsername()+", " +member.getTeam().getName() = member1, 팀A
member.getUsername()+", " +member.getTeam().getName() = member2, 팀A
Hibernate: 
    select
        team0_.id as id1_3_0_,
        team0_.name as name2_3_0_ 
    from
        Team team0_ 
    where
        team0_.id=?
member.getUsername()+", " +member.getTeam().getName() = member3, 팀B

해당 내용이 쿼리가 실행된 내용인데, 처음에 Member만 DB에서 가져오고 이후, Team이 사용되는 시점에 가져오게된다. 여기서 보면 팀A를 호출할때 영속성컨텍스트에 값이 없으므로 SQL로 값을 가져오고, member2의 경우는 팀A가 이제 1차캐시에 남아있으므로 따로 SQL을 실행하지않고, 팀B는 영속성컨텍스트에 없으므로 또 호출되는것을 알 수 있다

 

이러한 경우 N+1의 문제가 발생이되는데, 이러한 문제는 fetch join을 통해 해결할 수 있다.

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

Hibernate: 
    /* select
        m 
    from
        Member m 
    join
        fetch m.team */ select
            member0_.id as id1_0_0_,
            team1_.id as id1_3_1_,
            member0_.age as age2_0_0_,
            member0_.TEAM_ID as team_id4_0_0_,
            member0_.username as username3_0_0_,
            team1_.name as name2_3_1_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id
member = member1, 팀A
member = member2, 팀A
member = member3, 팀B

페치 조인을 사용한 경우 팀까지 db에서 값을 가져오게 된다 

 

페치 조인과 즉시로딩 알아야할 점

https://www.inflearn.com/questions/39516

 

fetch 조인, 엔티티 그래프 질문입니다. - 인프런 | 질문 & 답변

안녕하세요. 강의를 들으며 조금 불분명한 부분이 있어서 질문 남깁니다. 가령 멤버들의 팀을 조회하는 쿼리를 작성한다면 우선 팀 전체를 조회하는 쿼리가 실행되고 각 팀이 자신들의 멤버를

www.inflearn.com

 

컬렉션 페치 조인

일대다 관계, 컬렉션 페치 조인

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

            for (Team team : result) {
                System.out.println("team = " + team.getName()+ " | members = " + team.getMembers().size() );
            }

team = 팀A | members = 2
team = 팀A | members = 2
team = 팀B | members = 1

Team을 기준으로 조회했는데, 3번 출력이 된것을 확인할 수 있다. DB입장에서 1:다 인 경우 더해서 조회가 되는데 이러한 경우 중복되는 데이터를 제거하려면 distinct를 추가하면 같은 식별자를 가진 Team 엔티티를 제거한다

 

페치조인의  특징과 한계

  • 페치 조인 대상에는 별칭을 줄 수 없다(ex_ select t from Team t join fetch t.members as m
  • 둘 이상의 컬렉션은 페치 조인 할 수 없다
  • 컬렉션을 페치조인하면 페이징 api를 사용할 수 없다 

 

Named 쿼리 - 정적쿼리

  • 미리 정의해서 이름을 부여해두고 사용하는 JPQL
  • 정적 쿼리
  • 어노테이션, XML의 정의
  • 애플리케이션 로딩 시점에 초기화 후 재사용
    • - 즉 로딩시점에 JPA나 하이버네이트가 실행해서 캐시로 값을 가지고있다. 그러므로 비용소모를 줄일 수 있다
  • 애플리케이션 로딩 시점에 쿼리를 검증

사용방법

@NamedQuery(
        name = "Member.findByUsername",
        query = "select m from Member m where m.username =:username"

)
public class Member 

 

클래스 선언부위에 사용할 이름과 쿼리 내용을 적어주고 아래와같이 사용해주면된다

List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
        .setParameter("username", "회원1")
        .getResultList();

 

728x90
반응형