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

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

Inner 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
public class Main {
    public static void main(String[] args) {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
 
        tx.begin(); // transaction 시작 선언
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m from Member m join m.team t";
 
            List<Member> members = em.createQuery(query, Member.class)
                        .getResultList();
 
            System.out.println("members.size: " + members.size());
            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();
 
    }
}
 
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Hibernate: 
    /* select
        m 
    from
        Member m 
    inner join
        m.team t */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.name as name3_0_,
            member0_.TEAM_ID as team_id4_0_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id
 
 

 

 

Outer 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
public class Main {
    public static void main(String[] args) {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
 
        tx.begin(); // transaction 시작 선언
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m from Member m left join m.team t";
 
            List<Member> members = em.createQuery(query, Member.class)
                        .getResultList();
 
            System.out.println("members.size: " + members.size());
            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();
 
    }
}
 
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Hibernate: 
    /* select
        m 
    from
        Member m 
    left join
        m.team t */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.name as name3_0_,
            member0_.TEAM_ID as team_id4_0_ 
        from
            Member member0_ 
        left outer join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id
 
 

 

 

Theta 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
public class Main {
    public static void main(String[] args) {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
 
        tx.begin(); // transaction 시작 선언
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m from Member m, Team t where m.name=t.name";
 
            List<Member> members = em.createQuery(query, Member.class)
                        .getResultList();
 
            System.out.println("members.size: " + members.size());
            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();
 
    }
}
 
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Hibernate: 
    /* select
        m 
    from
        Member m,
        Team t 
    where
        m.name=t.name */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.name as name3_0_,
            member0_.TEAM_ID as team_id4_0_ 
        from
            Member member0_ cross 
        join
            Team team1_ 
        where
            member0_.name=team1_.name
 
 

 

 

ON 조건절 활용

1. 조인 대상 필터링

⌨️ 코드

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
public class Main {
    public static void main(String[] args) {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
 
        tx.begin(); // transaction 시작 선언
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m from Member m join m.team t on t.name= :name";
 
            List<Member> members = em.createQuery(query, Member.class)
                    .setParameter("name""Team")
                    .getResultList();
 
            System.out.println("members.size: " + members.size());
            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();
 
    }
}
 
 

 

🖨️발생한 쿼리

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
        m.team t 
            on t.name= :name */ select
                member0_.id as id1_0_,
                member0_.age as age2_0_,
                member0_.name as name3_0_,
                member0_.TEAM_ID as team_id4_0_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id 
                and (
                    team1_.name=?
                )
 
 

⭐ 필터링 후 조인 가능

 

2. 연관관계 없는 엔티티 외부 조인

⌨️ 코드

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
public class Main {
    public static void main(String[] args) {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
 
        tx.begin(); // transaction 시작 선언
 
        try {
 
            Team team = new Team();
            team.setName("Team");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member");
            member.setAge(10);
            member.setTeam(team);
            em.persist(member);
 
            em.flush();
            em.clear();
 
            String query = "select m from Member m left join m.team t on t.name=m.name";
 
            List<Member> members = em.createQuery(query, Member.class)
                    .getResultList();
 
            System.out.println("members.size: " + members.size());
            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();
 
    }
}
 
 

 

🖨️발생한 쿼리

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 
    left join
        m.team t 
            on t.name=m.name */ select
                member0_.id as id1_0_,
                member0_.age as age2_0_,
                member0_.name as name3_0_,
                member0_.TEAM_ID as team_id4_0_ 
        from
            Member member0_ 
        left outer join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id 
                and (
                    team1_.name=member0_.name
                )
 
 

⭐ 연관관계 없이도 외부 조인 가능

 

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

[JPA_Basic] 경로 표현식  (0) 2023.09.26
[JPA_Basic] JPQL 타입 표현과 기타식  (1) 2023.09.25
[JPA_Basic] 페이징  (0) 2023.09.23
[JPA_Basic] 프로젝션 - 여러 값 조회  (1) 2023.09.22
[JPA_Basic] 값 타입 컬렉션  (0) 2023.09.19

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

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

페이징

⌨️ 코드

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) {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
 
            for(int i=0; i<100; i++){
                Member member = new Member();
                member.setName("Member" + (i+1));
                member.setAge(i+1);
                em.persist(member);
 
            }
 
            em.flush();
            em.clear();
 
            List<Member> members = em.createQuery("select m from Member m order by m.age desc", Member.class)
                    .setFirstResult(11)
                    .setMaxResults(10)
                    .getResultList();
 
            System.out.println("members.size: " + members.size());
 
            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();
    }
}
 
 

