[Spring] Transactional의 ReadOnly는 왜 써야할까

2025. 4. 28. 18:47·CS/Spring

스프링에서 백엔드 Repository를 구현하다보면 사용하는 어노테이션이 있다.

바로 @Transactional!

 

AOP를 활용해서 커밋, 롤백 등 트랜잭션을 관리하는 기능이다.

만약 해당 repository에서 읽기작업만 수행할 경우, readOnly=true 속성을 사용하기도 한다.

@Transactional(readOnly = true)

 

위 설정을 통해 성능상의 이점을 가져올 수 있다고 하는데, 좀더 구체적으로 어떤 이점이 있는지 공부해보려고 한다.

 


 

Transcational과 Dirty Cheking

Transactional은 begin()부터 commit()작업 전까지 영속성 컨텍스트를 유지한다.

또한, 영속성 컨텍스트로 불러온 Entity의 변경사항을 DB에 바로 적용하지 않고, Dirty Cheking을 통해 변경사항을 적용한다.

Dirty Checking은 상태 변화가 생겼을때 이를 백엔드에 적용하는 방식인데,

1. 위와같이 Entity를 조회하면 영속성 컨텍스트 내에서 Entity의 초기상태를 SnapShot처럼 저장하고

2. Entity의 변경사항이 일어나도 곧바로 DB를 변경 하지 않는다.

3. 트랜잭션이 끝나고 Commit되는 순간, EntityManager에서 영속성 컨텍스트에서 관리중인 Entity와 초기상태인 SnapShot을 비교함으로써 변경사항을 한꺼번에 DB로 전송한다(Dirty Checking)

 

이를 통해 한가지 이점을 알 수 있다. 불필요한 쿼리가 나갈 경우의수를 없앤다.

즉, 다음과같이 하나의 엔티티를 두번 수정했을 경우,

@Transactional
public Users changeUserName(Long userId) {
    Users user = userRepository.findUser(userId);

    user.setName("유저1");
    user.setName("유저2");
    return user;
}
@Test
public void update쿼리개수확인() {
    Users user = userService.changeUserName(1L);

}
2025-04-28 18:01:02.193 DEBUG 8497 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        users
        (id, name) 
    values
        (default, ?)
2025-04-28 18:01:03.012 DEBUG 8397 --- [           main] org.hibernate.SQL                        : 
    select
        users0_.id as id1_0_0_,
        users0_.name as name2_0_0_ 
    from
        users users0_ 
    where
        users0_.id=?
2025-04-28 18:01:03.108 DEBUG 8397 --- [           main] org.hibernate.SQL                        : 
    update
    	users
    set
    	name=?
    where
    	id=?

 

 

update쿼리는 단 한번만 나가는것을 확인할 수 있다.

 

 

그래서 이게 ReadOnly 속성이랑 무슨상관이냐고?

ReadOnly=true를 설정함으로써, 위와같이 스냅샷을 관리하지 않겠다고 명시하는것이다.

즉, 스냅샷을 관리하고 Entity를 비교해서 DB에 전송하는 자원 자체를 사용하지 않는다는 장점이 있다!

 


 

그러나 이때 두가지 의문이 생긴다.

1. 영속성 컨텍스트로 Entity를 관리하는것이 아닌가?

2. ReadOnly=true를 사용했을때, SNAPSHOT으로 관리하지 않으면 ReadOnly로 불러온 Entity의 변경사항은 적용되지 않는가?

 

이에 대해 테스트를 통해 알아보자.

 

1. 여전히 영속성 컨텍스트로 Entity를 관리하는가?

@Transactional(readOnly = true)
public Users findUsers(Long userId) {
    userRepository.findUser(userId);
    return userRepository.findUser(userId);
}

특정 user를 검색하는 쿼리를 두번 작성했다.

만약 영속성 컨텍스트로 user를 관리하고 있다면 select쿼리는 1번만 나갈것이고, 관리하지 않는다면 2번 나갈것이다.

@Test
public void update쿼리개수확인() {
    Users user = userService.findUsers(1L);
}

테스트 코드를 돌린 결과는 다음과 같다.

 

2025-04-28 18:05:12.812 DEBUG 6174 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        users
        (id, name) 
    values
        (default, ?)
2025-04-28 18:05:12.840 DEBUG 6174 --- [           main] org.hibernate.SQL                        : 
    select
        users0_.id as id1_0_0_,
        users0_.name as name2_0_0_ 
    from
        users users0_ 
    where
        users0_.id=?

select 쿼리가 한번만 나간다. 따라서 영속성 컨텍스트가 User Entity를 관리하고 있다는 결론이 나온다.

 

 

2. 엔티티의 변경사항은 영속성 컨텍스트에 적용되지 않는가?

@Transactional(readOnly = true)
public Users findUsersAndChangeName(Long userId) {
    Users user = userRepository.findUser(userId);
    user.setName("새로운 닉네임");
    return user;
}

