728x90

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

Bulk 연산

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Team teamA = new Team();
            teamA.setName("TeamA");
            em.persist(teamA);
 
            Team teamB = new Team();
            teamB.setName("TeamB");
            em.persist(teamB);
 
 
            Member memberA = new Member();
            memberA.setName("MemberA");
            memberA.setTeam(teamA);
            em.persist(memberA);
 
            Member memberB = new Member();
            memberB.setName("memberB");
            memberB.setTeam(teamA);
            em.persist(memberB);
 
            Member memberC = new Member();
            memberC.setName("memberC");
            memberC.setTeam(teamB);
            em.persist(memberC);
 
            String query = "update Member m set m.age = 20";
            int resultCnt = em.createQuery(query)
                    .executeUpdate();
 
            System.out.println("ResultCnt: " + resultCnt);
 
            Member findMember = em.find(Member.class, memberA.getId());
 
            System.out.println("memberA.getAge(): " + findMember.getAge());
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

- executeUpdate()

    - 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리 전송

        - DB: memberA.getAge()는 20

        - 영속성 Context: memberA.getAge()는 20이 아닌 0으로 조회

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
Hibernate: 
    /* update
        Member m 
    set
        m.age = 20 */ update
            Member 
        set
            age=20
ResultCnt: 3
memberA.getAge(): 0
 
 

 

 

⭐ 벌크 연산 후, 영속성 컨텍스트 초기화

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Team teamA = new Team();
            teamA.setName("TeamA");
            em.persist(teamA);
 
            Team teamB = new Team();
            teamB.setName("TeamB");
            em.persist(teamB);
 
 
            Member memberA = new Member();
            memberA.setName("MemberA");
            memberA.setTeam(teamA);
            em.persist(memberA);
 
            Member memberB = new Member();
            memberB.setName("memberB");
            memberB.setTeam(teamA);
            em.persist(memberB);
 
            Member memberC = new Member();
            memberC.setName("memberC");
            memberC.setTeam(teamB);
            em.persist(memberC);
 
            String query = "update Member m set m.age = 20";
            int resultCnt = em.createQuery(query)
                    .executeUpdate();
 
            System.out.println("ResultCnt: " + resultCnt);
 
            em.clear();
 
            Member findMember = em.find(Member.class, memberA.getId());
 
            System.out.println("memberA.getAge(): " + findMember.getAge());
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Hibernate: 
    /* update
        Member m 
    set
        m.age = 20 */ update
            Member 
        set
            age=20
ResultCnt: 3
Hibernate: 
    select
        member0_.id as id1_0_0_,
        member0_.age as age2_0_0_,
        member0_.name as name3_0_0_,
        member0_.TEAM_ID as team_id5_0_0_,
        member0_.type as type4_0_0_ 
    from
        Member member0_ 
    where
        member0_.id=?
memberA.getAge(): 20
 
 

새로운 select query 발생

 

728x90

'Java > JPA' 카테고리의 다른 글

[JPA_Basic] Fetch Join 2  (0) 2023.09.28
[JPA_Basic] Fetch Join  (0) 2023.09.27
[JPA_Basic] 경로 표현식  (0) 2023.09.26
[JPA_Basic] JPQL 타입 표현과 기타식  (1) 2023.09.25
[JPA_Basic] 조인  (0) 2023.09.23
728x90

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

Fetch Join

     - Fetch Join 대상에는 alias를 줄 수 X

    - Hibernate는 가능하지만, 가급적 사용 X

    - 2 이상의 컬렉션은 Fetch Join X

    - Collection Fetch Join 시, 페이징 API 사용 X

        - 일대일, 다대일 같은 단일 값 연관 필드들은 페이징 가능

        - 🚨 Hibernate는 경고 Log를 남기고 페이징 처리를 하지만, 사용하지 않는 것을 권장

 

Collection과 Fetch Join 대안

1. 일대다 관계 ▶ 다대일 관계로 변경해서 fetch join

 