- setFirstResult(): 첫번째 레코드

- setMaxResults(): 페이지 당 레코드 수

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Hibernate: 
    /* select
        m 
    from
        Member m 
    order by
        m.age desc */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.name as name3_0_,
            member0_.TEAM_ID as team_id4_0_ 
        from
            Member member0_ 
        order by
            member0_.age desc limit ? offset ?
 
 

 

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

[JPA_Basic] JPQL 타입 표현과 기타식  (1) 2023.09.25
[JPA_Basic] 조인  (0) 2023.09.23
[JPA_Basic] 프로젝션 - 여러 값 조회  (1) 2023.09.22
[JPA_Basic] 값 타입 컬렉션  (0) 2023.09.19
[JPA_Basic] 값 타입과 불변 객체  (0) 2023.09.14

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

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

Query: SELECT m.name, m.age FROM Member m

 

1. Query 타입 조회

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
 
public class Main {
    public static void main(String[] args) {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member member1 = new Member();
            member1.setName("Member1");
            em.persist(member1);
 
            Member member2 = new Member();
            member2.setName("Member2");
            em.persist(member2);
 
            // Scalar Type Projection
            List list = em.createQuery("select distinct m.name, m.age from Member m")
                    .getResultList();
 
            for(Object o : list){
                Object[] result = (Object[]) o;
                System.out.println("name: " + result[0]);
            }
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
        emf.close();
 
    }
}
 
 

 

 

2. Object[] 타입 조회

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
 
public class Main {
    public static void main(String[] args) {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member member1 = new Member();
            member1.setName("Member1");
            em.persist(member1);
 
            Member member2 = new Member();
            member2.setName("Member2");
            em.persist(member2);
 
            // Scalar Type Projection
            List<Object[]> list = em.createQuery("select distinct m.name, m.age from Member m")
                    .getResultList();
 
            for(Object[] o : list){
                System.out.println("name: " + o[0]);
            }
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
        emf.close();
 
    }
}
 
 

 

 

3. new 명령어 조회

MemberDTO

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
public class MemberDTO {
 
    private String name;
    private int age;
 
    public MemberDTO(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}
 
 

 

Main

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) {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member member1 = new Member();
            member1.setName("Member1");
            em.persist(member1);
 
            Member member2 = new Member();
            member2.setName("Member2");
            em.persist(member2);
 
            // Scalar Type Projection
            List<MemberDTO> resultList = em.createQuery("select new jpql.MemberDTO(m.name, m.age) from Member m", MemberDTO.class)
                    .getResultList();
 
            MemberDTO memberDTO = resultList.get(0);
            System.out.println(memberDTO.getName());
 
            tx.commit(); // transaction 종료 후 commit
        } catch (Exception e) {
            tx.rollback(); // 문제가 생길 경우, rollback 진행
            e.printStackTrace();
        } finally {
            em.close(); // tx에 문제가 생기더라도 em 반드시 종료
        }
 
        emf.close();
 
    }
}
 
 

⭐ 권장

    - 단, 패키지명을 포함한 전체 클래스 명 입력이 필요(가독성 저하)

    - 순서와 타입이 일치하는 생성자 필요

 

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

