Spring/Spring Boot

JPA pageable 페이징 정렬, 외부 참조 엔티티 리스트 개수로 정렬할때 - @Formula

민돌v 2021. 12. 29. 13:05

문제

Spring Data JPA를 이용해서 페이지네이션-정렬을 구현하는데,

외부 참조 엔티티의 참조하는 엔티티의 수?를 이용해서 정렬을 하고싶다. (좋아요 수)

 

이게 방법을 몰라, 일단, 외부 엔티티를 참조하는 멤버변수의 이름으로 정렬을 해보니, 정렬이 되긴 되었다.

하지만, 첫번째 데이터가 한번 더 중복되서 나오는 문제가 발생

 

심지어 순서대로 중복되는 것도 아님

 

왜 중복이되서 나오지조차 감이 안잡힘

 

DomainEntity.class

@Table(name = "post")
public class Post extends Timestamped {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long idx;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    @Column
    private String img = "/img/no-pic.png";

    @Column(columnDefinition = "int default 0")
    private int view;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @JsonIgnore
    @OneToMany(mappedBy = "post", cascade=CascadeType.ALL)
    private List<PostComment> comments = new ArrayList<PostComment>();

    @JsonIgnore
    @OneToMany(mappedBy = "post", cascade=CascadeType.ALL)
    private List<PostLike> likes = new ArrayList<PostLike>();

DomainService.class

//게시글 리스트
public Page<Post> getPostList(int page, int size, String sorting){
    Pageable pageable;
    if(sorting.equals("descending")) {
        pageable = PageRequest.of(page, size, Sort.by("createdAt"));
    }
    else if(sorting.equals("likes")){
        pageable = PageRequest.of(page, size, Sort.by(sorting).descending());
    }
    else
        pageable = PageRequest.of(page, size, Sort.by(sorting).descending());

    Page<Post> postList = postrepository.findAll(pageable);

    return postList;
}

 


하고싶은것

1. 정렬 데이터 증복 에러 해결

2, 외부 참조 엔티티의 개수로 정렬하는 법 아기

 


해결방법

일단 JPA에서 쿼리를 어떻게 실행하는지 확인해 보자

 

Spring boot에서 JPA 쿼리 파리미터를 확인하는 방법은,

application.properties 에 아래 2줄을 추가해주면 터미널에서 확인할 수 있다.

logging.level.org.hibernate.SQL=debug

spring.jpa.properties.hibernate.format_sql=true

Full Scan 방식의 Offset 페이지네이션 쿼리를 이용하는 JPA



JPA&amp;nbsp; 쿼리문

 

똑같이 Mysql 쿼리문으로 해보았고 결과는 역시 똑같이 데이터가 중복되어 나왔다..

쿼리문이 잘못되었나보다,, 뭐가 잘못됬는지 알아보자

 

아 문제점은, post id와 post like 의 post id가 같으면 모든 컬럼을 불러오기 때문이였다.. 

테스트를 위해 다른 아이디를 하나 만들어 좋아요를 마구마구 눌러주었더니...

 

 

짜잔,,, 중복중복 히히

 

문제를 파악했으니 해결해보자!!


문제 해결

고민을 해보다 서브 쿼리 count 가 생각이 나서 찾아보니, JPA로 count를 구현혈려면, JPQL을 사용해야하고, 성능이 굉장히 안나온다고 한다.(모든 쿼리를 계속 호출해서)

 

그러다 @Formula어노테이션을 찾았다.

코드부터!

DomainEntity.class

//좋아요 수 count 컬럼 - 가상
    @Formula("(select count(*) from post_like l where l.post_id = id)")
    private int countOfLikes;

DomainService.class

//게시글 리스트
    public Page<Post> getPostList(int page, int size, String sorting){
        Pageable pageable;
        if(sorting.equals("descending")) {
            pageable = PageRequest.of(page, size, Sort.by("createdAt"));
        }
        else if(sorting.equals("likes")){
            pageable = PageRequest.of(page, size, Sort.by("countOfLikes").descending());
        }
        else
            pageable = PageRequest.of(page, size, Sort.by(sorting).descending());

        Page<Post> postList = postrepository.findAll(pageable);

        return postList;
    }

 

 

 

Formula 어노테이션

  1. JPA에 명세에서는 서브쿼리를 지원하지 않는다.
  2. Formula 어노테이션은 하이버네이트가 제공하는 기능이다.
  3. Formula로 만들어지는 컬럼은 가상 컬럼으로, 실제 존재하지않는 읽기전용의 컬럼으로 생각 할 수 있을 것 같다.
  4. Formula 어노테이션은 네이티브 SQL문을 사용한다!
  5. 보통 JPA에서 조건절 혹은 집계된 데이터가 필요할 때, 포뮬라 어노테이션을 사용한다고 한다.

 

 

 

깔금하게 해결

실행되는 JPA 쿼리문

 


참고

 

접근방법 - 외부 참조 엔티티 count

https://www.popit.kr/jpa-%EC%97%94%ED%84%B0%ED%8B%B0-%EC%B9%B4%EC%9A%B4%ED%8A%B8-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0/

 

JPA 엔터티 카운트 성능 개선하기 | Popit

JPA Java Persistence API 로 애그리게잇 을 구현할 때면 흔히 루트 엔터티(전역 식별성을 지니며 주체로 쓰이는 엔터티)에 연관 엔터티 컬렉션을 매핑한다. 때때로 루트 엔터티는 연관 엔터티 컬렉션

www.popit.kr

 

@Folmula 사용법

https://www-swpro-com.tistory.com/24

 

JPA의 엔티티에서의 카운팅(@Formula)

JPA를 이용해 개발하다보면 모호한 부분이 많은 것 같은데 대표적인게 JPA, Hibernate, Spring JPA인 것 같다. 여기서 정리 할 내용은 아니지만 간단히 설명하면 JPA는 기술명세, Hibernate는 인터페이스, Spr

www-swpro-com.tistory.com