상세 컨텐츠

본문 제목

JPA 영속성 컨텍스트 이해 (Persistence Context)

Spring/JPA

by Chan.94 2024. 8. 26. 14:53

본문

반응형

왜 JPA 영속성 컨텍스트에 대한 이해가 필요한가?

JpaRepository에서 EntityManager가 사용된다. 따라서, EntityManager가 어떻게 동작하고 관리되는지를 알고 있다면 JpaRepository를 사용하는 데 있어 도움이 될 것이다.

EntityManager의 중요한 키워드는 영속성이다.

 


영속성 컨텍스트

  • 영속성 컨텍스트는 JPA를 이해하는데 가장 중요한 용어로 논리적인 개념이다.
  • Entity들이 DB로 바로 가지 않고 Entity를 저장하는 환경
  • 1차 캐시와 쓰기 지연
  • 변경 감지(Dirty checking)

 

1차 캐시

  • 영속성 컨텍스트 내부에는 엔티티를 보관하는 저장소가 있는데 이것을 1차 캐시라 한다.
  • EntityManager로 조회하거나 변경하는 모든 엔티티는 1차 캐시에 저장된다.
  • 트랜잭션을 commit 하거나 flush()를 호출하면, 1차 캐시에 있는 Entity의 변경 내역을 DB에 동기화한다.

쓰기 지연

  • 한 트랜잭션 안에서 DB에 보낼 쿼리문을 모았다가 한 번에 보냄
    쿼리문 모아두는 곳을 쓰기 지연 SQL 저장소라 한다.
  • 쿼리문 보내는 시점
    - EntityManger flush 
    - 트랜잭션 commit시 EntityManger flush 자동 호출
    - JPQL 쿼리 실행하면 EntityManger flush 자동 호출

변경 감지(Dirty checking)

  • 영속성 컨텍스트에서 보관하는 데이터에 변경이 일어났는지 확인
  • 트랜잭션 Commit 시점에 변경 감지(Dirty Checking) 이 동작한다.


EntityManager

    • EntityManager를 통해 Entity를 영속성 컨텍스트에 보관, 관리한다.
    • EntityManager가 관리하는 영속성 컨텍스트는 하나의 Transaction 단위이다.
      트랜잭션이 종료되면 EntityManager는 갖고 있는 영속성 컨텍스트의 내용을 DB에 반영하고 종료한다.
    • 같은 트랜잭션의 범위에 있는 EntityManager는 동일 영속성 컨텍스트에 접근한다.


