TIL

이 글도 공감해주세요 refactoring

haedal-uni 2022. 12. 13. 02:43
728x90

기존에 이 글도 공감해주세요 게시판 코드를 refactoring했다.

2022.12.01 - [TIL] - 이 글도 공감해주세요 게시판 기능 수정

 

 

기존 코드

RegistryRepository

@Query("select r.idx from Registry r")
ArrayList<Long> findAllByIdx();

 

CommentRepository

@Query("select c.registry.idx from Comment c")
ArrayList<Long> findAllByRegistry_Idx();

 

CommentServiceImpl.java

public List<Optional<Registry>> needComments() {
    List<Long> allByIdx = registryRepository.findAllByIdx();
    List<Long> allByRegistry_idx = commentRepository.findAllByRegistry_Idx();

    List<Long> temp = new LinkedList<>();
    temp.addAll(allByIdx);

    for (Long item : allByIdx) {
        if (allByRegistry_idx.contains(item) == true) {
            temp.remove(item);
        }
    }

    List<Optional<Registry>> result = new ArrayList<>();

    if (temp.isEmpty()) {
        result = Collections.emptyList();
    }

    if (temp.toArray().length > 10) {
        for (int i = 0; i < 10; i++) {
            result.add(registryRepository.findById(temp.get(i)));
        }
    } else {
        for (int i = 0; i < temp.toArray().length; i++) {
            result.add(registryRepository.findById(temp.get(i)));
        }
    }
    return result;
}

 

특히 ServiceImpl에서 if문이 너무 많아서 이를 줄이려고 생각하다가 refactoring 하게 되었다.

 


먼저 RegistryRepository에 작성된 코드를 지웠다.

그리고 CommentRepsotiroy에서 모든걸 다 처리할 생각이었다.

 

Registry의 idx값 - Comment.registry.idx 값 이므로 차집합을 이용하면 될 것 같았다.  참고한 블로그  

 

SELECT r.registry_id FROM Registry r WHERE r.registry_id NOT IN (SELECT DISTINCT c.registry_id from Comment c)  Limit 10;

만약 NOT IN을 사용했을 때 오류가 떴다면 해당 데이터의 null이 있는지 확인해본다.

처음에 값이 안떠서 왜 안뜨는지 확인해보니 기존에 db를 만들고 난 후에

연관관계 매핑을 하면서 registryId 컬럼을 추가했고 기존에 있던 db들의 registryId값은

null로 띄워져서 제대로 실행이 되지 않았었다.

 

그리고 NOT IN을 이용한 서브쿼리는 성능이 좋지 않다고 하므로 아래 코드로 작성했다.  https://toe10.tistory.com/156

SELECT r.registry_id FROM Registry r LEFT JOIN Comment ON r.registry_id = comment.registry_id WHERE Comment.registry_id is null Limit 10;

LIMIT는 반환되는 행의 개수를 제한한다.

LIMIT 10을 주어서 10개까지만 처리되게 했다. → ServiceImpl에서 if문을 생략할 수 있다.

10개 보다 작은 데이터가 있으면 해당하는 데이터의 개수만큼만 보여준다. (최대 행수가 10개까지 라는 것)

 

but, LIMIT는 MySQL과 PostgreSQL에서 사용할 수 있는 문법이다. 

따라서 JPQL에서는 사용할 수 없다.

[SQL 첫걸음] 11. 결과 행 제한하기 - LIMIT

 

 

따라서 LIMIT와 같이 제한을 하고 싶다면 Top을 이용해서

쿼리가 아니라 메소드 명을 findTop10By(); 와 같이 작성을 해야한다.

https://stackoverflow.com/questions/44565820/what-is-the-limit-clause-alternative-in-jpql

하지만 실제 test해보니 잘 동작하지 않았고 paging을 이용해서 사용해야 할 듯 하다.

 

 

 

위에서 sql console로 실행했을 때 잘 동작하는 것을 보고 JPQL언어로 수정했다.

@Query("SELECT r.idx FROM Registry r LEFT JOIN Comment ON r.idx = Comment.registry.idx WHERE Comment.registry.idx is null")

