Spring-data-jpa 사용 시 @Transactional 은 꼭 있어야 할까?
코드리뷰를 하던 중 동료 개발자 분 코드에서 1건 저장 행위만 하는 Service 레이어 메서드의 @Transactional 이 없는 것을 발견했습니다.
나: "어? 이게 있어야하지 않을까요?"
동료 개발자 : "1개 만 저장하는 건데 있어야 하나요?"
생각해 보니 딱히 이유를 찾지 못했습니다.
어차피 spring-data-jpa 구현체인 SimpleJpaRepository의 save 메서드에서 @Transactional 이 걸려있습니다.
분명 제가 본 강의에서는 서비스 레이어의 클래스에는 @Transactional(readOnly=true) 어노테이션을 붙이고
쓰기 메서드에는 @Transactional을 붙이는 방식을 많이 활용한다고 했는데 이유가 기억나지 않았습니다.
조금 더 검색해 본 결과 다음과 같은 이유를 찾게 되었습니다.
1. JPA 영속성 컨텍스트에 의해 동작하는 변경감지, 지연로딩이 동작하지 않는다.
지연로딩
@Transactional 이 걸려있지 않다면 변경감지 지연로딩이 동작하지 않습니다.
다음과 같은 간단한? 엔티티가 있습니다.
Member 엔티티 에는 관계를 맺고 있는 MemberGroup이라는 엔티티가 있습니다.
FetchType.LAZY로 지연로딩하게 설정되어 있습니다.
다음과 같은 메서드가 있다고 했을 때 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 어노테이션을 붙여주는 게 아무런 고민을 해도 되지 않아 좋은 것 같습니다.😃