이 글은 김영한의 [자바 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

👉 기본 환경

- 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
@Entity
public class MemberUsingEm {
 
    @Id @GeneratedValue
    private long id;
 
    private String username;
 
    // Period
    @Embedded
    private Period workPeriod;
 
    // Address
    @Embedded
    private Address homeAddress;
    @Embedded
    private Address workAddress;
 
}
 
 

 

 

🖨️오류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
WARN: HHH000457: Joined inheritance hierarchy [jpa_basic.Item] defined explicit @DiscriminatorColumn.  Legacy Hibernate behavior was to ignore the @DiscriminatorColumn.  However, as part of issue HHH-6911 we now apply the explicit @DiscriminatorColumn.  If you would prefer the legacy behavior, enable the `hibernate.discriminator.ignore_explicit_for_joined` setting (hibernate.discriminator.ignore_explicit_for_joined=true)
Exception in thread "main" javax.persistence.PersistenceException: [PersistenceUnit: hello] Unable to build Hibernate SessionFactory
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:1326)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1252)
    at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:56)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54)
    at jpa_basic.Main.main(Main.java:14)
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: jpa_basic.MemberUsingEm column: city (should be mapped with insert="false" update="false")
    at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:862)
    at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:880)
    at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:876)
    at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:902)
    at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:634)
    at org.hibernate.mapping.RootClass.validate(RootClass.java:267)
    at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:351)
    at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:464)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1249)
    ... 4 more
 
 

 

 

📡 원인

@Embedded 타입의 Address 객체가 homeAddress와 workAddress가 2개 존재

MemberUsingEm에서 이름이 같은 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
@Entity
public class MemberUsingEm {
 
    @Id @GeneratedValue
    private long id;
 
    private String username;
 
    // Period
    @Embedded
    private Period workPeriod;
 
    // Address
    @Embedded
    private Address homeAddress;
    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name="city",
                    column=@Column(name = "WORK_CITY")),
            @AttributeOverride(name="street",
                    column=@Column(name = "WORK_STREET")),
            @AttributeOverride(name="zipcode",
                    column=@Column(name = "WORK_ZIPCODE"))
    })
    private Address workAddress;
 
}
 
 

@AttributeOverrides 사용하여 중복되지 않는 새로운 컬럼 name 부여

 

 

🖨️ 실행 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Hibernate: 
    
    create table MemberUsingEm (
       id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        username varchar(255),
        WORK_CITY varchar(255),
        WORK_STREET varchar(255),
        WORK_ZIPCODE varchar(255),
        endDate timestamp,
        startDate timestamp,
        primary key (id)
    )
 
 

 

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

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

⭐ 영속성 전이: CASCADE

특정 Entity를 영속 상태로 만들 때, 연관된 Entity도 함께 영속상태로 만들고 싶을 때 사용

 

⌨️ 코드

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
36
37
38
39
40
41
@Entity
public class Parent {
 
    @Id @GeneratedValue
    private long id;
 
    private String name;
 
    @OneToMany(mappedBy = "parent")
    private List<Child> children = new ArrayList<>();
 
    public void addChild(Child child){
        children.add(child);
        child.setParent(this);
    }
 
    public long getId() {
        return id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public List<Child> getChildren() {
        return children;
    }
 
    public void setChildren(List<Child> children) {
        this.children = children;
    }
}
 
 

main method

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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
 
            Child child1 = new Child();
            Child child2 = new Child();
 
            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);
 
            em.persist(parent);
 
            em.flush();
            em.clear();
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
 
 
        emf.close();
 
    }
 
}
 
 

 

🖨️발생한 쿼리

1
2
3
4
5
6
7
8
9
Hibernate: 
    /* insert jpa_basic.Parent
        */ insert 
        into
            Parent
            (name, id) 
        values
            (?, ?)
 
 

Child 객체는 저장되지 않고, Parent 객체만 저장됨 

 

⭐CascadeType.All 적용 후,

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
36
37
38
39
40
@Entity
public class Parent {
 
    @Id @GeneratedValue
    private long id;
 
    private String name;
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> children = new ArrayList<>();
 
    public void addChild(Child child){
        children.add(child);
        child.setParent(this);
    }
 
