[Spring] @Transactional을 어디에 붙여야 할까🤔❔

2025. 2. 12. 17:06·TIL (Today I Learned)

Spring JPA로 일정 관리 앱을 만들어보면서, @Transactionla을 붙이는 기준을 공부해봤다.

 

@Transactional

`@Transactional`은 데이터베이스의 일관성을 보장하기 위해 사용된다.

쓰기(INSERT, UPDATE, DELETE) 연산에는 필수적이지만, 읽기(SELECT) 연산에서도 성능 최적화를 위해 고려할 수 있다.

 


1. @Transactional(readOnly = true)는 GET 메서드에 왜 필요할까?

JPA의 영속성 컨텍스트는 기본적으로 변경 감지(Dirty Checking)를 수행한다.

단순 조회(SELECT) 메서드는 변경이 필요 없으므로 readOnly = true를 사용하면 성능이 향상된다.

➡️ Spring이 "읽기 전용 트랜잭션"을 적용하면, 영속성 컨텍스트가 Dirty Checking을 하지 않아 성능이 최적화된다.

➡️ @Transactional(readOnly = true)를 GET 메서드에 추가하는 것이 좋다.

 

예시: 

@Transactional(readOnly = true)
public CommentDetailResponseDto getCommentById(Long id) {
	Comment findComment = commentRepository.findByIdOrElseThrow(id);
	return CommentDetailResponseDto.toDto(findComment);
}

 


2.  DELETE 메서드에서 @Transactional이 꼭 필요할까?

❗ `JpaRepository.delete()` 자체는 트랜잭션 없이 실행 가능

`JpaRepository.delete(entity)`는 내부적으로 JPA가 트랜잭션을 시작해서 실행한다.
단순한 delete 요청이면 @Transactional 없이도 정상 동작한다.

 

예시: 단순한 삭제 요청

public void deleteComment(Long id) {
    commentRepository.deleteById(id); // ✅ 트랜잭션 없이 실행 가능
}

 

✅ @Transactional이 필요한 경우와 이유

public void deleteComment(Long id) {
    Comment findComment = commentRepository.findByIdOrElseThrow(id); 	// ❗SELECT 실행
    commentRepository.delete(findComment); 				// ❗DELETE 실행
}

@Transactional이 없으면 두 개의 SQL이 서로 다른 트랜잭션에서 실행된다.
findByIdOrElseThrow(id)는 조회(SELECT) 쿼리를 실행하는데, 이 과정에서 엔티티는 영속성 컨텍스트에 등록되지 않는다.
delete(findComment)를 호출할 때, 이미 조회된 엔티티가 영속성 컨텍스트에 없을 수도 있다.
➡️ 삭제가 제대로 되지 않거나, Lazy Loading 관련 예외가 발생할 가능성이 있음
➡️ 트랜잭션을 명확하게 지정하여, 하나의 트랜잭션 내에서 SELECT → DELETE가 수행되도록 보장!

 


3. 메서드별 @Transactional 적용 기준

예시: 일정 관리 앱의 댓글 관리 API 서비스단

@Service
@RequiredArgsConstructor
public class CommentService {

    private final CommentRepository commentRepository;
    private final MemberRepository memberRepository;
    private final ScheduleRepository scheduleRepository;

    @Transactional // ✅ INSERT 작업이므로 트랜잭션 필요
    public CommentResponseDto createComment(String contents, Long memberId, Long scheduleId) {
        Member findMember = memberRepository.findByIdOrElseThrow(memberId);
        Schedule findSchedule = scheduleRepository.findByIdOrElseThrow(scheduleId);

        Comment comment = new Comment(contents);
        comment.setMember(findMember);
        comment.setSchedule(findSchedule);

        Comment savedComment = commentRepository.save(comment);
        return CommentResponseDto.toDto(savedComment);
    }

    @Transactional(readOnly = true) // ✅ 성능 최적화를 위해 readOnly = true 설정
    public List<CommentDetailResponseDto> getComments() {
        return commentRepository.findAll()
                .stream()
                .map(CommentDetailResponseDto::toDto)
                .toList();
    }

    @Transactional(readOnly = true) // ✅ 단순 조회이므로 readOnly = true 설정
    public CommentDetailResponseDto getCommentById(Long id) {
        Comment findComment = commentRepository.findByIdOrElseThrow(id);
        return CommentDetailResponseDto.toDto(findComment);
    }

    @Transactional // ✅ UPDATE 작업이므로 트랜잭션 필요
    public CommentResponseDto updateComment(Long id, String contents) {
        Comment findComment = commentRepository.findByIdOrElseThrow(id);
        findComment.updateComment(contents);
        return CommentResponseDto.toDto(findComment);
    }

    @Transactional // ✅ DELETE 작업이므로 트랜잭션 필요
    public void deleteComment(Long id) {
        Comment findComment = commentRepository.findByIdOrElseThrow(id);
        commentRepository.delete(findComment);
    }
}

 


4. 결론: 언제 @Transactional을 붙여야 할까?

💡 GET 요청 메서드에는 @Transactional(readOnly = true)를 적용

💡 INSERT/UPDATE/DELETE 메서드에는 일반 @Transactional을 적용

 

저작자표시 비영리 변경금지 (새창열림)

'TIL (Today I Learned)' 카테고리의 다른 글

[Spring] 인증/인가와 Session 방식과 JWT 방식의 차이  (0) 2025.02.20
[Spring] JPA 연관관계 매핑 확실하게 알아보기❗  (0) 2025.02.14
[Spring] spring.jpa.hibernate.ddl-auto 설정과 각 옵션  (0) 2025.02.12
[Spring] JPA 영속성 컨텍스트와 JOIN 활용  (0) 2025.02.12
[Spring] JPA에서 꼭 알아야 할 개념: 영속성  (0) 2025.02.10
'TIL (Today I Learned)' 카테고리의 다른 글
  • [Spring] 인증/인가와 Session 방식과 JWT 방식의 차이
  • [Spring] JPA 연관관계 매핑 확실하게 알아보기❗
  • [Spring] spring.jpa.hibernate.ddl-auto 설정과 각 옵션
  • [Spring] JPA 영속성 컨텍스트와 JOIN 활용
기만나🐸
기만나🐸
공부한 내용을 기록합시다 🔥🔥🔥
  • 기만나🐸
    기만나의 공부 기록 🤓
    기만나🐸
  • 전체
    오늘
    어제
    • ALL (147)
      • TIL (Today I Learned) (56)
      • Dev Projects (15)
      • Algorithm Solving (67)
        • Java (52)
        • SQL (15)
      • Certifications (8)
        • 정보처리기사 실기 (8)
  • 인기 글

  • 태그

    HTML
    시뮬레이션
    websocket
    jwt
    백트래킹
    자료구조
    bootstrap
    CSS
    javascript
    Firebase
    BOJ
    join
    jpa
    sql
    greedy
    백준
    Google Fonts
    mysql
    GROUP BY
    프로그래머스
    Subquery
    dp
    BFS
    다이나믹프로그래밍
    programmers
    DFS
    완전탐색
    jQuery
    그리디
    java
  • 최근 글

  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.3
기만나🐸
[Spring] @Transactional을 어디에 붙여야 할까🤔❔
상단으로

티스토리툴바