이번엔 readOnly=true일때 Entity를 변경해보고, 스냅샷과 더티체킹으로 인한 update쿼리가 발생하는지 확인해보자.

만약 SnapShot을 관리하지 않고 있다면 update쿼리는 발생하지 않을것이다.

@Test
public void readOnly_Entity변경() {
    Users user = userService.findUsersAndChangeName(1L);
    
}

 

결과는 다음과 같다.

2025-04-28 18:11:43.423 DEBUG 8497 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        users
        (id, name) 
    values
        (default, ?)
2025-04-28 18:11:43.699 DEBUG 8397 --- [           main] org.hibernate.SQL                        : 
    select
        users0_.id as id1_0_0_,
        users0_.name as name2_0_0_ 
    from
        users users0_ 
    where
        users0_.id=?

 

update 쿼리가 발생하지 않았다. 즉, 스냅샷을 통한 더티체킹을 사용하지 않음을 알 수 있다.

 

 


 

결론적으로 readOnly=ture 속성으로 인한 이점은 다음과 같이 정리할 수 있었다.

 

1. 영속성 컨텍스트의 스냅샷을 관리하는 하드웨어 자원을 아낄 수 있다.

    (FlushType.MANUAL로 설정되어 더티체킹을 건너뛴다(CPU) 또한 영속성컨텍스트가 스냅샷을 보관하지 않는다(메모리))

2. 명시적으로 읽기 작업만 수행할것임을 알려준다.

 

또한 3. readOnly queryhint가 적용되어 DB레벨에서의 최적화가 이루어진다고 한다.

 

 

그렇다면 여기서 한단계 더 나아가서, DBMS별로 readonly=true 속성이 동일하게 동작할까?에 대해 알아보자.

 


DBMS별로 readonly=true 속성이 동일하게 동작할까?

마침 잘 정리된 블로그가 있어 참고했다.

마침 자주 사용하는 DB 세가지(Oracle, MySQL, PostgreSQL)이여서 정리하고 가고자 한다.

 

Oracle

트랜잭션이 시작되기 전엔 이전에 커밋된 데이터에만 접근할 수 있고,

트랜잭션이 실행되는 동안 커밋되는 데이터는 결과에 반영되지 않는다.

또한 해당 트랜잭션시 지원하는 DML은 SELECT문 뿐이다.

 

→  트랜잭션 내에서 일관적 데이터를 얻도록 보장한다. 성능이점만을 위한것이 아님!

 

 

PostgreSQL

SELECT를 제외한 DDL, DML, DCL은 작동하지 않는다.

읽기동작을 가정할 경우, Read/Write 속성과 성능차를 가지지않으며(최적화 X), Deffered속성(데이터 한번 수정 후 마지막에 한번에 제약조건을 확인하는 방식)을 적용했을때, Serializable이나 read only를 사용하게 되어 내부 데이터가 수정되지 않도록 안전하게 처리할 수 있게 도와준다. 

 

→  성능이점이 아닌 동시성 제어를 위함이다.

→  트랜잭션 ID를 일반적 ID가 아닌 가상ID로 제공하므로, 실제 제공되는 트랜잭션ID수가 줄어 성능이 개선될 수 있다.

 

MySQL

SELECT문에 대해서만 기능을 지원한다.

Transaction ID 설정에 대한 오버헤드를 해결할 수 있다.

Read Only 트랜잭션에 대해서는 ID가 부여되지 않는다.

 

→  Oracle과 마찬가지로 별도 스냅샷을 통해 데이터를 조회하므로 데이터 일관성이 보장된다.

→  트랜잭션 ID 설정에 대한 오버헤드를 해결하고, 스냅샷을 통해 데이터 일관성을 보장한다.

 

 

 

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

[JPA] JpaRepository에서 @Repository 생략이 가능한 이유  (1) 2025.03.10
'CS/Spring' 카테고리의 다른 글
  • [JPA] JpaRepository에서 @Repository 생략이 가능한 이유
▹ 서현서현
▹ 서현서현
좋아하는걸 마음껏하는 백엔드 개발자😎
  • ▹ 서현서현
    Circus
    ▹ 서현서현
  • 전체
    오늘
    어제
    • 분류 전체보기 (35)
      • 회고 (2)
      • 프로젝트 (22)
        • 빼곡 (11)
        • SEMENTO (2)
        • FindDog (1)
        • 척척약사 (1)
        • 차곡차곡 (2)
        • 원더웨이 (3)
        • 트래블 캐리어 (2)
      • 알고리즘 (4)
      • 기타 (2)
      • CS (4)
        • Spring (2)
        • 대규모 시스템 설계 (2)
  • 링크

    • 👾 Github
    • 🧩 Algorithm
  • 공지사항

    • 블로그를 이전했습니다 💖
  • 인기 글

  • hELLO· Designed By정상우.v4.10.1
▹ 서현서현
[Spring] Transactional의 ReadOnly는 왜 써야할까
상단으로

티스토리툴바