    public long getId() {
        return id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public List<Child> getChildren() {
        return children;
    }
 
    public void setChildren(List<Child> children) {
        this.children = children;
    }
}
 
 

 

🖨️발생한 쿼리

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: 
    /* insert jpa_basic.Parent
        */ insert 
        into
            Parent
            (name, id) 
        values
            (?, ?)
 
Hibernate: 
    /* insert jpa_basic.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)
 
Hibernate: 
    /* insert jpa_basic.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)
 
 

Parent 객체와 함께 Child 객체도 저장

 

- CASCADE 종류(일부)

    - ALL: 모두 적용

    - PERSIST: 영속

 

🚨 부모 객체로 인한 변경 시, 다른 객체에 영향을 미칠 수 있으므로 CASCADE 사용 시, 참조하는 곳이 하나일 때만 사용해야함

 

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

[JPA_Basic] 값 타입 컬렉션  (0) 2023.09.19
[JPA_Basic] 값 타입과 불변 객체  (0) 2023.09.14
[JPA_Basic] N+1 문제  (0) 2023.09.10
[JPA_Basic] Proxy  (0) 2023.09.08
[JPA_Basic] @MappedSuperclass  (0) 2023.09.05

👉 기본 환경

- 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
public class Main {
    public static void main(String[] args) {
 
        // 생략
 
        try {
            Child child1 = new Child();
            Child child2 = new Child();
 
            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);
 
            em.persist(parent);
            em.persist(child1);
            em.persist(child2);
 
            em.flush();
            em.clear();
 
            Parent findParent = em.find(Parent.class, parent.getId());
            em.remove(findParent);
 
            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
ERROR: Referential integrity constraint violation: "FKLH67J1N7X7GT59U0PBKWQH6O6: PUBLIC.CHILD FOREIGN KEY(PARENT_ID) REFERENCES PUBLIC.PARENT(ID) (1)"; SQL statement:
/* delete jpa_basic.Parent */ delete from Parent where id=? [23503-200]
javax.persistence.RollbackException: Error while committing the transaction
    at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:81)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104)
    at jpa_basic.Main.main(Main.java:446)
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute batch
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
    at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:65)
    ... 2 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute batch
    at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:109)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
    at org.hibernate.engine.jdbc.batch.internal.BatchingBatch.performExecution(BatchingBatch.java:129)
    at org.hibernate.engine.jdbc.batch.internal.BatchingBatch.doExecuteBatch(BatchingBatch.java:105)
    at org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl.execute(AbstractBatchImpl.java:148)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.executeBatch(JdbcCoordinatorImpl.java:198)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:633)
    at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
    at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1352)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:443)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3202)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2370)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:447)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
    ... 1 more
Caused by: org.h2.jdbc.JdbcBatchUpdateException: Referential integrity constraint violation: "FKLH67J1N7X7GT59U0PBKWQH6O6: PUBLIC.CHILD FOREIGN KEY(PARENT_ID) REFERENCES PUBLIC.PARENT(ID) (1)"; SQL statement:
/* delete jpa_basic.Parent */ delete from Parent where id=? [23503-200]
    at org.h2.jdbc.JdbcPreparedStatement.executeBatch(JdbcPreparedStatement.java:1235)
    at org.hibernate.engine.jdbc.batch.internal.BatchingBatch.performExecution(BatchingBatch.java:119)
    ... 20 more
 
 

 

 

📡 원인

Parent 객체를 삭제하려고 했으나, 외래키 제약 조건에 의해서 오류 발생

 

 

📰 해결 방법

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
@Entity
public class Parent {
 
    @Id @GeneratedValue
    private long id;
 
    private String name;
 
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> children = new ArrayList<>();
 
    public void addChild(Child child){
        children.add(child);
        child.setParent(this);
    }
 
    public long getId() {
        return id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public List<Child> getChildren() {
        return children;
    }
 
    public void setChildren(List<Child> children) {
        this.children = children;
    }
}
 
 

CascadeType.All을 선언하여, Parent 객체 삭제 시, Child도 함께 삭제

 

 

🖨️ 실행 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Hibernate: 
    /* delete jpa_basic.Child */ delete 
        from
            Child 
        where
            id=?
 
Hibernate: 
    /* delete jpa_basic.Child */ delete 
        from
            Child 
        where
            id=?
 
Hibernate: 
    /* delete jpa_basic.Parent */ delete 
        from
            Parent 
        where
            id=?