[JPA_Basic] 조인  (0) 2023.09.23
[JPA_Basic] 페이징  (0) 2023.09.23
[JPA_Basic] 값 타입 컬렉션  (0) 2023.09.19
[JPA_Basic] 값 타입과 불변 객체  (0) 2023.09.14
[JPA_Basic] Cascade  (0) 2023.09.11

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

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

값 타입 컬렉션

    - 값 타입을 하나 이상 저장할 때 사용

    - 데이터베이스는 컬렉션을 같은 테이블로 저장할 수 없으므로, 별도의 테이블 필요

 

 

⌨️ 코드

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
@Entity
public class MemberClctn {
 
    @Id
    @GeneratedValue
    private long id;
 
    private String username;
 
    // Address
    @Embedded
    private Address homeAddress;
 
    @ElementCollection
    @CollectionTable(name = "FAVORITE_FOOD",
            joinColumns = @JoinColumn(name = "id"))
    private Set<String> favoriteFoods = new HashSet<>();
 
    @ElementCollection
    @CollectionTable(name = "ADDRESS",
            joinColumns = @JoinColumn(name = "id"))
    private List<Address> addressesHist = new ArrayList<>();
 
}
 
 

 

🖨️발생한 쿼리

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
Hibernate: 
    create table MemberClctn (
       id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        username varchar(255),
        primary key (id)
    )
 
Hibernate:     
    create table FAVORITE_FOOD (
       id bigint not null,
        favoriteFoods varchar(255)
    )
 
Hibernate:     
    alter table FAVORITE_FOOD 
       add constraint FK9inywg52proq6ctbba5w6i06l 
       foreign key (id) 
       references MemberClctn
 
Hibernate:     
    create table ADDRESS (
       id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255)
    )
 
Hibernate:     
    alter table ADDRESS 
       add constraint FKaxd8wbkc203qq3eky4kjiv7mo 
       foreign key (id) 
       references MemberClctn
 
 

 

 

값 타입 컬렉션 라이프 사이클 = Entity 라이프 사이클

⭐ 값 타입은 별도로 라이프 사이클을 갖지 않음

⌨️ 코드

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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            MemberClctn mClctn = new MemberClctn();
            mClctn.setUsername("user");
            mClctn.setHomeAddress(new Address("homeCity""street""10000"));
 
            mClctn.getFavoriteFoods().add("pizza");
            mClctn.getFavoriteFoods().add("pasta");
            mClctn.getFavoriteFoods().add("risotto");
 
            mClctn.getAddressesHist().add(new Address("old1""street""10000"));
            mClctn.getAddressesHist().add(new Address("old2""street""10000"));
 
            em.persist(mClctn);
 
            tx.commit();
 
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
 
        emf.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
39
40
41
42
43
44
45
46
47
48
49
50
51
Hibernate: 
    /* insert jpa_basic.MemberClctn
        */ insert 
        into
            MemberClctn
            (city, street, zipcode, username, id) 
        values
            (?, ?, ?, ?, ?)
 
Hibernate: 
    /* insert collection
        row jpa_basic.MemberClctn.addressesHist */ insert 
        into
            ADDRESS
            (id, city, street, zipcode) 
        values
            (?, ?, ?, ?)
Hibernate: 
    /* insert collection
        row jpa_basic.MemberClctn.addressesHist */ insert 
        into
            ADDRESS
            (id, city, street, zipcode) 
        values
            (?, ?, ?, ?)
 