* JPQL에서 사용하는 Member는 클래스 명이 아니라 엔티티 명이다.

 

JPQL로 작성하면 엔티티 클래스를 바라보고 작성을 하기 때문에 

이후에 SQL 대신 다른 언어로 바꿔도 적용이 되기 때문에 언어에 의존적이지 않게 작성할 수있다.

* db 언어로 쓸수는 있지만 db를 변경하게 되면 다시 언어를 바꿔 써야 한다.

https://haedal-uni.github.io/posts/JPQL/

 

 

 

그런데 실행을 해보니 

Could not create query for public abstract Validation failed for query for method public abstract ~

cannot read field value because s1 is null

이런 에러가 계속 떴다.

 

 

대부분 해당 오류를 nativeQuery = true 로 보고 있었다.

직접 쿼리를 날리는 경우라면 NativeQueryTrue로 설정해주면 된다고 한다.

sql문으로 작성한게 아니라 JPQL로 작성한 것인데 왜 해당 오류가 뜬건지는 모르겠으나

혹시몰라 SQL 언어로 수정하여 실행했더니 동작했다.

🤔이 부분에 대해서 좀 더 찾아봐야겠다.

 

알고보니 Alias를 안써서 생긴 오류였다. JPQL에서는 Alias를 필수로 사용해야한다..!!

@Query("SELECT r.idx FROM Registry r LEFT JOIN Comment c ON r.idx = c.registry.idx WHERE c.registry.idx is null")

limit는 JPQL에서는 사용할 수 없기 때문에 paging으로 작성했다.

 

 

ServiceImpl

Pageable pageable = PageRequest.of(0, 10);
List<Long> temp = commentRepository.findTop10By(pageable);

 

Repository

@Query("SELECT r.idx FROM Registry r LEFT JOIN Comment c ON r.idx = c.registry.idx WHERE c.registry.idx is null")
List<Long> findTop10By(Pageable pageable);

Pageable : 페이지 처리에 필요한 정보를 담게 되는 인터페이스

 

PageRequest에 의해 Pageable에 페이징 정보가 담겨 객체화 된다.

PageRequest의 메서드

  • of(int page, int size) : 0부터 시작하는 페이지 번호와 개수. 정렬이 지정되지 않음
  • of(int page, int size, Sort sort) : 페이지 번호와 개수, 정렬 관련 정보

JPA Paging(Page, Pageable, PageRequest, Sort)

 

 

pr - [refactoring] postComment


아래는 sql 언어로 사용했을 때 코드로 작성했다.

 

@Query어노테이션에 들어있는 nativeQuery라는 속성을 이용하여

JPQL로 작성한 것인지 SQL로 작성한 것인지를 구분할 수 있다.

  • nativeQuery = true → SQL
  • nativeQuery = false (default) → JPQL

[JPA] @Query, 직접 쿼리 작성

 

 

 

@Query(value = "SELECT r.registry_id FROM Registry r LEFT JOIN Comment ON r.registry_id = comment.registry_id WHERE Comment.registry_id is null Limit 10;", nativeQuery = true)
List<Long> findTop10By();

 

 

위에서 최대 10개의 id값을 가지고 오니

ServiceImpl에서는 해당 id에 맞는 Resigtry db를 가져오는 코드만 작성하면 끝난다.

public List<Optional<Registry>> needComments() {
    List<Long> temp = commentRepository.findTop10By();
    List<Optional<Registry>> result = new ArrayList<>();
    for (int i = 0; i < temp.toArray().length; i++) {
        result.add(registryRepository.findById(temp.get(i)));
    }
    return result;
}

 

 

쿼리문을 활용해 db를 가지고 오니 코드가 훨씬 간결해졌다.

 

 

[pr - [refactoring] 이 글도 공감해주세요 ]

 

 

728x90

'TIL' 카테고리의 다른 글

채팅 재연결  (0) 2022.12.28
프로젝트 - 연관관계 매핑 끝  (0) 2022.12.20
findById vs getReferenceById  (0) 2022.12.12
가설  (0) 2022.12.10
스터디 요약  (0) 2022.12.04