일대다 관계

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class Main {
    public static void main(String[] args) {
 
        // 
 
        try {
            Team teamA = new Team();
            teamA.setName("TeamA");
            em.persist(teamA);
 
            Team teamB = new Team();
            teamB.setName("TeamB");
            em.persist(teamB);
 
 
            Member memberA = new Member();
            memberA.setName("MemberA");
            memberA.setTeam(teamA);
            em.persist(memberA);
 
            Member memberB = new Member();
            memberB.setName("memberB");
            memberB.setTeam(teamA);
            em.persist(memberB);
 
            Member memberC = new Member();
            memberC.setName("memberC");
            memberC.setTeam(teamB);
            em.persist(memberC);
 
            em.flush();
            em.clear();
 
            String query = "select t from Team t join fetch t.members ";
 
            List<Team> teams = em.createQuery(query, Team.class)
                    .setFirstResult(0)
                    .setMaxResults(1)
                    .getResultList();
 
            for (Team t : teams){
                System.out.println("Team: " + t.getName());
                for(Member m: t.getMembers()){
                    System.out.println(" -> Member: " + m);
                }
            }
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
WARN: HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
Hibernate: 
    /* select
        t 
    from
        Team t 
    join
        fetch t.members  */ select
            team0_.id as id1_3_0_,
            members1_.id as id1_0_1_,
            team0_.name as name2_3_0_,
            members1_.age as age2_0_1_,
            members1_.name as name3_0_1_,
            members1_.TEAM_ID as team_id5_0_1_,
            members1_.type as type4_0_1_,
            members1_.TEAM_ID as team_id5_0_0__,
            members1_.id as id1_0_0__ 
        from
            Team team0_ 
        inner join
            Member members1_ 
                on team0_.id=members1_.TEAM_ID
Team: TeamA
 -> Member: Member{id=3, name='MemberA', age=0}
 -> Member: Member{id=4, name='memberB', age=0}
 
 

 

다대일 관계

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
            Team teamA = new Team();
            teamA.setName("TeamA");
            em.persist(teamA);
 
            Team teamB = new Team();
            teamB.setName("TeamB");
            em.persist(teamB);
 
 
            Member memberA = new Member();
            memberA.setName("MemberA");
            memberA.setTeam(teamA);
            em.persist(memberA);
 
            Member memberB = new Member();
            memberB.setName("memberB");
            memberB.setTeam(teamA);
            em.persist(memberB);
 
            Member memberC = new Member();
            memberC.setName("memberC");
            memberC.setTeam(teamB);
            em.persist(memberC);
 
            em.flush();
            em.clear();
 
            String query = "select m from Member m join fetch m.team t";
 
            List<Member> members = em.createQuery(query, Member.class)
                    .setFirstResult(0)
                    .setMaxResults(1)
                    .getResultList();
 
            for (Member m : members){
                System.out.println("Team: " + m.getTeam().getName());
            }
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Hibernate: 
    /* select
        m 
    from
        Member m 
    join
        fetch m.team t */ select
            member0_.id as id1_0_0_,
            team1_.id as id1_3_1_,
            member0_.age as age2_0_0_,
            member0_.name as name3_0_0_,
            member0_.TEAM_ID as team_id5_0_0_,
            member0_.type as type4_0_0_,
            team1_.name as name2_3_1_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id limit ?
Team: TeamA
 
 

 

 

2. Batch_size 사용

Collection field에 @BatchSize 추가

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
            Team teamA = new Team();
            teamA.setName("TeamA");
            em.persist(teamA);
 
            Team teamB = new Team();
            teamB.setName("TeamB");
            em.persist(teamB);
 
 
            Member memberA = new Member();
            memberA.setName("MemberA");
            memberA.setTeam(teamA);
            em.persist(memberA);
 
            Member memberB = new Member();
            memberB.setName("memberB");
            memberB.setTeam(teamA);
            em.persist(memberB);
 
            Member memberC = new Member();
            memberC.setName("memberC");
            memberC.setTeam(teamB);
            em.persist(memberC);
 
            em.flush();
            em.clear();
 
            String query = "select t from Team t";
 
            List<Team> teams = em.createQuery(query, Team.class)
                    .setFirstResult(0)
                    .setMaxResults(2)
                    .getResultList();
 
            for (Team t : teams){
                System.out.println("Team: " + t.getName());
                for(Member m: t.getMembers()){
                    System.out.println(" -> Member: " + m);
                }
            }
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

cf. Batch size Global Setting

persistence.xml에 "<property name="hibernate.default_batch_fetch_size" value="100"/>" 추가

 

 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Hibernate: 
    /* select
        t 
    from
        Team t */ select
            team0_.id as id1_3_,
            team0_.name as name2_3_ 
        from
            Team team0_ limit ?
Team: TeamA
Hibernate: 
    /* load one-to-many jpql.Team.members */ select
        members0_.TEAM_ID as team_id5_0_1_,
        members0_.id as id1_0_1_,
        members0_.id as id1_0_0_,
        members0_.age as age2_0_0_,
        members0_.name as name3_0_0_,
        members0_.TEAM_ID as team_id5_0_0_,
        members0_.type as type4_0_0_ 
    from
        Member members0_ 
    where
        members0_.TEAM_ID in (
            ?, ?
        )
 -> Member: Member{id=3, name='MemberA', age=0}
 -> Member: Member{id=4, name='memberB', age=0}
Team: TeamB
 -> Member: Member{id=5, name='memberC', age=0}
 
 

Batch Size만큼 in query 발생

 

3. DTO 활용

    - fetch join의 결과 → DTO 변환

    - jpql 작성 시, new DTO 사용

 

 

 

⭐ 실무에서 글로벌 로딩 전략은 모두 지연 로딩으로 하고, 최적화가 필요한 곳에 페치 조인 적용

⭐ 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면,

페치 조인보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적

 

728x90

'Java > JPA' 카테고리의 다른 글

[JPA_Basic] Bulk 연산  (0) 2023.09.29
[JPA_Basic] Fetch Join  (0) 2023.09.27
[JPA_Basic] 경로 표현식  (0) 2023.09.26
[JPA_Basic] JPQL 타입 표현과 기타식  (1) 2023.09.25
[JPA_Basic] 조인  (0) 2023.09.23
728x90

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

Fetch Join

    - JPQL에서 성능 최적화를 위해 제공하는 기능

    - 연관된 Entity나 Collection을 SQL 한 번으로 함께 조회하는 기능

1
2
3
4
5
6
-- JPQL
select m from Member m join fetch m.team;
 
-- SQL
SELECT M.*, T.* FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID=T.ID;
 
 

 

Join

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
            Team teamA = new Team();
            teamA.setName("TeamA");
            em.persist(teamA);
 
            Team teamB = new Team();
            teamB.setName("TeamB");
            em.persist(teamB);
 
 
            Member memberA = new Member();
            memberA.setName("MemberA");
            memberA.setTeam(teamA);
            em.persist(memberA);
 
            Member memberB = new Member();
            memberB.setName("memberB");
            memberB.setTeam(teamA);
            em.persist(memberB);
 
            Member memberC = new Member();
            memberC.setName("memberC");
            memberC.setTeam(teamB);
            em.persist(memberC);
 
            em.flush();
            em.clear();
 
            String query = "select m from Member m ";
 
            List<Member> members = em.createQuery(query, Member.class)
                    .getResultList();
 
            for (Member m : members)
                System.out.println("member: " + m);
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
Hibernate: 
    /* select
        m 
    from
        Member m  */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.name as name3_0_,
            member0_.TEAM_ID as team_id5_0_,
            member0_.type as type4_0_ 
        from
            Member member0_
 
 

@ManyToOne 관계로 fetchType.LAZY 설정

    - Team이 사용되지 않으면 select이 실행되지 않음

일반 조인 실행 시, 연관된 엔티티를 함께 조회하지 않음

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            // 생략
 
            String query = "select m from Member m ";
 
            List<Member> members = em.createQuery(query, Member.class)
                    .getResultList();
 
            for (Member m : members)
                System.out.println("member: " + m + ", member.team" + m.getTeam().getName());
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Hibernate: 
    /* select
        m 
    from
        Member m  */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.name as name3_0_,
            member0_.TEAM_ID as team_id5_0_,
            member0_.type as type4_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: Member{id=3, name='MemberA', age=0}, member.teamTeamA
member: Member{id=4, name='memberB', age=0}, member.teamTeamA
Hibernate: 
    select
        team0_.id as id1_3_0_,
        team0_.name as name2_3_0_ 
    from
        Team team0_ 
    where
        team0_.id=?
member: Member{id=5, name='memberC', age=0}, member.teamTeamB
 
 

🚨 N+1 문제 발생

    - Member 객체에 포함된 Team 개수만큼 select query 발생

        - SQL: MemberA + TeamA

        - 1차 캐시: MemberB + TeamA

        - SQL: MemberC + TeamB

 

 

Fetch Join

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            // 생략
 
            String query = "select m from Member m join fetch m.team ";
 
            List<Member> members = em.createQuery(query, Member.class)
                    .getResultList();
 
            for (Member m : members)
                System.out.println("member: " + m + ", member.team" + m.getTeam().getName());
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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_.name as name3_0_0_,
            member0_.TEAM_ID as team_id5_0_0_,
            member0_.type as type4_0_0_,
            team1_.name as name2_3_1_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id
member: Member{id=3, name='MemberA', age=0}, member.teamTeamA
member: Member{id=4, name='memberB', age=0}, member.teamTeamA
member: Member{id=5, name='memberC', age=0}, member.teamTeamB
 
 

Fetch Join으로 회원과 팀을 함께 조회하여 지연 로딩 X

 

 

Collection Fetch Join

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            // 생략
 
            String query = "select t from Team t join fetch t.members ";
 
            List<Team> teams = em.createQuery(query, Team.class)
                    .getResultList();
 
            for (Team t : teams){
                System.out.println("Team: " + t.getName());
                for(Member m: t.getMembers()){
                    System.out.println(" -> Member: " + m);
                }
            }
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Hibernate: 
    /* select
        t 
    from
        Team t 
    join
        fetch t.members  */ select
            team0_.id as id1_3_0_,
            members1_.id as id1_0_1_,
            team0_.name as name2_3_0_,
            members1_.age as age2_0_1_,
            members1_.name as name3_0_1_,
            members1_.TEAM_ID as team_id5_0_1_,
            members1_.type as type4_0_1_,
            members1_.TEAM_ID as team_id5_0_0__,
            members1_.id as id1_0_0__ 
        from
            Team team0_ 
        inner join
            Member members1_ 
                on team0_.id=members1_.TEAM_ID
Team: TeamA
 -> Member: Member{id=3, name='MemberA', age=0}
 -> Member: Member{id=4, name='memberB', age=0}
Team: TeamA
 -> Member: Member{id=3, name='MemberA', age=0}
 -> Member: Member{id=4, name='memberB', age=0}
Team: TeamB
 -> Member: Member{id=5, name='memberC', age=0}
 
 

🚨 record 수에 맞춰서 Team이 중복되어 출력됨

 

Distinct

    - SQL의 DISTINCT 기능(데이터가 다르면 의도한 DISTINCT가 제대로 동작하지 X)

    - 애플리케이션 엔티티 중복 제거(같은 식별자를 가진 엔티티 제거)

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            // 생략
 
            String query = "select distinct t from Team t join fetch t.members ";
 
            List<Team> teams = em.createQuery(query, Team.class)
                    .getResultList();
 
            for (Team t : teams){
                System.out.println("Team: " + t.getName());
                for(Member m: t.getMembers()){
                    System.out.println(" -> Member: " + m);
                }
            }
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Hibernate: 
    /* select
        distinct t 
    from
        Team t 
    join
        fetch t.members  */ select
            distinct team0_.id as id1_3_0_,
            members1_.id as id1_0_1_,
            team0_.name as name2_3_0_,
            members1_.age as age2_0_1_,
            members1_.name as name3_0_1_,
            members1_.TEAM_ID as team_id5_0_1_,
            members1_.type as type4_0_1_,
            members1_.TEAM_ID as team_id5_0_0__,
            members1_.id as id1_0_0__ 
        from
            Team team0_ 
        inner join
            Member members1_ 
                on team0_.id=members1_.TEAM_ID
Team: TeamA
 -> Member: Member{id=3, name='MemberA', age=0}
 -> Member: Member{id=4, name='memberB', age=0}
Team: TeamB
 -> Member: Member{id=5, name='memberC', age=0}
 
 

컬렉션 내 중복 제거

 

🤓 member1_TEAM_ID 가 select절에 2번 쓰인 이유

    - 하나의 별칭(team_id5_0_0__)은 Team 객체를 구성하는데 사용

    - 다른 하나의 별칭(team_id5_0_1_)은 Member 객체를 구성하는데 사용
    - 같은 필드에 대해 서로 다른 용도로 별칭을 부여함으로써, 하나의 SQL 쿼리 결과로 여러 개의 객체를 올바르게 생성할 수 있음

따라서 실제 데이터베이스에서 members1_.TEAM_ID 필드가 두 번 조회되는 것은 아니며, 하이버네이트가 내부적으로 같은 필드를 서로 다른 용도로 활용하기 위해 별칭을 두 번 부여한 것

 

728x90

'Java > JPA' 카테고리의 다른 글

[JPA_Basic] Bulk 연산  (0) 2023.09.29
[JPA_Basic] Fetch Join 2  (0) 2023.09.28
[JPA_Basic] 경로 표현식  (0) 2023.09.26
[JPA_Basic] JPQL 타입 표현과 기타식  (1) 2023.09.25
[JPA_Basic] 조인  (0) 2023.09.23
728x90

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

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

⭐ 경로 탐색의 끝, 탐색 X

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            member.setType(MemberType.ADMIN);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m.name from Member m";
 
            List<String> members = em.createQuery(query, String.class)
                    .getResultList();
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
Hibernate: 
    /* select
        m.name 
    from
        Member m */ select
            member0_.name as col_0_0_ 
        from
            Member member0_
 
 

 

 

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

    - 단일 값 연관 필드: @ManyToOne, @OneToOne, 대상이 Entity

    ⭐ 묵시적 내부 조인 발생, 탐색 O

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            member.setType(MemberType.ADMIN);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m.team from Member m";
 
            List<Team> members = em.createQuery(query, Team.class)
                    .getResultList();
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
Hibernate: 
    /* select
        m.team 
    from
        Member m */ select
            team1_.id as id1_3_,
            team1_.name as name2_3_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id
 
 

내부 Team Entity를 조회하기 위해 묵시적 Join 발생

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            member.setType(MemberType.ADMIN);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m.team.name from Member m";
 
            List<String> members = em.createQuery(query, String.class)
                    .getResultList();
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
Hibernate: 
    /* select
        m.team.name 
    from
        Member m */ select
            team1_.name as col_0_0_ 
        from
            Member member0_ cross 
        join
            Team team1_ 
        where
            member0_.TEAM_ID=team1_.id
 
 

내부 Entity로부터 재 탐색 가능

 

 

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

    - 컬렉션 값 연관 필드: @OneToMany, @ManyToMany, 대상이 Collection

    ⭐ 묵시적 내부 조인 발생, 탐색 X

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

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            member.setType(MemberType.ADMIN);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select t.members from Team t";
 
            List<Collection> members = em.createQuery(query, Collection.class)
                    .getResultList();
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Hibernate: 
    /* select
        t.members 
    from
        Team t */ select
            members1_.id as id1_0_,
            members1_.age as age2_0_,
            members1_.name as name3_0_,
            members1_.TEAM_ID as team_id5_0_,
            members1_.type as type4_0_ 
        from
            Team team0_ 
        inner join
            Member members1_ 
                on team0_.id=members1_.TEAM_ID
 
 

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            member.setType(MemberType.ADMIN);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select t.members.name from Team t";
 
            List<String> members = em.createQuery(query, String.class)
                    .getResultList();
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️ 발생한 오류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
java.lang.IllegalArgumentException: org.hibernate.QueryException: illegal attempt to dereference collection [team0_.id.members] with element property reference [name] [select t.members.name from jpql.Team t]
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:725)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:816)
    at org.hibernate.internal.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:23)
    at jpql.Main.main(Main.java:139)
