본문 바로가기
💻 개발/Spring

11/22 - TIL : 영속성 컨텍스트와 Transaction

by 컴쏘 2024. 11. 22.

 

JPA를 공부하다보면 반드시 마주하게 되는 내용은 영속성 컨텍스트와 Transaction이다. 

 

ORM객체와 데이터베이스 테이블의 매핑을 통해 엔티티 클래스 객체 안에 포함된 정보를 테이블에 저장하는 기술이다.

그리고 JPA는 ORM의 표준 인터페이스이다. 

 

 

JPA에서 중요한 개념으로 생각되는 영속성 컨텍스트에 대해 알아보자. 

영속성 컨텍스트(Persistence Context)  

영속성 컨텍스트는 엔티티의 상태를 관리하는 JPA의 핵심 개념으로, 특정 트랜잭션 범위 내에서 엔티티를 메모리에 저장하고 관리한다. 

  • 데이터베이스에서 조회되거나 저장된 엔티티 객체를 캐싱해두고, 동일한 엔티티에 대한 요청이 있을 때 데이터베이스를 다시 조회하지 않도록 최적화한다. 
  • 특징 :
    • 1차 캐시 : 동일한 영속성 컨텍스트 내에서 동일한 엔티티를 조회하면 데이터베이스에 다시 쿼리를 날리지 않고 메모리 캐시에 저장된 엔티티를 반환한다. 
    • 동일성 보장 : 같은 트랜잭션 안에서 같은 객체는 동일성(==, 동등성과는 다르다. 동일성 vs 동등성 )이 보장된다. 
    • 변경 감지 : 영속성 컨텍스트에서 관리 중인 엔티티 객체의 상태를 지속적으로 감지하여 변경된 필드만 자동으로 DB에 반영한다. 
    • 지연 로딩 : 연관된 엔티티나 데이터는 실제로 접근할 때 로딩된다. 
    • 쓰기 지연 : 한 트랜잭션 안에서 DB에 보낼 쿼리문을 모았다가 한번에 보낸다. 

 

영속성 컨텍스트의 특징을 잘 살펴보면, 트랜잭션관련이 있다는 것을 알 수 있다. 

 

 

트랜잭션에 대해서도 알아보자.

트랜잭션(Transaction)

트랜잭션은 데이터베이스에서 수행되는 작업 단위이다. 트랜잭션 안에서는 모든 작업이 성공적으로 완료되어야 한다. 중간에 문제가 발생해서 작업이 실패한다면, 모든 작업을 취소하고 이전 상태로 되돌릴 수 있어야 한다. 

 

따라서, 트랜잭션을 안전하게 처리하기 위해서는 ACID 원칙을 준수해야 한다. 

  • Atomicity (원자성) : 트랜잭션 내의 모든 작업은 모두 완료되거나, 전혀 실행되지 않아야 한다. 부분적으로는 안됨
  • Consistency (일관성) : 트랜잭션이 완료되면, 일관성 있는 데이터베이스 상태 유지해야 한다.
  • Isolation (고립성) : 동시에 여러 트랜잭션이 수행되어도 각각의 트랜잭션은 다른 트랜잭션에 영향을 주지 않아야 한다. 
  • Durability (지속성) : 트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 반영되어야 한다. 

 

영속성 컨텍스트는 일반적으로 트랜잭션 단위로 동작한다. 

  • 트랜잭션이 시작되면 영속성 컨텍스트가 열리고
  • 트랜잭션이 종료되면 영속성 컨텍스트가 닫힌다. 

따라서, 트랜잭션이 다수의 논리적 작업을 포함하는 경우, 동일한 영속성 컨텍스트를 유지하는 것이 일반적이다. 

 

 

하지만... 만약, 하나의 트랜잭션에서 2개의 영속성 컨텍스트가 사용된다면? 

  • 동일한 엔티티서로 다른 영속성 컨텍스트에 존재할 수 있다. 
  • 이렇게 되면 해당 엔티티의 상태가 불일치할 수 있다. (한 쪽에서 수정했는데, 다른 쪽에서 모르는 경우) 

 

