DB 설계

URI 설계

  1. entity => dto 변환 (어디서?)

  2. 조회를 위한 메서드 - sql문을 작성할 때 파라미터 값이 비슷하고 반환 값이 같을 경우 각 조회하고 싶은 경우마다 새로운 sql문을 작성할 지 아니면 sql문을 유연하게 동적 쿼리로 작성하여 조회마다 같은 메서드를 사용하여 데이터를 얻도록 쿼리를 짜야하는 지 고민이 되었다.

    → 나는 조회 화면에 필요한 쿼리를 작성할 때 마다 input 값이 비슷하고 반환 타입도 같은 쿼리를 반복적으로 짜고 있었다. 그래서 의문이 들었다 굳이 비슷한 쿼리를 반복하여 여러 메서드를 만들어야 하나? 그래서 쿼리를 유연하게 동적쿼리 짜보았다. 작성하고 보니 너무 쿼리가 복잡해졌다. 이렇게 하나의 메서드로 반복되는 조회기능을 해결하여 복잡성을 줄이고 싶었지만 오히려 더 쿼리가 복잡져 이쁘지 않게 되었고 후에 쿼리를 보았을 때 이해가 되지 않을 것 같아 다시 조회에 따라 쿼리문을 작성해 주었다.

3.주요 코드에 대한 설명

4.프로젝트를 진행할 때 어려웠던 점/ 고민했던 부분과 해결 방법

-배포시 문제 :

  1. 버전 관리

Untitled

  1. secrekeyManager 적용 springboot 2.6 이상 버전 적용 x
  2. 2.6 미만으로 수정 → 기존 querydsl - springboot 2.6 이상 코드 추가한 코드 오류
  3. querydsl 빌드에 관한 코드 springboot 버전 2.6 미만으로 수정
  1. 빌드시 테스트 오류
  1. 기능별로 테스트 기본 데이터 설정이 달라 가정한 데이터 설정에 따라 테스트 클래스를 분리하여 작성하였다.

Untitled

  1. 로컬에서 테스트 실행시에는 모두 통과결과를 받았지만 git actions 에서 배포를 위해 테스트코드를 실행할 시에는

    ⇒ spring test Closing JPA EntityManagerFactory for persistence unit 'default’

    와 같은 에러를 내면서 BoardServiceReadBySearchConditionTest 의 판매글리스트_조회() 만 실패하여 배포에 실패하였다. (위 테스트클래스에는 판매글리스트_조회() 테스트 하나만 존재)

  2. 해결 x → 해당 테스트코드는 주석처리하여 배포 진행 - 실제 기능을 잘 처리됨.

  3. 도메인 설계 계층 어디까지?

  4. 게시글 이미지 수정시 이미지 데이터를 포함하지 않고 보내더라도 MultipartFile 이 null 값으로 들어오지 않고 빈 객체의 mutipartFile 값이 들어와 imageFiles ≠ null 을 통과해버려 오류가 발생했다. !imageFiles.isEmpty 도 통하지 않았다.

그래서 실제 get(0).getOriginalFilename() 를 찍어보았더니 “” 빈 공백이 출력되었다.

그래서 밑의 코드로 이미지가 들어오지 않았을 경우를 체크하여 해결하였다.

!boardUpdateDTO.getImageFiles().get(0).getOriginalFilename().isEmpty()

  1. 조회 로직시 서브쿼리

Untitled

예전부터 고민하던 경우이다. 예시는 회원이 관심 목록에 등록한 게시물을 조회하는 쿼리있다.

첫번째 - where문 서브쿼리를 통해 회원이 관심 등록한 boardId 들을 뽑아 where 에서 In 메서드를 통해 가져와 데이터를 뽑아내는 경우이다.

두번째 - join 을 통해 거르는 경우있다. heart 객체와 조인하여 게시물의 관심 데이터로 거르고 where문에서 한번 더 memberid가 principalId와 같은 경우만 가져오도록 걸러 데이터를 가져온다.