Hibernate: 
    /* insert collection
        row jpa_basic.MemberClctn.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (id, favoriteFoods) 
        values
            (?, ?)
Hibernate: 
    /* insert collection
        row jpa_basic.MemberClctn.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (id, favoriteFoods) 
        values
            (?, ?)
Hibernate: 
    /* insert collection
        row jpa_basic.MemberClctn.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (id, favoriteFoods) 
        values
            (?, ?)
 
 

mClctn insert 시, 자동으로 insert

 

 

⭐ 값 타입 컬렉션: 지연 로딩 전략

⌨️ 코드

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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            MemberClctn mClctn = new MemberClctn();
            mClctn.setUsername("user");
            mClctn.setHomeAddress(new Address("homeCity""street""10000"));
 
            mClctn.getFavoriteFoods().add("pizza");
            mClctn.getFavoriteFoods().add("pasta");
            mClctn.getFavoriteFoods().add("risotto");
 
            mClctn.getAddressesHist().add(new Address("old1""street""10000"));
            mClctn.getAddressesHist().add(new Address("old2""street""10000"));
 
            em.persist(mClctn);
 
            em.flush();
            em.clear();
 
            System.out.println("========== START ==========");
            MemberClctn findMClctn = em.find(MemberClctn.class, mClctn.getId());
 
            System.out.println("========== addressesHist ==========");
            List<Address> addressesHist = findMClctn.getAddressesHist();
            for(Address address : addressesHist){
                System.out.println("Address: " + address.getCity());
            }
 
            System.out.println("========== favoriteFoods ==========");
            Set<String> favoriteFoods = findMClctn.getFavoriteFoods();
            for(String favoriteFood : favoriteFoods){
                System.out.println("food: " + favoriteFood);
            }
 
            tx.commit();
 
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
 
        emf.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
39
40
========== START ==========
Hibernate: 
    select
        memberclct0_.id as id1_7_0_,
        memberclct0_.city as city2_7_0_,
        memberclct0_.street as street3_7_0_,
        memberclct0_.zipcode as zipcode4_7_0_,
        memberclct0_.username as username5_7_0_ 
    from
        MemberClctn memberclct0_ 
    where
        memberclct0_.id=?
 
========== addressesHist ==========
Hibernate: 
    select
        addressesh0_.id as id1_0_0_,
        addressesh0_.city as city2_0_0_,
        addressesh0_.street as street3_0_0_,
        addressesh0_.zipcode as zipcode4_0_0_ 
    from
        ADDRESS addressesh0_ 
    where
        addressesh0_.id=?
Address: old1
Address: old2
 
========== favoriteFoods ==========
Hibernate: 
    select
        favoritefo0_.id as id1_4_0_,
        favoritefo0_.favoriteFoods as favorite2_4_0_ 
    from
        FAVORITE_FOOD favoritefo0_ 
    where
        favoritefo0_.id=?
food: risotto
food: pizza
food: pasta
 
 

조회 시 마다 SELECT QUERY 발생

 

 

🚨 값 타입의 일부 수정 시에는 setter가 아닌 새로운 instance 생성

⌨️ 코드

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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            MemberClctn mClctn = new MemberClctn();
            mClctn.setUsername("user");
            mClctn.setHomeAddress(new Address("homeCity""street""10000"));
 
            mClctn.getFavoriteFoods().add("pizza");
            mClctn.getFavoriteFoods().add("pasta");
            mClctn.getFavoriteFoods().add("risotto");
 
            mClctn.getAddressesHist().add(new Address("old1""street""10000"));
            mClctn.getAddressesHist().add(new Address("old2""street""10000"));
 
            em.persist(mClctn);
 
            em.flush();
            em.clear();
 
            System.out.println("========== START ==========");
            MemberClctn findMClctn = em.find(MemberClctn.class, mClctn.getId());
 
            System.out.println("========== From old1 To old2 ==========");
            findMClctn.getAddressesHist().remove(new Address("old1""street""10000"));
            // remove: equals(o, get(i))로 동작하므로 equals가 올바르게 정의되어 있어야 함
            findMClctn.getAddressesHist().add(new Address("new1""street""10000"));
 
            tx.commit();
 
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
 
        emf.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
Hibernate: 
    /* delete collection jpa_basic.MemberClctn.addressesHist */ delete 
        from
            ADDRESS 
        where
            id=?
 
Hibernate: 
    /* insert collection
        row jpa_basic.MemberClctn.addressesHist */ insert 
        into
            ADDRESS
            (id, city, street, zipcode) 
        values
            (?, ?, ?, ?)
 
