[내배캠/33일차] TIL - Spring 숙련 과제 완료 및 리팩토링

euphony·2025년 2월 12일
0

내일배움캠프

목록 보기
47/66

✅오늘의 한 일

  • Spring 숙련 과제
    • 댓글 CRUD
    • 페이지네이션
    • 사용자 정의 예외처리 적용
    • ENUM으로 에러메세지 관리
    • 기타 리팩토링
    • SQL 및 ERD 작성

💻오늘의 학습

Spring 숙련 과제 - 일정 관리 앱(develop)

세션 키 가져올 때 ClassCastException

댓글 생성 시, 현재 로그인한 member의 세션키를 가져오는 과정에서 ClassCastException 오류가 발생했다.

HttpSession session = request.getSession(false);
Long memberId = (Long) session.getAttribute("sessionKey"); // 오류 발생 
CommentResponseDto commentResponseDto = commnetService.save(id, memberId, dto);

세션키로 Email을 저장했는데, StringLong으로 변환하려고 하니 오류가 난 것이다. 따라서 다음과 같이 수정하여 문제를 해결했다.

HttpSession session = request.getSession(false);
String email = (String) session.getAttribute("sessionKey");
CommentResponseDto commentResponseDto = commnetService.save(id, email, dto);

Service에서도 id로 찾던 member를 email로 찾아 반환했다.

// Member member = memberRepository.findByIdOrElseThrow(id); // 기존 코드
Member member = memberRepository.findByEmailOrElseThrow(email);

INSERT 됐지만 테이블이 안보이는 문제

댓글 작성 시, 아래와 같이 INSERT문은 제대로 실행된 것 같은데 DB에서는 comment 테이블이 보이지 않는 문제가 있었다.

@Transactional을 추가하지 않은 것이 원인이다. 트랜잭션이 자동으로 커밋되지 않아서 데이터가 DB에 반영되지 않았던 것이다. Service 계층에서 DB 작업을 수행할 때는 반드시 @Transactional 을 사용해 트랜잭션을 관리해야한다.

다음과 같이 @Transactional 을 붙인다.

@Transactional
public CommentResponseDto save(Long scheduleId, String email, CommentRequestDto dto) {
    Schedule schedule = scheduleRepository.findByIdOrElseThrow(scheduleId);
    Member member = memberRepository.findByEmailOrElseThrow(email);

    Comment comment = new Comment(dto.getContents(), member, schedule);
    Comment savedComment = commentRepository.save(comment);

    return new CommentResponseDto(
        savedComment.getId(),
        savedComment.getContents(),
        member.getUsername(),
        savedComment.getCreatedAt(),
        savedComment.getModifiedAt()
    );
}

이렇게 해도 되지만, 어제 <연관 관계 매핑 세션>에서 튜터님께서 클래스 레벨에 우선@Transactional(readOnly = true)를 사용한다는 것이 기억나 다음과 같이 수정했다. 그리고 지난번 피드백을 다시 보니 다른 튜터님께서도 "트랜잭션 범위를 확실히 하기 위해 DB 업데이트와 관련된 모든 작업을 트랜잭션 내에서 처리하는 것이 좋다."라고 피드백을 남겨주셨다.

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CommentService {

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

    @Transactional
    public CommentResponseDto save(Long scheduleId, String email, CommentRequestDto dto) {
        Schedule schedule = scheduleRepository.findByIdOrElseThrow(scheduleId);
        Member member = memberRepository.findByEmailOrElseThrow(email);

        Comment comment = new Comment(dto.getContents(), member, schedule);
        Comment savedComment = commentRepository.save(comment);

        return new CommentResponseDto(
            savedComment.getId(),
            savedComment.getContents(),
            member.getUsername(),
            savedComment.getCreatedAt(),
            savedComment.getModifiedAt()
        );
    }
}

이 문제를 해결하면서 잘 신경쓰지 않았던 부분의 개념을 확실히 익히게 되었다.

  • 클래스 레벨의 @Transactional(readOnly = true) 적용
    • 기본적으로 모든 메서드는 읽기 전용 트랜잭션 사용
    • 변경 감지(Dirty Checking) 비활성화 → 불필요한 데이터 변경 감지 로직 생략 → 성능 최적화
    • 조회 시 @Transactional이 없어도 동작하지만, @Transactional(readOnly = true)를 사용하면 성능이 최적화 되며 Lazy Loading(지연 로딩) 사용 시 트랜잭션 유지가 필요할 수 있음
  • 쓰기 작업에는 @Transactional을 개별적으로 추가해야 함.
    • 메서드 레벨에서 @Transactional을 명시하면, 클래스 레벨의 readOnly = true를 무시하고 데이터 변경이 가능한 트랜잭션으로 동작

📝오늘의 회고

어제 연관 관계 매핑 못하는 못나가는 방에서 못나와서(...) 과제를 하나도 못하고 굉장히 피곤한 상태로 잤다.(TIL도 못쓴채..) 그래서 오늘 일찍 일어나서 오전 중에 댓글 CRUD 끝내고 오후에는 페이지네이션, 예외 처리 분리, 기타 리팩토링까지 했다. README까지 끝내려고 했는데 머리가 멍해져서 그냥 내일 하기로..🫠

📌내일의 할 일

  • Spring 숙련 과제 해설 듣기
  • 수준별 세션 못들은 강의 듣기

0개의 댓글

관련 채용 정보