두가지 경우 모두 정상적으로 작동하지만 어느것을 선택하여 쿼리를 짜는 것이 좋은 건지 고민이다. 이번에는 querydsl로 서브쿼리를 작성해보적이 없어 공부하는 겸 서브 쿼리로 작성해보았지만 개인적을 생각으론 서브로쿼리로 작성하면 쿼리가 복잡해진다고 느끼고 생각할게 더 많아지는것 같다. 또한 join 보다 서브쿼리가 성능 좋지 않다는 것으로 알고 있어 최대한 join으로 해결하는 방안으로 하는 것이 좋다고 생각한다.

  1. uri 설계 고민 - 특히 restApi 를 설계할 때 더욱 어렵다. /엔티티s/{앞의 엔티티에 해당하는 Id}

이런식으로 짜고 점점 화면이 넘어갈 수록 계층적으로 /엔티티s/{id}/엔티티s/{id} 설계한다. 하지만 갈 수록 uri가 길어지고 controller 부분에서 패스변수를 사용하지 않는 경우도 발생한다. 그래서 어디까지 계층적으로 작성을 해야하고 어느지점부터는 그냥 다시 /엔티티s/{id} 로 작성해야하는 고민이며 패스변수가 필요없는 부부은 같은 엔티티 부분을 설계한다고 하더라고 필요없는 앞의 /엔티티s/{id} 부분은 빼도 되는지 궁금하다.

  1. insert, update, delete 와 같은 벌크성 쿼리는 JPA를 거치지 않고 바로 디비로 sql을 보내기 때문에 JPA의 1차캐시와 동기화가 되지 않는다. 때문에 @Modify 의 clearAutomatically = true 로 설정하면 1차 캐시를 모두 비우기 때문에 데이터를 불러 올 때 디비의 데이터를 불러와 동기화를 해준다.

예를 들어 테스트 코드에서 삭제 기능을 테스트하기 위해 위에 해당하는 delete 문을 보내고 실제로 데이터가 삭제되 었는지 findById 등을 통해 불러온다면 NoSuchElement 에러가 발생해야 한다. 하지만 1차 캐시에는 아직 데이터가 남아있으로 에러가 발생하지 않는다.

때문에 나는 @Modify 의 clearAutomatically = true 로 설정해주었다.

하지만 게시글 이미지 수정 기능에서 문제가 발생하였다.

Untitled

기존 이미지를 삭제하고(@Modify 의 clearAutomatically 사용) 새로운 이미지를 저장하는 상황이다. BoardImg.setBoard(board) - 에서

Untitled

board.getBoardImgList() 에서 no-session 에러가 발생한다. board 도메인에서 boadImgList 를 fetch = FetchType.*LAZY 로 설정해주어 영속성 컨텍스트를 닫히나고 나서 값을 얻으려 할때 발생하는 에러이다.*

분명 같은 트랙잭션에서 발생하고 있는 코드들인데 왜 no-session 에러가 발생하는지 궁금하여 clearAutomatically 를 false로 두고 실행보았더니 정상적으로 동작하였다. 때문에 내 추즉은 delete 쿼리가 sql를 보내고 clearAutomatically로 1차 캐시를 비울 때 board에 접근할 수 있는 권한을 끊는 것 같다. .. 하지만 (아직 같은 트랜잭션인데 왜 값을 얻을 수 없는지 이해가 안되는건 똑같다.)

때문에 clearAutomatically = true로 두곤 수정 기능이 수행되지 않기때문에 false 바꾸고 삭제 테스트코드는 주석 처리 해놓았다. 밑의 사진은 true 로 두었을 때 테스트코드가 제대로 통과하는 사진이다.

Untitled

  1. 삭제 벌크 연산 시 JPA - cascade = CascadeType.*REMOVE 작동 안함.*

회원 탈퇴 시 board를 삭제해야하기 때문에 delete from board b where b.member = :member 로 쿼리를 작성하여 삭제하였다. 하지만 벌크연산은 JPA가 작동하지 않기 때문에 board에 해당하는 comment, heart 등이 자동으로 삭제되지 않아 맡의 에러가 발생하였다.

Untitled

그래서 나는

Untitled

와 같이 직접 heart, comment 와 같은 boardId를 외래키로 같는 엔티티들의 삭제쿼리를 작성해 수행한후 delete from board b where b.member = :member 쿼리를 실행하여 board를 삭제해주고 member 엔티티까지 삭제할 수 있었다.

Untitled

위 해결방법으로 테스트 코드를 통과하였다.