이럴 때는 트랜잭션 범위를 명확히 설정해야 한다. 

  • 논리적인 작업 전체가 하나의 트랜잭션으로 묶이도록 구성한다. (동일한 트랜잭션과 영속성 컨텍스트를 공유하기 위해)
  • Spring에서는 보통 @Transactional 어노테이션을 사용하여 트랜잭션을 관리한다. 

 

Spring이 @Transactional 어노테이션을 사용하여 트랜잭션을 관리한다고 했으므로, @Transactional 어노테이션에 대해서도 알아보자.

@Transactional

선언만 해주면 구현을 따로 해주지 않아도 사용할 수 있기 때문에, 선언적 트랜잭션이라고도 한다. 

 

영속성 컨텍스트와의 관계는 다음과 같다.

  • 영속성 컨텍스트의 시작과 종료 : @Transactional 이 선언되면 Spring은 트랜잭션을 시작하고 해당 트랜잭션에 연결된 영속성 컨텍스트를 생성한다. 
  • 변경 감지(Dirty Checking) : 트랜잭션이 커밋될 때 영속성 컨텍스트는 변경된 엔티티(Dirty Entity)를 감지하여 변경된 내용을 데이터베이스에 자동 반영한다. 
  • 지연 로딩 : @Transactional이 적용된 메서드 내에서 영속성 컨텍스트가 활성화되어 있어, 엔티티와 연관된 데이터가 필요할 때 지연 로딩이 정상적으로 동작한다. 
    • 만약, @Transactional이 없거나, 트랜잭션이 종료된 이후에 지연 로딩을 시도하면 LazyInitializationException이 발생한다.
    • 트랜잭션이 없다면, 영속성 컨텍스트도 없다. 
    • 따라서, 엔티티는 준영속 상태가 되고, 준영속 상태에서는 지연 로딩이 동작하지 않는다. (지연 로딩영속성 컨텍스트를 통해 DB에 접근해 데이터를 가져오는 방식)

 

추가적으로 @Transactional은 Spring AOP를 기반으로 동작한다. 

  • Spring AOP는 부가기능을 애플리케이션 코드와 분리하여 애플리케이션에 프록시를 통해 적용한다.
  • @Transactional의 동작은 다음과 같다.
    • 프록시 생성 : Spring은 트랜잭션이 선언된 클래스 또는 메서드를 감싸는 프록시 객체를 생성한다. (프록시 객체는 원래 객체의 메서드를 호출하기 전후부가작업(트랜잭션 시작, 커밋, 롤백...)을 수행한다.)
    • 프록시를 통한 호출 흐름 : 클라이언트가 트랜잭션이 선언된 메서드를 호출하면, Spring AOP는 프록시를 통해 요청을 가로채고, 프록시가 트랜잭션 논리를 처리한 후 실제 메서드를 호출한다. 

요약) 프록시가 메서드 호출 가로채기 - 트랜잭션 시작 - 원래 메서드 실행 - 결과 처리 (성공이면 커밋, 실패면 롤백) - 자원 정리 

 

 

조금 더 알아보기..! 

 

+) OSIV

더보기

OSIV : 영속성 컨텍스트를 요청(Request) ~ 응답(Response)까지 열어두는 전략 

 

보통 JPA의 영속성 컨텍스트는 트랜잭션 범위와 동일하게 관리된다. 하지만, OSIV를 사용하면 트랜잭션이 종료되더라도 영속성 컨텍스트는 유지된다. 

 

이를 통해 View에서도 지연 로딩 같은 기능을 사용할 수 있게 된다. 

 

개발에 편의성을 줄 것 같지만, 실무에서는 false로 사용할 것을 권장한다고 한다. 

- 데이터베이스 커넥션 리소를 오랜 시간 점유하게 되는 문제가 발생할 수 있고(실시간 트래픽이 중요한 곳에서는 커넥션 부족으로 장애 발생 가능하다.), 영속성 컨텍스트가 트랜잭션의 범위를 벗어난 View에서도 남아있기 때문에 잘못된 데이터가 사용될 수도 있다. 

 

출처

 

 

영속성 컨텍스트에 조금 더 가까워진 느낌이다.