Entity 생명주기

 

  • 비영속(New) : 영속성 컨텍스트와 연관이 없는 상태
  • 영속(Managed) : 영속성 컨텍스트에서 관리 중인 상태
  • 준영속(Detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(Removed) : 영속성 컨텍스트에서 완전히 삭제된 상태

비영속

User user = new User();
user.setUserId(userId);
user.setEmail(email);
user.setUsername(username);

영속

entityManager.persist(user);

Entity를 영속성 컨텍스트에 저장

준영속

entityManager.detach(user);

Entity를 영속성 컨텍스트에서 분리

삭제

entityManager.remove(user);

Entity를 영속성 컨텍스트에서 제거

 


EntityManager 주요 메서드

find(Class<T> entityClass, Object primaryKey)

  • PrimaryKey를 사용하여 영속성 컨텍스트 or 데이터베이스에서 Entity를 조회한다.
  • Entity객체에 @Id, @EmbeddedId 어노테이션이 선언된 것을 PrimaryKey라 한다.
  • Entity가 영속성 컨텍스트에 존재하지 않으면 데이터베이스에서 조회하여 영속성 컨텍스트에 추가한다.
  • 해당 Entity가 없으면 null을 반환한다.

persist(Object entity)

  • 엔티티를 영속성 컨텍스트에 추가한다.
  • persist를 호출한 후에는 해당 Entity가 영속 상태가 된다.
  • 트랜잭션이 커밋될 때 데이터베이스에 저장된다.
  • 영속성 컨텍스트에 이미 존재하는 Entity를 다시 persist 하면 예외가 발생한다.

detach(Object entity)

  • 지정된 Entity를 영속성 컨텍스트에서 분리하여 더 이상 관리되지 않도록 한다.
  • 분리된 Entity는 준영속 상태가 된다.
  • 이후 변경 사항은 데이터베이스에 반영되지 않는다.

merge(Object entity)

  • merge는 준영속 상태의 Entity나 새로운 Entity를 받아 영속성 컨텍스트에서 관리되는 Entity와 병합한다.
  • merge 메서드는 병합된 새 Entity를 반환한다.
  • merge를 호출하는 경우에는 반드시 리턴된 Entity를 통해서 다음 작업을 진행해야 EntityManager가 관리하는 상태를 추적할 수 있다.

remove(Object entity)

  • 영속성 컨텍스트에서 Entity를 제거한다.
  • 제거된 Entity는 트랜잭션이 커밋될 때 데이터베이스에서 삭제된다.

flush()

  • 영속성 컨텍스트에서 보류 중인 변경 사항(INSERT, UPDATE, DELETE)을 즉시 데이터베이스에 반영한다.
  • 보통 트랜잭션이 커밋될 때 자동으로 플러시 되지만, 이 메서드를 사용하여 수동으로 플러시 할 수 있다.

clear()

  • 영속성 컨텍스트를 완전히 초기화하여 모든 관리 Entity를 분리한다.
  • 이후 영속성 컨텍스트에 포함된 모든 Entity는 준영속 상태가 됩니다.

close()

  • EntityManager를 종료하여 영속성 컨텍스트를 닫는다.
  • close 된 EntityManager는 다시 사용할 수 없다.
  • EntityManager는 트랜잭션이 끝날 때 자동으로 닫히므로 수동으로 호출할 필요는 거의 없다.

refresh(Object entity)

  • 데이터베이스에서 해당 Entity의 최신 상태를 다시 읽어와 영속성 컨텍스트의 상태를 갱신한다.
  • 외부에서 데이터베이스가 변경된 경우, 영속성 컨텍스트의 Entity를 최신 상태로 유지하기 위해 사용한다.

createQuery(String qlString)

  • JPQL(Java Persistence Query Language) 쿼리를 생성하여 실행한다.
Query query = entityManager.createQuery("SELECT e FROM User e WHERE e.username = :username");
query.setParameter("username", "홍길동");
List<User> results = query.getResultList();

JpaRepository의 구현체 SimpleJpaRepository의 save(e entity)

@Transactional
@Override
public <S extends T> S save(S entity) {

    Assert.notNull(entity, "Entity must not be null.");

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}
  • 새로운 Entity이면 persist()를 그게 아니면 merge()를 호출한다.
    (save 메서드를 호출하면 INSERT 또는 UPDATE가 자동으로 되는 이유가 이거다.)
  • 트랜잭션이 종료될 때 DB에 반영된다.

JpaRepository의 구현체 SimpleJpaRepository의 delete(e entity)

@Override
@Transactional
@SuppressWarnings("unchecked")
public void delete(T entity) {

    Assert.notNull(entity, "Entity must not be null!");

    if (entityInformation.isNew(entity)) {
        return;
    }

    Class<?> type = ProxyUtils.getUserClass(entity);

    T existing = (T) em.find(type, entityInformation.getId(entity));

    // if the entity to be deleted doesn't exist, delete is a NOOP
    if (existing == null) {
        return;
    }

    em.remove(em.contains(entity) ? entity : em.merge(entity));
}
  • remove() 메서드를 사용한다.
  • 트랜잭션이 종료될 때 DB에 반영된다.

Transaction Manager

JpaTransactionManager

JpaTransactionManager는 JPA(Java Persistence API)를 사용하여 Entity와 데이터베이스 간의 작업을 관리할 때 사용된다. EntityManager와 함께 작동하며, JPA 표준에 따른 트랜잭션 관리 기능을 제공한다.

  • EntityManagerFactory와 연결되어, JPA 영속성 컨텍스트를 관리한다.
  • EntityManager를 통해 데이터베이스와의 연결을 유지하며, 트랜잭션 경계 내에서 데이터를 관리한다.

DataSourceTransactionManager

DataSourceTransactionManager는 순수 JDBC 또는 MyBatis와 같은 프레임워크를 사용할 때 주로 사용된다. 

DataSource와 직접 연결되어 JDBC 연결을 관리한다.

  • JDBC 기반의 트랜잭션 관리
  • DataSource를 사용하여 데이터베이스 연결을 직접 관리
  • JDBC 커넥션을 직접 사용하거나, MyBatis와 같은 ORM 프레임워크와 함께 사용할 때 적합

기본적으로, JpaTransactionManager는 JPA Entity Manager를 통해 트랜잭션을 관리한다.

하지만, JpaTransactionManager는 내부적으로 DataSource를 사용하여 트랜잭션을 처리하기 때문에, 이 DataSource를 MyBatis와 공유할 수 있다.

따라서, MyBatis를 사용할 때도 동일한 트랜잭션 컨텍스트에서 작업을 수행할 수 있다.

JPA와 MyBatis가 같은 데이터베이스에 대해 작업을 하고 있고, 동일한 트랜잭션 경계 내에서 두 프레임워크를 함께 사용하고자 한다면 JpaTransactionManager만 사용해도 된다

 

Spring Boot는 spring-boot-starter-data-jpa 의존성을 추가하면 자동으로 JpaTransactionManager를 구성하기때문에

별도로 Transaction Manager를 설정하지 않아도 된다.

 

 


JPA 영속성 컨텍스트로 관리되는 Entity를 MyBatis로 업데이트할 때 주의사항

JPA의 영속성 컨텍스트와 MyBatis의 분리된 상태

  • JPA의 영속성 컨텍스트는 Entity 객체를 관리한다. Entity가 영속성 컨텍스트에 포함되면, 해당 Entity의 상태는 영속성 컨텍스트에 의해 추적된다.
  • MyBatis로 데이터를 직접 업데이트하면, JPA는 해당 변경 사항을 인지하지 못한다. 즉, JPA의 영속성 컨텍스트에 있는 Entity와 데이터베이스의 상태가 불일치하게 됩니다.

변경 감지 실패

  • JPA는 Entity의 상태 변화를 자동으로 감지하고, flush() 시점에 데이터베이스에 반영한다.
  • MyBatis를 사용해 Entity를 직접 업데이트한 경우, JPA는 해당 Entity의 변경 사항을 모니터링할 수 없기 때문에, JPA가 관리하는 Entity가 여전히 이전 상태로 유지될 수 있다. 이는 예상치 못한 동작을 일으킬 수 있다.

JPA의 1차 캐시 문제

  • JPA는 Entity를 1차 캐시에 저장하여 데이터베이스와의 동기화 작업을 최소화한다. MyBatis로 데이터베이스를 업데이트하더라도 JPA의 1차 캐시는 여전히 이전 데이터를 참조하게 된다. 따라서, JPA를 통해 Entity를 다시 조회하면 변경된 데이터가 반영되지 않고, 이전 상태의 데이터가 반환될 수 있다.

 

JPA 영속성 컨텍스트에 의해 관리되는 Entity를 MyBatis로 업데이트하면, JPA의 영속성 컨텍스트와 데이터베이스 간의 상태 불일치가 발생할 수 있다. 따라서 이 경우에는 JPA와 MyBatis 간의 사용에 주의가 필요하며, 적절한 동기화 전략을 사용하는 것이 중요하다.

 


이제는 JPA Auditing 기능에 대해 알아보자.

 

JPA Auditing는 Entity의 변경 이력 관리에 필요한 기본적인 기능을 자동화하여 코드의 간결함과 유지보수성을 높이는 데 기여한다. 이를 통해 엔티티 생성과 수정 작업에 대해 일관성 있고 신뢰할 수 있는 이력을 관리할 수 있다.

 

자세한 내용은 아래 포스팅을 참고하기 바란다.

 

JPA Auditing 구현 (Custom EntityListener 적용)

 

JPA Auditing 구현 (Custom EntityListener 적용)

JPA Auditing이란엔티티(Entity)에 대한 생성, 수정, 삭제 등의 작업에 대한 추적 정보를 자동으로 기록해 주는 기능이다.주로 엔티티가 언제 생성되었는지, 누가 생성했는지, 언제 수정되었는지, 누

fvor001.tistory.com

 

반응형

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

JPA Auditing 구현 (Custom EntityListener 적용)  (1) 2024.08.28
[Spring] JPA Example (JpaRepository)  (12) 2023.03.30
ORM, JPA에 대한 이해  (12) 2023.03.29

관련글 더보기

댓글 영역

>