해결된 질문
작성
·
830
·
수정됨
1
안녕하세요. 테스트 코드를 수강하고 있는 학생입니다.
긴 작업일 경우 트랜잭션을 걸지 말라고 하셨는데.
조회용 트랜잭션이 Repository 단에서 걸린다는 말씀을 하셨는데 Repository의 조회 로직이 수행될 때 트랜잭션이 걸리는 건가요?
뭔가 잘못 이해한거 같기도 해서 질문합니다.
그리고
@RequiredArgsConstructor
@Service
public class MailService {
private final MailSendClient mailSendClient;
private final MailSendHistoryRepository mailSendHistoryRepository;
public boolean sendMail(String fromEmail, String toEmail, String subject, String content) {
boolean result = mailSendClient.sendEmail(fromEmail, toEmail, subject, content);
if (result) {
mailSendHistoryRepository.save(MailSendHistory.builder()
.fromEmail(fromEmail)
.toEmail(toEmail)
.subject(subject)
.content(content)
.build()
);
return true;
}
return false;
}
}
여기서는 Transaction을 걸어줘야 하는게 맞는거 같아서.
이 부분도 궁금합니다.
요약
저는 서비스 Layer에서 Transaction을 걸어 db작업에서 문제가 발생했을 경우 rollback 되어야 한다고 생각했습니다.
강의에서 긴 작업들이 실제로 트랜잭션에 참여하지 않아도 되는데 -> OrderStatisticsService에서는 CUD작업이 아니니깐 Transaction을 안 걸어도 된다.?
MailService에서는 Transaction을 걸어줘야 하는게 아닌가요? (Create 작업 수행)
조회용 트랜잭션이 Repository 단에서 걸린다.? 뭔지 잘 모르겠습니다.
답변 2
2
안녕하세요, Anfield 님! :)
2가지 질문 같은데, 하나씩 답변 드리겠습니다.
조회용 트랜잭션이 Repository 단에서 걸린다는 말씀을 하셨는데 Repository의 조회 로직이 수행될 때 트랜잭션이 걸리는 건가요?
서비스 레이어에서 우리가 트랜잭션을 걸지 않더라도, 스프링에서 기본적으로 JpaRepository의 구현체로 사용하는 SimpleJpaRepository를 검색하여 찾아보시면, 위와 같이 읽기 전용 트랜잭션이 걸려있는 것을 볼 수 있습니다.
즉, Repository 단에서는 우리가 트랜잭션을 걸지 않아도 SimpleJpaRepository 구현체에 의해 트랜잭션이 보장됩니다.
MailService에서는 Transaction을 걸어줘야 하는게 아닌가요? (Create 작업 수행)
앞선 질문에 대한 답변에서와 마찬가지로, SimpleJpaRepository를 다시 살펴보시면 save 메서드에는 엔티티를 저장할 수 있도록 쓰기 트랜잭션이 재정의 되어있는 것을 보실 수 있습니다. delete 메서드도 마찬가지입니다.
즉, CUD 작업 모두에서 적절한 트랜잭션을 보장하는 SimpleJpaRepository 덕에, 우리가 서비스 레이어에 트랜잭션을 반드시 걸지 않아도, 기본적인 CUD 동작은 하게 됩니다. ('변경 감지' 기능으로 동작하는 Update는 제외)
트랜잭션이 걸려있으면 롤백도 당연히 그 범위에 맞게 동작할 것이고요.
네트워크 요청 등이 포함된 긴 트랜잭션을 지양해야 하는 대표적인 이유 중 하나는, DB 커넥션 때문입니다.
보통 DB 커넥션을 생성하는 것은 비싼 행위이기 때문에, 미리 커넥션 풀(Pool)을 만들어두고 커넥션 사용 및 반환하는 구조로 되어있는데요.
DB와 직접적으로 관련 없는 작업인 네트워크 작업까지 포함하여 DB 커넥션을 점유하고 있게 되면, 상대적으로 다른 요청들에서 커넥션이 모자라 대기하는 문제가 발생할 수 있습니다.
만약 메일 전송 이후에 해야하는 작업들이 1가지가 아니라 트랜잭션이 보장되고 처리되어야만 하는 N개의 작업이었다면, 별도의 서비스로 추출해 트랜잭션을 적용하는 방법을 생각해볼 수 있습니다.
도움이 되셨기를 바랍니다.
감사합니다. :)
0
안녕하세요, 인프런 AI 인턴입니다.
질문하신 내용에 대해 답변드립니다.
서비스 계층에서 트랜잭션을 거는 것은 맞습니다. 데이터베이스 작업에서 문제가 발생했을 경우, 해당 작업을 원자적으로 처리해야 하고 오류가 발생하면 롤백이 되는 것이 일반적입니다. 이를 통해 데이터 일관성을 유지할 수 있습니다.
강의에서 언급하신 ‘긴 작업’이 트랜잭션에 참여하지 않아도 된다는 말은 상황에 따라 다를 수 있습니다. CUD(Create, Update, Delete) 작업이 아닌 조회 작업을 할 때는 readOnly = true
옵션을 걸어주어 성능을 최적화할 수 있습니다. 그러나 강의의 문맥에 따라 다르게 적용될 수 있으니, 상황을 명확히 하기 위해선 강의의 구체적인 맥락을 참조하시는 것이 좋습니다.
MailService 예제에서 이메일 전송 후 전송 이력을 저장하는 부분에서는 트랜잭션을 사용하는 것이 맞습니다. 실제 이메일 전송이 성공적으로 이뤄진 뒤 데이터베이스에 기록을 남긴다면, 이 두 작업은 하나의 단위로 처리되어야 하며, 만약 이메일 전송에 성공했으나 이력 저장시 오류가 발생한다면 롤백되어야 하기 때문입니다.
Repository에서 조회용 트랜잭션이 걸리는 부분은, Repository 메서드에 대해 @Transactional(readOnly = true)
로 설정하면, 해당 메서드가 데이터를 조회(read)만 한다는 것을 명시하고 성능 최적화를 위한 힌트를 제공하는 것입니다. 이는 스프링 데이터 JPA 또는 JDBC를 사용할 때 자주 적용되는 방식입니다.
질문에 대응하는 Spring Data 문서의 인용을 아래에 첨부하니 참고하시기 바랍니다.
Spring Data JDBC에서의 조회용 트랜잭션은 다음 문서에 설명되어 있습니다:
It is highly recommended to make query methods transactional. These methods might execute more then one query in order to populate an entity. Without a common transaction Spring Data JDBC executes the queries in different connections.
Spring Data JPA에서의 조회용 트랜잭션은 아래 문서에 설명되어 있습니다:
For read operations, the transaction configuration readOnly flag is set to true. All others are configured with a plain @Transactional so that default transaction configuration applies.
이렇게 조회용 트랜잭션 설정을 통해 성능을 최적화 할 수 있으며, 생성, 갱신, 삭제 작업에 대해서는 트랜잭션을 명시적으로 걸어 롤백 옵션을 사용할 수 있습니다.