Caused by: org.hibernate.QueryException: illegal attempt to dereference collection [team0_.id.members] with element property reference [name] [select t.members.name from jpql.Team t]
    at org.hibernate.QueryException.generateQueryException(QueryException.java:120)
    at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:220)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144)
    at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113)
    at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73)
    at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:155)
    at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:604)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:716)
    ... 3 more
Caused by: org.hibernate.QueryException: illegal attempt to dereference collection [team0_.id.members] with element property reference [name]
    at org.hibernate.hql.internal.ast.tree.DotNode$1.buildIllegalCollectionDereferenceException(DotNode.java:59)
    at org.hibernate.hql.internal.ast.tree.DotNode.checkLhsIsNotCollection(DotNode.java:639)
    at org.hibernate.hql.internal.ast.tree.DotNode.resolve(DotNode.java:245)
    at org.hibernate.hql.internal.ast.tree.FromReferenceNode.resolve(FromReferenceNode.java:114)
    at org.hibernate.hql.internal.ast.tree.FromReferenceNode.resolve(FromReferenceNode.java:109)
    at org.hibernate.hql.internal.ast.tree.FromReferenceNode.resolve(FromReferenceNode.java:104)
    at org.hibernate.hql.internal.ast.tree.DotNode.resolveSelectExpression(DotNode.java:768)
    at org.hibernate.hql.internal.ast.HqlSqlWalker.resolveSelectExpression(HqlSqlWalker.java:1065)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectExpr(HqlSqlBaseWalker.java:2319)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectExprList(HqlSqlBaseWalker.java:2256)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectClause(HqlSqlBaseWalker.java:1518)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:597)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:325)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:273)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:272)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192)
    ... 9 more
 
 