Hibernate: 
    /* insert collection
        row jpa_basic.MemberClctn.addressesHist */ insert 
        into
            ADDRESS
            (id, city, street, zipcode) 
        values
            (?, ?, ?, ?)
 
 

🚨 Address의 내용이 모두 delete 후, insert가 2번 실행 됨

⭐ 값 타입 컬렉션에 변경사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장

    - 값 변경 시 추적을 위한 id가 없어 찾아서 지우기 쉽지 않음

        - → @OrderColumn을 통해서 순번을 부여해서 update를 수행할 수 있지만 권장 X

1
2
3
4
5
6
7
8
Hibernate: 
    create table ADDRESS (
       id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255)
    )
 
 

 

⭐ 상황에 따라서 값 타입 컬렉션 대신 일대다 관계를 고려

⌨️ 코드

AddressEntity

1
2
3
4
5
6
7
8
9
10
@Entity
public class AddressEntity {
 
    @Id @GeneratedValue
    private Long id;
 
    private Address address;
 
}
 
 

MemberClctn

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
@Entity
public class MemberClctn {
 
    @Id
    @GeneratedValue
    @Column(name = "MCLCTN_NAME")
    private long id;
 
    private String username;
 
    // Address
    @Embedded
    private Address homeAddress;
 
    @ElementCollection
    @CollectionTable(name = "FAVORITE_FOOD",
            joinColumns = @JoinColumn(name = "id"))
    private Set<String> favoriteFoods = new HashSet<>();
 
//    @ElementCollection
//    @CollectionTable(name = "ADDRESS",
//            joinColumns = @JoinColumn(name = "id"))
//    private List<Address> addressesHist = new ArrayList<>();
 
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "MCLCTN_NAME")
    private List<AddressEntity> addressesHist = new ArrayList<>();
 
}
 
 

Main

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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            MemberClctn mClctn = new MemberClctn();
            mClctn.setUsername("user");
            mClctn.setHomeAddress(new Address("homeCity""street""10000"));
 
            mClctn.getFavoriteFoods().add("pizza");
            mClctn.getFavoriteFoods().add("pasta");
            mClctn.getFavoriteFoods().add("risotto");
 
            mClctn.getAddressesHist().add(new AddressEntity("old1""street""10000"));
            mClctn.getAddressesHist().add(new AddressEntity("old2""street""10000"));
 
            em.persist(mClctn);
 
            em.flush();
            em.clear();
 
            System.out.println("========== START ==========");
            MemberClctn findMClctn = em.find(MemberClctn.class, mClctn.getId());
 
            tx.commit();
 
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
 
        emf.close();
 
    }
 
}
 
 

 

🤓 실행 결과, update query 발생 이유

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Hibernate: 
    /* create one-to-many row jpa_basic.MemberClctn.addressesHist */ update
        ADDRESS 
    set
        MCLCTN_NAME=
    where
        id=?
 
Hibernate: 
    /* create one-to-many row jpa_basic.MemberClctn.addressesHist */ update
        ADDRESS 
    set
        MCLCTN_NAME=
    where
        id=?
 
 

@OneToMany와 관련된 엔티티 관계로 설정되어 있으면 JPA는 이러한 관계를 변경 감지 대상으로 인지

해당 컬렉션에 엔티티를 추가하면 JPA는 해당 변경을 데이터베이스에 적용하기 위해 업데이트 쿼리를 생성

= MemberClct 엔티티 인스턴스를 저장한 후에 AddressEntity 인스턴스들이 해당 MemberClct 인스턴스를 참조하도록 갱신하는 작업이 발생

 

 

 

⭐ 식별자가 필요하고, 지속적으로 값을 추적, 변경해야 한다면 값 타입이 아닌 엔티티 사용

 

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

[JPA_Basic] 페이징  (0) 2023.09.23
[JPA_Basic] 프로젝션 - 여러 값 조회  (1) 2023.09.22
[JPA_Basic] 값 타입과 불변 객체  (0) 2023.09.14
[JPA_Basic] Cascade  (0) 2023.09.11
[JPA_Basic] N+1 문제  (0) 2023.09.10

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

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

