본문 바로가기

Backend/Spring

[Spring/JPA] 영속성 컨텍스트 이해하기

안녕하세요.

 

이번 포스팅에서는 Spring Data JPA의 영속성 컨텍스트에 대해 이해하는 시간을 가져보도록 하겠습니다.

 

영속성 컨텍스트란?

영속성(persistency)이란 데이터를 영구적으로 저장하는 것을 의미합니다.

따라서 영속성 컨텍스트란 직역하자면 데이터를 영구적으로 저장하는 환경 정도로 해석될 수 있습니다. 

 

기본적으로 JPA는 객체지향 언어인 JAVA와 Database 사이의 패러다임 불일치를 해결하기 위해서 도입된 규약입니다.

해당 내용을 기억하면서 본 포스팅을 이해하시면 좋을 것 같습니다.

 

 

만약에 저희가 Spring framework를 사용하여 웹 개발을 진행하고 있다고 가정해보겠습니다.

 

 

저희는 database에서 특정 id값을 갖는 데이터를 수정해달라는 요청을 받으면, database에 update 쿼리를 보내 수정을 진행하고 싶습니다.

 

하지만 위 그림처럼 진행하게 되면 크게 두 가지의 문제점이 있습니다.

 

1. 객체로 되어있는 Java의 코드를 바로 Database에 적용할 수 없습니다.

2. Database에 직접적으로 Update를 진행하게 되면 수정 중 오류가 발생하였을 때 데이터베이스의 정보가 원하는 방향으로 모두 수정이 이루어지지 않거나, Database의 정보들이 손상될 수 있습니다.

 

1번 문제를 해결하기 위해 저희는 JPA라는 규약을 사용합니다.

 

 

이제 2번 문제를 해결하기 위해 JPA는 영속성 콘텍스트를 이용합니다.

 

저희가 Service에서 update문을 수행하는 코드를 간단하게 나타내어보겠습니다.

 

@Transactional
public Long update(Long id, CommentUpdateDto commentUpdateDto, User user){
    Comment comment = commentRepository.findById(id).orElse(null);
    if(comment == null)
        return ErrorCodes.NOT_EXIST;
    else if(!comment.getUser().getId().equals(user.getId()))
        return ErrorCodes.NOT_SAME_USER;
    comment.update(commentUpdateDto.getComment());
    return id;
}

 

 

해당 코드의 로직은 다음과 같습니다.

 

1. commentRepository에서 우리가 보낸 id와 일치하는 comment 데이터를 가져온다.

2. 해당 comment가 null값이 아닌지, 그리고 해당 comment의 작성자가 현재 요청을 보낸 user와 일치하는지 확인한다.

3. comment를 수정한다.

 

저희는 update 쿼리를 생성하지 않았는데, 해당 함수로 database에 업데이트를 진행할 수 있습니다.

 

그 과정을 한번 살펴볼게요.

 

1. 저희는 commentRepository에서 원하는 comment를 찾아옵니다.

해당 comment를 찾았으면 Persistence Context에 1차 캐시에 해당 Comment를 가져오고 이를 Java program에 넘겨줍니다.

 

당연하게도 이 시점에는 1차 캐시에 존재하는 Comment Object와 Database에 존재하는 Comment Object가 같습니다.

 

2. 반환받은 Comment Object의 데이터를 저희가 원하는 데이터와 비교합니다. 오류가 없으면 3번에서 실제 값을 수정합니다.

 

영속성이란 데이터를 일치시킴을 의미합니다. 저희가 Java 프로그램에서 해당 값을 수정하면 영속성 컨텍스트에서 보관하고 있는 Comment Object도 저희가 수정한 값으로 변경되게 됩니다.

 

이제 이렇게 되면 Database의 데이터와 Persistence Context가 가지고 있는 data가 달라지게 됩니다.

 

@Transactional은 스프링 AOP전략에 따라서 해당 코드의 실행이 마무리될 때 자동적으로 commit을 진행해 줍니다. commit이 진행될 때 수정된 Object와 database의 데이터를 각각 비교하여 다른 부분을 모두 update를 해줍니다.

 

 

간략하게 작동 순서에 대해 알아보았습니다. 

그럼 이제 내부적으로 어떻게 동작하는지 조금 자세하게 알아보겠습니다.

 

JPA는 앱 실행과 동시에 한 DB당 하나의 EntityManagerFactory를 생성합니다.

WAS가 종료되는 시점에 EntityManagerFactory는 사라집니다.

 

EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("xxx");
entityManagerFactory.close();

 

이 EntityManagerFactory는 Transaction 요청이 하나 들어올 때마다 하나의 EntityManger를 생성합니다.

 

즉 고객의 요청이 들어오면 -> 하나의 스레드를 생성하여 EntityManager를 만들고 -> Transaction이 종료되면 해당 스레드를 종료합니다.

 

EntityManger의 Transaction은 다음과 같이 실행됩니다.

 

EntityTransaction trans = entityManager.getTransaction();
trans.begin();
trans.commit();
trans.rollback();
...

 

transaction이 시작되고, 해당 변경사항에 이상이 없으면 commit, 무언가 오류가 발견되면 rollback을 진행합니다.

 

영속성 컨텍스트의 생명주기

  • 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
  • 영속(managed): 영속성 컨텍스트에 저장된 상태
  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed): 삭제된 상태

즉 처음에 객체가 생성된 상태 -> 비영속

영속성 컨텍스트에 데이터가 저장된 상태 -> 영속

영속성 컨텍스트에 저장된 데이터가 분리된 상태 -> 준영속

마지막으로 삭제된 상태 -> 삭제

 

로 이해하시면 될 것 같습니다.

 

EntityManager entityManager = new EntityManager();

TestClass testClass = new TestClass();
// 비영속

entityManager.persist(testClass);
// 영속

entityManager.detach(testClass);
entityManager.clear();
entityManager.close();
// 준영속

entityManager.remove(testClass);
//삭제

 

 

영속성 컨텍스트의 이점

1. 1차 캐시

영속성 컨텍스트는 내부적으로 1차 캐시가 존재합니다.

1차 캐시는 Map<Key(primary key), Value(Entity)> 형태로 저장합니다.

 

이렇게 되면, DB에 직접 조회를 요청하지 않아도 캐시에서 바로 조회가 가능하다는 이점이 있습니다.

존재하지 않는다면, DB에서 조회하여 캐시에 저장하고, 저장되어 있는 동안에는 빠른 조회가 가능합니다.

 

2. 동일성 보장

영속성 컨텍스트는 엔티티의 동일성을 보장합니다.

 

3. 쓰기 지연

transaction이 시작되는 시점부터 끝나는 시점까지 SQL을 모아 두고, 

transaction이 종료되는 시점에 transaction.commit()이 호출됨과 동시에 모아둔 쿼리를 모두 보냅니다.

 

4. 변경 감지

위 예시에서 보듯 별도의 update 쿼리를 사용하지 않더라도 자동으로 commit시 db에 있는 데이터와 값 비교를 통해 변경을 감지합니다. 이를 Dirty Checking이라고 합니다.

 

*저의 글에 대한 피드백이나 지적은 언제나 환영합니다.