[JPA] @Transactional(readOnly = true) 사용 이유

해니·2024년 10월 30일
0

JPA

목록 보기
8/8
post-thumbnail



작년 이직 준비 시절 .. 과제 면접을 실무에서 해보지 않은 JPA를 사용했었다
어찌저찌 과제 전형 통과하고 면접을 봤는데 'readOnly = true 사용하신 이유가 무엇인가요?'라는 질문을 받고 아무 말도 못 해버림 .. 면접 다녀와서 간략하게 메모해둔 게 다라 확실하게 이해하기 위해 이제라도 정리해본다 ... ✏️ 💭





@Transactional의 readOnly 속성


@Transactional(readOnly = true)

  • true인 경우 INSERT, UPDATE, DELETE 실행 시 예외 발생
  • 예상치 못한 엔티티의 등록(INSERT), 수정(UPDATE), 삭제(DELETE)를 예방하고 성능을 최적화 할 수 있다.

@Transactional(readOnly = false)

  • 기본 설정은 false
  • INSERT, UPDATE, DELETE 실행 시 예외 발생하지 않는다.





readOnly = true 설정에 따른 성능 향상


더티 체킹(dirty checking)

  • 영속성 컨텍스트는 Entity 조회 시 초기 상태에 대한 Snapshot을 저장한다.
    • 트랜잭션이 Commit 될 때, 초기 상태의 정보를 가지는 SnapshotEntity의 상태를 비교하여 변경된 내용에 대해 update query를 생성해 쓰기 지연 저장소에 저장한다.
    • 일괄적으로 쓰기 지연 저장소에 저장되어 있는 SQL queryflush() 하고 데이터베이스의 트랜잭션을 commit함으로써 우리가 update와 같은 메서드를 사용하지 않고도 Entity의 수정이 이루어진다.
  • readOnly = true를 설정하게 되면 스프링 프레임워크는 JPA의 세션 플러시 모드를 MANUAL로 설정한다.
  • readOnly = true를 설정하게 되면 JPA는 해당 트랜잭션 내에서 조회하는 Entity는 조회용임을 인식하고 변경 감지를 위한 Snapshot을 따로 보관하지 않으므로 메모리가 절약된다.

더티 체킹에 대한 자세한 기록은 ->[JPA] 더티 체킹(dirty checking)



플러시 (Flush)

  • 영속성 컨텍스트의 변경 내용을 데이터 베이스에 반영한다.

트랜잭션 commit시, flush 발생


변경 감지에서의 flush


flush()는 어떤 작업들을 수행하는 것이고, flush는 그 수행 작업들 중 하나다. 💭


Transaction Commit시 내부적으로 flush() 호출

  1. 엔티티와 스냅샷 비교 후 변경된 것에 대한 SQL 생성
  2. 생성된 SQL을 쓰기 지연 SQL 저장소에 등록
  3. 쓰기 지연 SQL 저장소에 등록된 쿼리를 DB로 전송 -> flush

Transaction Commitflush가 자동 호출 되는 이유

  • flush() 는 우리가 DB Editor 에서 SQL문을 작성하는 것이라고 할 수 있으며, 트랜잭션 커밋은 말 그대로 commit을 수행하는 것이다.
  • SQL문을 작성하지 않고 commit을 수행한다면 당연히 어떤 일도 일어나지 않기 때문에, JPA는 이런 문제를 예방하기 위해 트랜젝션을 커밋할 때 플러시를 자동으로 호출한다.



MANUAL 모드

  • 트랜잭션 내에서 사용자가 수동으로 flush()를 호출하지 않으면 flush()가 자동으로 수행되지 않는 모드

  • 트랜잭션 내에서 강제로 flush()를 호출하지 않는 한, 수정 내역에 대해 DB에 적용되지 않는다.

  • 트랜잭션 commit 시 영속성 컨텍스트가 자동으로 flush() 되지 않으므로 조회용으로 가져온 Entity의 예상치 못한 수정을 방지할 수 있다.




readOnly = true 설정에 따른 가독성 향상

@Transactional(readOnly = true)
fun findAccountLog(memberDetailsVO: MemberDetailsVO): List<AccountLogFindResponse> {
	/* ... */
}
  • 직관적으로 해당 메서드가 조회용 메서드임을 알 수 있어 가독성 측면에서도 이점을 가진다.

⛔️ 모든 조회용 메서드에 @Transactional(readOnly = true)를 설정하는 등 무분별한 사용은 스냅샷 유지, flush()의 필요 등 관리적 / 메모리적 측면에서 오히려 좋지 않을 수 있고, 커넥션을 오래 가지고 있어 커넥션 부족 등의 문제가 발생할 수 있기 때문에. ⛔️





readOnly = true 설정으로 LazyInitializationException 해결


LazyInitializationException 발생 원인

  • JPA를 이용하여 객체의 양방향 연관관계를 구성한 후에 엔티티를 조회했을 때 LazyInitializationException 예외가 발생할 수 있다.
    • FetchType.LAZY로 연관관계가 설정된 필드는 지연 로딩으로 조회 되는데, 이때 해당 필드의 엔티티를 프록시 객체로 조회하게 된다.
    • 프록시 객체로 조회된 엔티티는 초기화 되어있지 않기 때문에 해당 엔티티를 조회하려 할 때 LazyInitializationException 예외가 발생한다.

🔎 프록시 객체
: 지연 로딩을 사용하여 엔티티를 조회했을 때, 실제 엔티티 객체 대신에 Mock Object 역할을 하는 가짜 객체

📖 지연 로딩 (LAZY LOADING)

  • @ManyToOne(fetch = FetchType.LAZY) 설정하여 사용.
  • 실제 데이터가 필요한 시점에 데이터베이스를 조회해서 프록시 객체를 초기화한다.
  • @OneToMany, @ManyToMany 와 같이 연관된 엔티티가 여러 개인 경우에 대한 디폴트 패치 전략

LazyInitializationException 해결 방법

  • LazyInitializationException 예외는 JPA의 영속성 컨텍스트가 종료된 후에 연관관계가 설정된 엔티티를 조회하려고 할 때 발생하기 때문에, 세션이 유지되도록 트랜잭션을 설정해준다.







readOnly = true 설정에 따른 Replication 부하 분산


레플리케이션(Replication) 방식


  • 등장 배경: 기본적으로 간단한 프로젝트에서는 데이터베이스를 하나만 두지만, 실제 서비스에서는 사용량에 따른 문제를 해결하기 위해 레플리케이션 같은 방식을 사용한다.
  • 두 개의 이상의 DBMS 시스템을 Mater / Slave로 나눠서 동일한 데이터를 저장하는 방식
    • Master DB의 장애 발생 시 Slave DBMaster DB로 승격시켜 장애를 빠르게 복구할 수 있다.
    • 조회 작업은 Slave DB에서 수행하고, 수정 작업은 Master DB에서 수행함으로써 트래픽을 분산할 수 있다.
  • readOnly = true가 설정되어있는 메서드의 경우 Slave DB에서 데이터를 가져오도록 동작한다.






출처

@Transactional(readOnly = true)를 왜 붙여야 하나요
[JPA] commit, flush, Entity Manager의 clear()와 close()에서 궁금한 부분들 탐구 + 데이터 삭제 및 수정 시 1차 캐시에서 발생하는 현상 + 준영속과 비영속의 차이점
JPA에서 플러시(flush) 개념 및 호출 방법 3가지
Database의 리플리케이션(Replication)이란?
JPA LazyInitializationException 해결하기

profile
💻 ⚾️ 🐻

0개의 댓글