DTO! 그리고 영속성

김재현·2023년 12월 1일
0

TIL

목록 보기
46/88
post-thumbnail

오늘의 알고리즘

>문자열 나누기_자바
>대충 만든 자판_자바

밸로그는 가끔 반응이 느려지고 업로드가 안될때가 있다. 이용자가 많아서 서버에 부하가 많이 걸리는걸까?
앞으로 개발하다보면 이런 문제들을 해결하는 방법도 알게되겠지 기대된다.


DTO! 엔터티를 직접 건내지 말자

DTO는 "Data Transfer Object"의 약자로, 데이터 전송을 위한 객체이다.
DTO는 주로 비즈니스 로직(service,controller단)에서 데이터를 전송하거나 전달할 때 사용된다.

DTO를 사용함으로써 레이어 간의 의존성을 줄이고, 데이터 포맷 변환으로 보안, 성능 최적화 등의 장점을 가져갈 수 있다.

왜 이런 이야기를 했느냐, 그것은 오늘 몸소 체험했기 때문이다.
일전에 "Spring security(SS) 형님은 DTO에 민감하시다."라고 쓴 적이 있는데, SS형님께서 DTO를 사용하지 않고 엔터티 객체를 직접 보내면 서버에서 에러를 발생시킨다는 내용이었다.
그런데 아무리 찾아봐도 그런 내용은 나오지 않는 것이다..! 그래서 "뭐지... 내가 잘못알았나" 라고 생각하며 포스팅에 줄을 그어놨다.

그런데 오늘! DTO를 사용하지 않고 repository에서 받아온 객체를 직접 전달하려하니 SS에서 허가를 내주지 않았다!! 원리를 알고싶어 디버깅하며 따라가보았지만 아직 모르는것 투성이었다...
열심히 공부해서 이해해보자.


영속성?

오늘도 어김없이 CRUD를 작업하던 중, 추가한 댓글이 예상과 달리 출력되지 않는 일이 있었다.

댓글5를 추가했는데, 4까지만 출력되는 모습.

    public TodoCardWithCommentsResponseDto createComment(Long cardId, CommentRequestDto requestDto, String tokenValue) {
        User user = validateToken(tokenValue);

        TodoCard todoCard = validateCardId(cardId);

        List<CommentResponseDto> commentResponseDtoList
                =commentRepository.findAllByTodoCardId(cardId).stream().map(CommentResponseDto::new).toList();

        Comment comment = commentRepository.save(new Comment(requestDto,user,todoCard));

        return new TodoCardWithCommentsResponseDto(comment.getTodoCard(), commentResponseDtoList);
    }

결과를 보는 순간, "어 뭐지? 내가 영속성 잘못썼나? @Transactional을 줘야했던건가?" 하고 코드를 다시 살펴봤다. 하지만 save 메서드는 영속성이 내장된 메서드이기 때문에 문제 있어보이지 않았다.

문제 상황을 다시 보면 댓글이 받아와지긴 하지만 방금 생성한 것만 출력되지 않았다.
그렇다면 commentResponseDtoList를 만들 때 뭔가 잘못되었다는건데...

이렇게 유심히 보다가 결국 찾았다.

commentResponseDtoList를 만들기위해 레포지터리에 select쿼리를 날렸을 때의 DB는 save가 반영되지 않은 상태였다.

간단하게 위아래 줄을 바꿔서 오류를 해결했다.


Fulsh()와 commit()의 차이

영속성인지 고민해본김에 이전에 고민해봤던 문제를 써본다.
강의 자료에 다음과 같은 말이 있었다.

  • "트랜잭션 commit 후 추가적인 동작이 있는데 바로 em.flush(); 메서드의 호출입니다."

그렇다면 commit이 2번 수행된다는 것인가? DB에서의 commit과 구분해서 2번으로 한건가?
이런 의문으로 여기저기 자료를 찾던 중 이런 말도 있었다.

  • 쓰기지연 SQL 저장소에 있는 쿼리들이 flush() 되어 DB로 전송, 반영됩니다.

둘다 맞긴 하지만 애매한 말이다.

flush()

em.flush로 사용된다.
현재 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 역할이다. 이런 애매한 말은 싫다. 자세히 풀자면,

플러시가 발생하면 변경 내용이 데이터베이스에 즉시 반영되지만, 트랜잭션이 여전히 활성 상태이며 커밋되지 않은 상태.

요점은 변경이 끝나지 않았다는 것이다.

그렇다면 이걸 굳이 왜 commit과 분리하여 써야하는가? 그 이유는 DB는 매우 소중한 것이기 때문이다. 아기처럼 다뤄야 할 필요가 있다. 잘못 만지면 매우 큰 사태가 벌어질 수 있기에... 상상만해도 끔찍하다.

아무튼 그 예시로

1. DB에 저장되기 전에 추가적인 검증을 수행하는 것

try {
    // DB에 저장되기 전에 검증 수행
    validateEntity(entity);

    // flush()를 통해 DB에 반영 
    em.flush();

    // 트랜잭션 커밋
    em.getTransaction().commit();
} catch (ValidationException e) {
    // 검증 실패 시 예외 처리
    em.getTransaction().rollback();
    throw e;
}

여기서 flush도 한번 실패를 걸러주는 것이다!
반약 catch로 흘러들어가게되면 commit()이 되지 않기에 DB에 변경사항이 저장되지 않는다.

2. 로깅에 사용

// 특정 동작 로깅
log.info("Custom action before flushing");

em.flush();

하지만 꼭 기억해야 할 것은 '특정한 상황에서만 사용'하는 것이다. 부주의한 사용은 성능 이슈나 예상치 못한 동작을 초래할 수 있으니 주의하자.


detach와 clear 차이

  • em.detach();
    특정 entity를 준영속 상태로 만든다.
    (영속성 컨텍스트의 어떠한 기능도 사용할 수 없음)
  • em.clear();
    모든 컨텍스트를 초기화, 모든 영속 상태의 엔터티를 준영속으로 만들어버린다. 하지만 영속성 컨텍스트의 틀은 유지 된다.
    (계속해서 영속성 컨텍스트를 이용할 수 있음)

디버깅하면서 살펴봤는데,
여기서 틀을 유지한다는 의미가 detach한 뒤 다시 조회해서 쓸 수는 있지만 주소값이 다르게 저장된다는 얘기이다.


profile
I live in Seoul, Korea, Handsome

0개의 댓글