백엔드/JPA

Spring-data-jpa 사용 시 @Transactional 은 꼭 있어야 할까?

infitry 2024. 3. 24. 18:27
반응형

코드리뷰를 하던 중 동료 개발자 분 코드에서 1건 저장 행위만 하는 Service 레이어 메서드의 @Transactional 이 없는 것을 발견했습니다.

나: "어? 이게 있어야하지 않을까요?"

동료 개발자 : "1개 만 저장하는 건데 있어야 하나요?"

생각해 보니 딱히 이유를 찾지 못했습니다.

어차피 spring-data-jpa 구현체인 SimpleJpaRepository의 save 메서드에서 @Transactional 이 걸려있습니다.

 

 

분명 제가 본 강의에서는 서비스 레이어의 클래스에는 @Transactional(readOnly=true) 어노테이션을 붙이고

쓰기 메서드에는 @Transactional을 붙이는 방식을 많이 활용한다고 했는데 이유가 기억나지 않았습니다.

조금 더 검색해 본 결과 다음과 같은 이유를 찾게 되었습니다.

1. JPA 영속성 컨텍스트에 의해 동작하는 변경감지, 지연로딩이 동작하지 않는다.

지연로딩
@Transactional 이 걸려있지 않다면 변경감지 지연로딩이 동작하지 않습니다. 

다음과 같은 간단한? 엔티티가 있습니다.

Member Entity

Member 엔티티 에는 관계를 맺고 있는 MemberGroup이라는 엔티티가 있습니다.

FetchType.LAZY로 지연로딩하게 설정되어 있습니다.

findMemberLazyWithoutTransactional

다음과 같은 메서드가 있다고 했을 때 Member 엔티티를 조회한 후 MemberGroup 엔티티를 순회하며 code를 출력합니다.

(물론 출력하지 않는다고 해도 보통의 경우는 엔티티를 DTO로 변환하거나 하여 지연로딩이 실행됩니다.)

지연로딩 테스트

테스트를 실행을 시켜보면 

 

LazyInitializationException 이 발생하게 됩니다.

만약 당장 엔티티에 관계를 맺고 있지 않더라도 추후 다른 사람이 관계를 추가하게 될 수도 있을 것 같습니다.

따라서 미리 붙여놓는 게 좋을 것 같습니다.

 

변경감지

방금 본 Member 엔티티의 값을 변경해 보겠습니다.

member의 이름은 초기에 "tester1"로 저장되어 있습니다.

변경감지

다음과 같이 영속성 컨텍스트의 변경감지에 의해 값이 변경되길 기대하게 됩니다.

변경감지 테스트

위와 같은 테스트를 실행하였을 때 변경감지에 의해 값이 변경되었다면 두 멤버의 이름은 달라져야 합니다.

변경감지 테스트 결과

값이 변경되지 않아 테스트가 통과하게 됩니다.

그럼 "꼭 JPA의 변경감지 기능을 사용해야 할까?" 라고 생각을 할 수도 있습니다.

변경감지 기능을 쓰면 병합을 쓸 때보다 안전합니다.

직접적으로 SimpleJpaRepository의 save 메서드를 통해 병합을 하게 되면 모든 필드를 변경합니다.
예를 들어 특정 엔티티가 생성 시는 10개의 필드를 입력하지만 수정 시 3개의 필드만 수정가능하다고 했을 때,

준영속 상태의 엔티티를 병합하게 된다면 모든 필드를 입력하여 병합하게 됩니다.
(null 혹은 빈 값으로 업데이트할 위험이 있습니다.)

하지만 의식적으로 변경감지를 쓰게 된다면 @Id로 영속 상태의 엔티티를 가져오고 필요한 부분만 수정하면 됩니다.

 

2. @Transactional(readOnly=true)를 붙여주면 성능이 향상된다.

  • DB 마다 다르지만 읽기 전용 모드로 동작할 수 있습니다.
  • 변경감지 기능을 사용하지 않습니다.
  • 영속성 컨텍스트 flush를 하지 않습니다.

3. 개인적인 생각일 수도 있지만 추후 작업 시 다른 사람의 부담을 줄여줍니다.

하나의 데이터만 저장하고 있던 서비스 레이어의 메서드에서 다른 작업이 추가가 되었습니다.

이때 @Transactional 어노테이션이 걸려있다면, 다른 사람은 트랜잭션에 대한 고민을 하지 않아도 됩니다.

 

정리

위와 같은 이유로 스프링 + JPA를 사용 중이라면 서비스 레이어 클래스에는 @Transactional(readOnly=true) 어노테이션을 붙여 모든 메서드를 기본적으로 read only 트랜잭션으로 동작하게 만들고, 쓰기 작업을 하는 메서드의 경우만 @Transactional 어노테이션을 붙여주는 게 아무런 고민을 해도 되지 않아 좋은 것 같습니다.😃

반응형