[JAVA] JPA 영속성 컨텍스트(5)

About JPA Relation

Posted by JongMin-Lee on February 11, 2020

관련된 글

  1. [JAVA] JPA(Java Persistence API)란?(1)
  2. [JAVA] JPA 기본설정 및 객체생성(2)
  3. [JAVA] JPA 어노테이션 및 트랜잭션(3)
  4. [JAVA] JPA 연관관계(4)
  5. [JAVA] JPA 영속성 컨텍스트(5)

마지막으로 JPA에서 가장중요하다고도 할 수 있는 Persistence Context(영속성 컨텍스트)에 대해 알아보겠습니다.
JPA를 이해하는데 가장 중요한 용어이며 Entity를 영구 저장하는 환경이라는 뜻입니다. 이 영속성 컨텍스트는 눈에 보이지 않는 논리적인 개념입니다. EntityManager를 통해서 접근이 가능합니다. 이 영속성 컨텍스트에 대해 알아보기 전에 Entity의 생명주기를 먼저 보겠습니다.

1. Entity의 생명주기

1. 비영속(new/transient)

: 영속성 컨텍스트와 전혀 관계가 없는 상태

2. 영속(managed)

: 영속성 컨텍스트에 저장된 상태

3. 준영속(detached)

: 영속성 컨텍스트에 저장되었다가 분리된 형태

4. 삭제(removed)

: 삭제된 상태

간단한 코드와 함께 알아보겠습니다!

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
public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);

    EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jongmin");
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();

    // 트랜잭션 시작
    transaction.begin();
    try{
        Club club = new Club("baskball");
        entityManager.persist(club);

        Friend friend = new Friend("park", "01022221111");
        Member member = new Member("jongmin", 30, friend);
        member.setClub(club);
        entityManager.persist(member);

        entityManager.flush();
        entityManager.clear();

        Member findMember = entityManager.find(Member.class, member.getMember_id());
        Club findClub = findMember.getClub();

        List<Member> members = findClub.getMemberList();
        for(Member member1 : members){
            System.out.println("Member Name : " + member1.getName());
        }

        transaction.commit();
    }catch (Exception e){
        transaction.rollback();
    }finally {
        entityManager.close();
    }
    entityManagerFactory.close();

}

이전에 사용했던 코드 입니다. 처음에 Entity를 생성합니다. Club club = new Club("baskball"); 이 상태는 비영속(new) 상태입니다. 왜냐하면 아직 영속성 컨텍스트에 의해 관리되고 있지 않기 때문이죠. entityManager.persist(club)를 통해서 영속성 컨텍스트에 넣어줍니다.

앞에서 persist()는 물리적으로 데이터베이스에 값을 저장하는 것이 아니라고 한 부분이 이 이유 때문입니다.

그림으로 보게 된다면 좀더 이해가 잘 될 것입니다.

flush()commit()을 아직 하지 않았기 때문에 데이터베이스에 저장된 상태가 아니라 Persistence Context에 저장된 상태인 것 입니다. flush()에 대해 잠시 살펴보겠습니다.

flush()

: 영속성 컨테이너의 변경 내용을 데이터베이스에 반영
flush()발생은 .flush()를 통해 직접 호출하거나 commit()을 하는 경우 또는 JPQL쿼리를 실행하는 경우 flush가 자동 발생됩니다.

flush() 주의사항

  • 영속성 컨텍스트를 비우지 않습니다.
  • 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화 합니다.
  • 트랜잭션이라는 작업단위가 중요합니다.

여러 실습을 통해서 눈치 채신분이 있을 수도 있지만 clear()를 사용했을 때와 사용하지 않았을 때의 차이점이 있습니다. 콘솔을 통해 확인하실 수 있는데 바로 SELECT QUERY입니다. 기존에 clear()를 하지 않고 조회시에 콘솔창에 SELECT문이 날라가지 않았지만 조회된 것을 보실 수 있습니다. 그 이유는 영속성 컨텍스트 내에 있는 데이터를 먼저 조회한 후 데이터베이스를 조회하기 때문입니다. 따라서 clear()를 사용하여 영속성 컨텍스트를 비운 후 조회를 진행하면 SELECT쿼리가 날라가는 것을 콘솔을 통해 확인 가능합니다.

Hibernate: 
    select
        club0_.club_id as club_id1_0_0_,
        club0_.clubName as clubName2_0_0_ 
    from
        Club club0_ 
    where
        club0_.club_id=?

다음 영속성 컨택스트의 특징을 보겠습니다. 바로 변경 감지(Dirty Checking) 입니다.

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
public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);

    EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jongmin");
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();

    // 트랜잭션 시작
    transaction.begin();
    try{
        Club club = new Club("baskball");
        entityManager.persist(club);

        entityManager.flush();
        entityManager.clear();

        Club club2 = entityManager.find(Club.class, club.getClub_id());
        System.out.println("Club Name1 : " + club2.getClubName());

        club2.setClubName("BASEBALL");

        transaction.commit();
    }catch (Exception e){
        transaction.rollback();
    }finally {
        entityManager.close();
    }
    entityManagerFactory.close();

}

처음 clubNamebaskball로 만들어 flush()해 데이터베이스에 값을 넣어 줬습니다. 그 다음 조회를 실행 한 후 엔터티의 clubName값을 BASEBALL로 바꾸기만 한 후 종료했습니다. 아래는 h2-console의 화면입니다.

CLUBNAME의 값이 BASEBALL인 것을 확인 할 수 있습니다. 저는 다른 행동을 취하지 않았지만 영속성 컨텍스트가 변경을 감지하여 데이터베이스와 동기화 시켜준 것 입니다. 콘솔을 확인하면 더욱 확실해집니다.

Club Name1 : baskball
Hibernate: 
    /* update
        com.example.demo.entity.Club */ update
            Club 
        set
            clubName=? 
        where
            club_id=?

Club Name1을 출력한 후 UPDATE 쿼리가 날라가는 것을 보실 수 있습니다.

이러한 1차 캐시 특성과 변경감지 특성들을 활용한다면 더욱 편리하고 빠르게 JPA를 활용할 수 있을거라 믿습니다.
이외에도 쓰기 지연(write-behind) 또한 존재합니다. 쓰기 지연INSERT 등을 수행할 때 쿼리를 모았다가 한번에 수행해줍니다. 기존에 DAO에서는 batch를 사용해서 구현해야 했지만 JPA에서는 쉽게 지원해줍니다.
JPA에서 중요한 것은 Transaction이라고 생각합니다. JPA는 동일한 Transaction에서 조회한 엔터티는 같음을 보장해주기 때문입니다.

사실 Spring Boot에서 Spring Data JPA를 사용한다면 지금까지와의 실습과는 사용이 다릅니다. Spring Data JPA에서는 지금까지 봐왔던 것을 알아서 처리해 주기 때문입니다. 그러나 이렇게 실습한 이유는 조금 더 JPA의 동작에 대해서 이해하기 위해 실습한 것 입니다. 다음에 Spring Data JPA에 대해서도 다뤄보도록 하겠습니다. 부족한 내용이지만 읽어주셔서 감사드립니다!(잘못된 내용에 대해서 말씀해주시면 참고 후 수정하겠습니다..ㅎㅎ)

전체 코드는 Github에서 확인해보실수 있습니다.

참고
  • Baeldung (https://www.baeldung.com/jpa-hibernate-persistence-context)
  • SKplanet Academy (https://www.youtube.com/channel/UCtV98yyffjUORQRGTuLHomw)