본문 바로가기
가치관 쌓기/개발 돌아보기

영속성 처리하는 Method에 @transactional는 항상 붙이는게 맞을까?

by simplify-len 2024. 4. 20.

문제

오늘 회사에서 특정 기능을 배포하자마자 문제가 발생했다. 핀포인트에서 발생하고 스택 트레이스는 다음과 같았다.

> lazyinitializationexception could not initialize proxy

Spring Boot, JPA 활용하는 개발자에게는 흔히 발생되는 예외중 하나이다.

이 예외가 발생하게된 이유는 무엇일까?

코드를 통해 이해해보자.

@Entity
public class Order {

    private long id;
    
    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    private List<OrderItem> orderItems;
    
}
public interface OrderRepository extends JpaRepository<Order, Long> { 
}
@Service
public class OrderService {

	private final OrderRepository repository;
    
	//@Transcational
    public Order getOrder(long id){
        Order order = repository.findById(id);
        order.getOrderItem().get(0).getName(); // 여기서 문제가 발생함!
    	return order
    }
}
spring:
  jpa:
    open-in-view: false

 코드를 살펴보면, OrderService 의 getOrder 에서 @Transactional 붙이지 않게 되면서, getOrder의 Transactional 의 영속성이 인터페이스까지 연결되지 않아 orderItems entity 를 조회할 수 없어 발생하게 된다.

 이 문제에 대해 개발자가 코드를 잘못 작성했기 때문에 문제가 발생했다고 할 수 있을까? 휴먼에러라 결정하고 다음부터는 코드를 잘 작성해야 한다고 결정하는게 맞는걸까?

우리가 겪었던 문제 상황에 대해서 다시 돌아가보자.

method에 @Transactional 추가하지 않아 문제가 발생했다.
JPA의 객체그래프는 Lazy Loading 방식으로 호출되도록 설정되었기 때문에 orderItem 을 호출하는 시점까지 Proxy로 물고있던 부분이 @Service 클래스에서는 Thread에 Hibernate의 Session이 유지되지 않아 예외가 발생하게 되었다. 

질문으로 돌아가 '영속성 처리하는 Method에 @transactional는 항상 붙이는게 맞을까?' 에 대해서 이 글을 읽는 사람은 어떻게 생각할까? OpenSessionInView가 False 이기 때문에 무조건 붙이는게 맞을까? 

내 생각이 틀릴 수도 있지만 적어도 나는 이렇게 생각했다.

1. 관심사의 분리가 잘되고 있는걸까?

흔히 JPA 에서 사용되는 @Entity 애노테이션을 활용해 객체그래프를 만들게 된다. @OneToMany, @ManyToOne 등등 여기서 fetch=Lazy 를 설정하도록 여러 선배개발자들은 권고하고 있다. @Entity 를 통해 만들어진 객체그래프를 활용하는 @Service 가 붙은 클래스 관점에서는 fetch=Lazy 설정을 알 수 있을까?

 우리가 개발하는 대부분의 아키텍쳐는 레이어드아키텍쳐를 따르게 되는게 이 아키텍쳐를 설명할 때 첫번째로 말하는 핵심은 관심사의 분리를 말한다. 

 

각각의 계층의 역할을 관심사에 따라 독립적으로 분리될 수 있어야 한다. 관심사의 분리에 의해 우리는 비지니스 로직에 집중해 코드를 작성할 수 있는데, @Service가 붙은 클래스에서는  @Entitiy 에 붙은 Lazy 설정에 관심을 갖고 코딩해야 되는걸까?

뭔가 이상하다고 느꼈다.

JPA 의 객체그래프 설정시 Lazy Loading, Eager Loading 등의 설정은 JPA 를 활용한 개발 복잡도를 더 높이는게 아닌가? 라는 생각이 든다. 

2. 무분별한 @Transactional 사용은 앙꼬 없는 찐빵이지 않을까?

@Service
public class OrderService {

	private final OrderRepository repository;
	private final OrderItemRepository orderItemRepository;    
	private final SlackSender slackSender;
	
    @Transcational
    public void increaseViewCount(long id){
        Order order = repository.findById(id);
        order.increaseViewCount();
        slackSender.send("성공"); // 만약 여기가 실패한다면?
    }
    
    @Transcational
    public OrderItem getOrderItem(long id){
         OrderItem order = orderItemRepository.findById(id);
         return order;
    }
}

 예를 들어, 관리자에게 View Count가 올라갔다는 슬랙 메세지 전송이 포함된 @Transactional 이 붙은 메소드라면, 서드파트 라이브러리 슬랙 API 가 사용되는데, increaseViewCount(id) 메소드 자체가 실패하는 문제가 발생했다고 가정해보자. 그렇다면 increaseViewCount() 메소드도 rollback 이 되면서 유저는 좋지 못한 경험을 할 가능성이 높아진다.

서드파트 라이브러리가 @Transactional 에 함께 포함되어 호출될 경우 조심해야 한다.

이번에는 getOrderItem() 메소드를 살펴보자. @Transactional가 필요할까? 이미 findById 에서 @Transactional 이 정의되어 있기 때문에 불필요한 비용을 발생하게 된다.

결론

JPA 을 잘 알고 사용하자....!

 

참고자료

https://medium.com/@jkha7371/is-transactional-readonly-true-a-silver-bullet-1dbf130c97f8    

https://hungseong.tistory.com/74   

https://brunch.co.kr/@anonymdevoo/44

댓글