Hibernate는 컬렉션에 대한 직접적인 접근을 허용하지 않음

그러므로, FROM 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            member.setType(MemberType.ADMIN);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m.name from Team t join t.members m ";
 
            List<String> members = em.createQuery(query, String.class)
                    .getResultList();
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Hibernate: 
    /* select
        m.name 
    from
        Team t 
    join
        t.members m  */ select
            members1_.name as col_0_0_ 
        from
            Team team0_ 
        inner join
            Member members1_ 
                on team0_.id=members1_.TEAM_ID
 
 

 

 

⭐⭐⭐ 묵시적 조인을 사용하지말고, 명시적 조인 사용 ⭐⭐⭐

 

728x90

'Java > JPA' 카테고리의 다른 글

[JPA_Basic] Fetch Join 2  (0) 2023.09.28
[JPA_Basic] Fetch Join  (0) 2023.09.27
[JPA_Basic] JPQL 타입 표현과 기타식  (1) 2023.09.25
[JPA_Basic] 조인  (0) 2023.09.23
[JPA_Basic] 페이징  (0) 2023.09.23
728x90

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

ENUM

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            member.setType(MemberType.ADMIN);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m.name from Member m where m.type = jpql.MemberType.ADMIN";
 
            List<String> members = em.createQuery(query, String.class)
                    .getResultList();
 
            System.out.println("members.size: " + members.size());
            for (String m : members)
                System.out.println("name: " + m);
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