⌨️ 코드

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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Address address = new Address("city""street""zipcode");
 
            MemberUsingEm memberUsingEm1 = new MemberUsingEm();
            memberUsingEm1.setUsername("hello1");
            memberUsingEm1.setHomeAddress(address);
            em.persist(memberUsingEm1);
 
            MemberUsingEm memberUsingEm2 = new MemberUsingEm();
            memberUsingEm2.setUsername("hello2");
            memberUsingEm2.setHomeAddress(address);
            em.persist(memberUsingEm2);
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
 
        emf.close();
 
    }
 
}
 
 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Hibernate: 
    /* insert jpa_basic.MemberUsingEm
        */ insert 
        into
            MemberUsingEm
            (city, street, zipcode, username, WORK_CITY, WORK_STREET, WORK_ZIPCODE, endDate, startDate, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 
Hibernate: 
    /* insert jpa_basic.MemberUsingEm
        */ insert 
        into
            MemberUsingEm
            (city, street, zipcode, username, WORK_CITY, WORK_STREET, WORK_ZIPCODE, endDate, startDate, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 
 

 

 

🚨 memberUsingEm2의 city를 변경하고자 할 때, setter를 활용하면 side effect 발생

⌨️ 코드

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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Address address = new Address("city""street""zipcode");
 
            MemberUsingEm memberUsingEm1 = new MemberUsingEm();
            memberUsingEm1.setUsername("hello1");
            memberUsingEm1.setHomeAddress(address);
            em.persist(memberUsingEm1);
 
            MemberUsingEm memberUsingEm2 = new MemberUsingEm();
            memberUsingEm2.setUsername("hello2");
            memberUsingEm2.setHomeAddress(address);
            em.persist(memberUsingEm2);
 
           memberUsingEm2.getHomeAddress().setCity("newCity");
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
 
        emf.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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Hibernate: 
    /* insert jpa_basic.MemberUsingEm
        */ insert 
        into
            MemberUsingEm
            (city, street, zipcode, username, WORK_CITY, WORK_STREET, WORK_ZIPCODE, endDate, startDate, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 
Hibernate: 
    /* insert jpa_basic.MemberUsingEm
        */ insert 
        into
            MemberUsingEm
            (city, street, zipcode, username, WORK_CITY, WORK_STREET, WORK_ZIPCODE, endDate, startDate, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 
Hibernate: 
    /* update
        jpa_basic.MemberUsingEm */ update
            MemberUsingEm 
        set
            city=?,
            street=?,
            zipcode=?,
            username=?,
            WORK_CITY=?,
            WORK_STREET=?,
            WORK_ZIPCODE=?,
            endDate=?,
            startDate=
        where
            id=?
 
Hibernate: 
    /* update
        jpa_basic.MemberUsingEm */ update
            MemberUsingEm 
        set
            city=?,
            street=?,
            zipcode=?,
            username=?,
            WORK_CITY=?,
            WORK_STREET=?,
            WORK_ZIPCODE=?,
            endDate=?,
            startDate=
        where
            id=?
 
 

memberUsingEm1의 city도 newCity로 update 발생

 

 

🤓 해결 방안: 새로운 Address 객체 생성

⌨️ 코드

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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Address address = new Address("city""street""zipcode");
 
            MemberUsingEm memberUsingEm1 = new MemberUsingEm();
            memberUsingEm1.setUsername("hello1");
            memberUsingEm1.setHomeAddress(address);
            em.persist(memberUsingEm1);
 
            Address newAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
 
            MemberUsingEm memberUsingEm2 = new MemberUsingEm();
            memberUsingEm2.setUsername("hello2");
            memberUsingEm2.setHomeAddress(newAddress);
            em.persist(memberUsingEm2);
 
            memberUsingEm2.getHomeAddress().setCity("newCity");
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
 
        emf.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
Hibernate: 
    /* insert jpa_basic.MemberUsingEm
        */ insert 
        into
            MemberUsingEm
            (city, street, zipcode, username, WORK_CITY, WORK_STREET, WORK_ZIPCODE, endDate, startDate, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 
Hibernate: 
    /* insert jpa_basic.MemberUsingEm
        */ insert 
        into
            MemberUsingEm
            (city, street, zipcode, username, WORK_CITY, WORK_STREET, WORK_ZIPCODE, endDate, startDate, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 
Hibernate: 
    /* update
        jpa_basic.MemberUsingEm */ update
            MemberUsingEm 
        set
            city=?,
            street=?,
            zipcode=?,
            username=?,
            WORK_CITY=?,
            WORK_STREET=?,
            WORK_ZIPCODE=?,
            endDate=?,
            startDate=
        where
            id=?
 
 

 

 

⭐ 객체 타입을 수정할 수 없게 만들어 부작용을 차단

    ▶ 값 타입을 immutable object로 설계

    = 생성자로만 값을 설정하고, Setter를 만들지 않는 방법

 

* immutable object: 생성 시점 이후, 절대 값을 변경할 수 없는 객체

 

 

😮 값을 변경하고 싶을 경우에는 Setter가 아닌 새로운 Address 객체를 생성해야 함

⌨️ 코드

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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Address address = new Address("city""street""zipcode");
 
            MemberUsingEm memberUsingEm1 = new MemberUsingEm();
            memberUsingEm1.setUsername("hello1");
            memberUsingEm1.setHomeAddress(address);
            em.persist(memberUsingEm1);
 
            Address newAddress = new Address("newCity", address.getStreet(), address.getZipcode());
            memberUsingEm1.setHomeAddress(newAddress);
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
 
        emf.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
Hibernate: 
    /* insert jpa_basic.MemberUsingEm
        */ insert 
        into
            MemberUsingEm
            (city, street, zipcode, username, WORK_CITY, WORK_STREET, WORK_ZIPCODE, endDate, startDate, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 
Hibernate: 
    /* update
        jpa_basic.MemberUsingEm */ update
            MemberUsingEm 
        set
            city=?,
            street=?,
            zipcode=?,
            username=?,
            WORK_CITY=?,
            WORK_STREET=?,
            WORK_ZIPCODE=?,
            endDate=?,
            startDate=
        where
            id=?
 
 

 

 

🤓 em.persist(memberUsingEm1); 이후, 변경된 address를 persist 또는 update하지 않아도 DB에 반영되는 이유

em.persist(memberUsingEm1)를 호출하면 memberUsingEm1 엔티티가 영속성 컨텍스트에 등록

→ 이후 엔티티의 필드 값을 변경하면 JPA는 변경사항을 자동으로 감지

→ 트랜잭션을 커밋할 때, JPA는 영속성 컨텍스트에 있는 엔티티의 변경사항을 DB에 반영

 

* JPA의 더티 체킹(Dirty Checking)

영속성 컨텍스트가 엔티티의 원래 상태와 현재 상태를 비교하여 변경된 필드를 감지하고, 변경사항을 자동으로 DB에 반영

 

 

 

📚 참고 자료

 

[SpringBoot_JPA_1] Dirty Checking & Merge

이 글은 김영한의 [실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발]을 수강하며 정리한 글입니다. 🟦 기본 환경: IDE: IntelliJ, Language: Java 1. Dirty Checking, 변경감지 : Transaction Commit 시, 영속화

hj0216.tistory.com

 

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

[JPA_Basic] 프로젝션 - 여러 값 조회  (1) 2023.09.22
[JPA_Basic] 값 타입 컬렉션  (0) 2023.09.19
[JPA_Basic] Cascade  (0) 2023.09.11
[JPA_Basic] N+1 문제  (0) 2023.09.10
[JPA_Basic] Proxy  (0) 2023.09.08