entity => dto 변환 (어디서?)
조회를 위한 메서드 - sql문을 작성할 때 파라미터 값이 비슷하고 반환 값이 같을 경우 각 조회하고 싶은 경우마다 새로운 sql문을 작성할 지 아니면 sql문을 유연하게 동적 쿼리로 작성하여 조회마다 같은 메서드를 사용하여 데이터를 얻도록 쿼리를 짜야하는 지 고민이 되었다.
→ 나는 조회 화면에 필요한 쿼리를 작성할 때 마다 input 값이 비슷하고 반환 타입도 같은 쿼리를 반복적으로 짜고 있었다. 그래서 의문이 들었다 굳이 비슷한 쿼리를 반복하여 여러 메서드를 만들어야 하나? 그래서 쿼리를 유연하게 동적쿼리 짜보았다. 작성하고 보니 너무 쿼리가 복잡해졌다. 이렇게 하나의 메서드로 반복되는 조회기능을 해결하여 복잡성을 줄이고 싶었지만 오히려 더 쿼리가 복잡져 이쁘지 않게 되었고 후에 쿼리를 보았을 때 이해가 되지 않을 것 같아 다시 조회에 따라 쿼리문을 작성해 주었다.
3.주요 코드에 대한 설명
키워드, 카테고리(중복) 검색
댓글, 대댓글 기능 - 댓글 (최신순), 대댓글(생성순)
성능 최적화 -
댓글수, 관심수 count 경우에는 데이터가 곱하기 되지 않기 때문에 sql문에서 해결하게 되면 후에 getCommentList().size() 와 같은 코드를 작성하여 추가 쿼리가 발생하지 않아도 된다. 때문에 나는 Board 와 같은 엔티티로 return 타입을 정하지 않고 Object[] 로 받아 댓글수와 관심수 데이터를 얻었다.
하지만 onToMany 에 해당하는 컬렉션 엔티티들을 페치조인을 사용하게 된다면 데이터가 곱해져 메모리에 부하가 오고 후에 페이징 처리를 하지 못하게 되는 문제가 발생한다.
때문에 나는 컬렉션 리스트에 해당하는 엔티티들을 @BatchSize를 사용하여 1+n 문제를 1+1으로 성능을 최적화하였다.
4.프로젝트를 진행할 때 어려웠던 점/ 고민했던 부분과 해결 방법
-배포시 문제 :
로컬에서 테스트 실행시에는 모두 통과결과를 받았지만 git actions 에서 배포를 위해 테스트코드를 실행할 시에는
⇒ spring test Closing JPA EntityManagerFactory for persistence unit 'default’
와 같은 에러를 내면서 BoardServiceReadBySearchConditionTest 의 판매글리스트_조회()
만 실패하여 배포에 실패하였다. (위 테스트클래스에는 판매글리스트_조회() 테스트 하나만 존재)
해결 x → 해당 테스트코드는 주석처리하여 배포 진행 - 실제 기능을 잘 처리됨.
도메인 설계 계층 어디까지?
게시글 이미지 수정시 이미지 데이터를 포함하지 않고 보내더라도 MultipartFile 이 null 값으로 들어오지 않고 빈 객체의 mutipartFile 값이 들어와 imageFiles ≠ null 을 통과해버려 오류가 발생했다. !imageFiles.isEmpty 도 통하지 않았다.
그래서 실제 get(0).getOriginalFilename() 를 찍어보았더니 “” 빈 공백이 출력되었다.
그래서 밑의 코드로 이미지가 들어오지 않았을 경우를 체크하여 해결하였다.
!boardUpdateDTO.getImageFiles().get(0).getOriginalFilename().isEmpty()
예전부터 고민하던 경우이다. 예시는 회원이 관심 목록에 등록한 게시물을 조회하는 쿼리있다.
첫번째 - where문 서브쿼리를 통해 회원이 관심 등록한 boardId 들을 뽑아 where 에서 In 메서드를 통해 가져와 데이터를 뽑아내는 경우이다.
두번째 - join 을 통해 거르는 경우있다. heart 객체와 조인하여 게시물의 관심 데이터로 거르고 where문에서 한번 더 memberid가 principalId와 같은 경우만 가져오도록 걸러 데이터를 가져온다.
두가지 경우 모두 정상적으로 작동하지만 어느것을 선택하여 쿼리를 짜는 것이 좋은 건지 고민이다. 이번에는 querydsl로 서브쿼리를 작성해보적이 없어 공부하는 겸 서브 쿼리로 작성해보았지만 개인적을 생각으론 서브로쿼리로 작성하면 쿼리가 복잡해진다고 느끼고 생각할게 더 많아지는것 같다. 또한 join 보다 서브쿼리가 성능 좋지 않다는 것으로 알고 있어 최대한 join으로 해결하는 방안으로 하는 것이 좋다고 생각한다.
이런식으로 짜고 점점 화면이 넘어갈 수록 계층적으로 /엔티티s/{id}/엔티티s/{id} 설계한다. 하지만 갈 수록 uri가 길어지고 controller 부분에서 패스변수를 사용하지 않는 경우도 발생한다. 그래서 어디까지 계층적으로 작성을 해야하고 어느지점부터는 그냥 다시 /엔티티s/{id} 로 작성해야하는 고민이며 패스변수가 필요없는 부부은 같은 엔티티 부분을 설계한다고 하더라고 필요없는 앞의 /엔티티s/{id} 부분은 빼도 되는지 궁금하다.
예를 들어 테스트 코드에서 삭제 기능을 테스트하기 위해 위에 해당하는 delete 문을 보내고 실제로 데이터가 삭제되 었는지 findById 등을 통해 불러온다면 NoSuchElement 에러가 발생해야 한다. 하지만 1차 캐시에는 아직 데이터가 남아있으로 에러가 발생하지 않는다.
때문에 나는 @Modify 의 clearAutomatically = true 로 설정해주었다.
하지만 게시글 이미지 수정 기능에서 문제가 발생하였다.
기존 이미지를 삭제하고(@Modify 의 clearAutomatically 사용) 새로운 이미지를 저장하는 상황이다. BoardImg.setBoard(board) - 에서
board.getBoardImgList() 에서 no-session 에러가 발생한다. board 도메인에서 boadImgList 를 fetch = FetchType.*LAZY
로 설정해주어 영속성 컨텍스트를 닫히나고 나서 값을 얻으려 할때 발생하는 에러이다.*
분명 같은 트랙잭션에서 발생하고 있는 코드들인데 왜 no-session 에러가 발생하는지 궁금하여 clearAutomatically 를 false로 두고 실행보았더니 정상적으로 동작하였다. 때문에 내 추즉은 delete 쿼리가 sql를 보내고 clearAutomatically로 1차 캐시를 비울 때 board에 접근할 수 있는 권한을 끊는 것 같다. .. 하지만 (아직 같은 트랜잭션인데 왜 값을 얻을 수 없는지 이해가 안되는건 똑같다.)
때문에 clearAutomatically = true로 두곤 수정 기능이 수행되지 않기때문에 false 바꾸고 삭제 테스트코드는 주석 처리 해놓았다. 밑의 사진은 true 로 두었을 때 테스트코드가 제대로 통과하는 사진이다.
cascade = CascadeType.*REMOVE
작동 안함.*회원 탈퇴 시 board를 삭제해야하기 때문에 delete from board b where b.member = :member 로 쿼리를 작성하여 삭제하였다. 하지만 벌크연산은 JPA가 작동하지 않기 때문에 board에 해당하는 comment, heart 등이 자동으로 삭제되지 않아 맡의 에러가 발생하였다.
그래서 나는
와 같이 직접 heart, comment 와 같은 boardId를 외래키로 같는 엔티티들의 삭제쿼리를 작성해 수행한후 delete from board b where b.member = :member 쿼리를 실행하여 board를 삭제해주고 member 엔티티까지 삭제할 수 있었다.
위 해결방법으로 테스트 코드를 통과하였다.