ENUM Type은 패키지명을 포함하여 기재

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            member.setType(MemberType.ADMIN);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m.name from Member m where m.type = :userType";
 
            List<String> members = em.createQuery(query, String.class)
                    .setParameter("userType", MemberType.ADMIN)
                    .getResultList();
 
            System.out.println("members.size: " + members.size());
            for (String m : members)
                System.out.println("name: " + m);
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

Parameter 사용

 

⭐ Enum type 사용 시, Default가 Number로 되어있으므로, String으로 지정 필수

 

 

엔티티 타입

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            // 생략
 
            em.createQuery("select i from Item i where type(i) = Book", Item.class)
                    .getResultList();
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Hibernate: 
    /* select
        i 
    from
        Item i 
    where
        type(i) = Book */ select
            item0_.ITEM_ID as item_id2_3_,
            item0_.createBy as createby3_3_,
            item0_.createdDate as createdd4_3_,
            item0_.lastModifiedBy as lastmodi5_3_,
            item0_.lastModifiedDate as lastmodi6_3_,
            item0_.name as name7_3_,
            item0_.price as price8_3_,
            item0_.stockQuantity as stockqua9_3_,
            item0_.author as author10_3_,
            item0_.isbn as isbn11_3_,
            item0_.actor as actor12_3_,
            item0_.director as directo13_3_,
            item0_.artist as artist14_3_,
            item0_.etc as etc15_3_,
            item0_.DTYPE as dtype1_3_ 
        from
            Item item0_ 
        where
            item0_.DTYPE='Book'
 
 

상속 관계에서 type을 활용한 DTYPE 검색

    - @DiscriminatorValue를 활용한 검색어 지정 가능

 

728x90

'Java > JPA' 카테고리의 다른 글

[JPA_Basic] Fetch Join  (0) 2023.09.27
[JPA_Basic] 경로 표현식  (0) 2023.09.26
[JPA_Basic] 조인  (0) 2023.09.23
[JPA_Basic] 페이징  (0) 2023.09.23
[JPA_Basic] 프로젝션 - 여러 값 조회  (1) 2023.09.22