게시판 프로젝트를 진행하던 중 좋아요 순으로 게시글을 정렬하려고 했으나, 막히게 되었고 그 해결방법으로 @Formula
라는 것을 알게 되어서 기록하고자 한다.
게시글(Article) 엔티티는 다음과 같다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {
@Id @GeneratedValue
@Column(name = "article_id")
private Long id;
private String title;
private String content;
@Enumerated(value = EnumType.STRING)
private ArticleCategory category;
@Column(name = "post_date")
private LocalDateTime postDate;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
private List<ArticleTag> articleTags = new ArrayList<>();
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
private List<ArticleImage> images = new ArrayList<>();
@OneToMany(mappedBy = "article")
private List<LikeArticle> likeArticles = new ArrayList<>();
...
}
게시글 좋아요와 관련해서 매핑은 다음과 같이 되어있다.
article
: like_article
(일대다)
like_article
: users
(다대일)
게시글 좋아요 순으로 정렬하기 위해 처음에는 다음과 같이 코드를 작성했다.
public interface ArticleRepository extends JpaRepository<Article, Long> {
@Query(value = "select a from Article a join a.likeArticles la order by la.size desc")
Page<Article> findAllOrderByLike(Pageable pageable);
}
그런데 다음과 같은 에러가 발생했다.
aused by: java.lang.IllegalArgumentException: org.hibernate.query.SemanticException: Could not resolve attribute 'size' of 'com.example.mypetlife.entity.article.LikeArticle'
Caused by: org.hibernate.query.SemanticException: Could not resolve attribute 'size' of 'com.example.mypetlife.entity.article.LikeArticle'
아마 la.size
를 기준으로 정렬을 해야하는데, la.size
가 like_article의 칼럼이 아니여서 발생한 오류인 거 같다. 즉 정렬을 하기 위해선 해당 정렬 기준이 특정 테이블의 칼럼이어야 하는 거 같다.
'게시글 좋아요 수를 Article 엔티티 내의 필드로 관리하지 않고, LikeArticle과 같이 다른 엔티티와 연관관계를 맺도록 하였는데, 어떻게 좋아요 수를 Article의 필드로 갖게하지..?' 라고 고민하다가, 구글링을 통해 @Formula
라는 것이 있다는 것을 알게 되었다!
@Formula
는 JPA의 명세는 아니지만 Hiberante에서 제공하는 어노테이션이다. 이를 사용하면 가상 컬럼을 매핑할 수 있다. 가상컬럼이라는 것은 JPA 상에는 존재하지만 DB에는 생성되지 않는 칼럼을 말한다. 이때 주의해야 할 점은 JPQL이 아닌 네이티브 SQL을 사용해야 한다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {
@Id @GeneratedValue
@Column(name = "article_id")
private Long id;
...
@OneToMany(mappedBy = "article")
private List<LikeArticle> likeArticles = new ArrayList<>();
// 좋아요 갯수를 의미하는 가상 칼럼
@Formula("(select count(*) from like_article where like_article.article_id = article_id)")
private int likeCount;
...
}
위와 같이 Article 엔티티에 @Formula
를 사용하여 가상 칼럼인 likeCount를 정의해주었다. 가상 칼럼이기 때문에 DB에서는 like_count라는 칼럼을 확인할 수 없다.
그리고 이 likeCount는 select count(*) from like_article where like_article.article_id = article_id
쿼리를 통해, 해당 article과 연관 관계를 맺고 있는 like_article의 수, 즉 게시글 좋아요 수를 가진다.
public interface ArticleRepository extends JpaRepository<Article, Long> {
@Query(value = "select a from Article a order by a.likeCount desc, a.postDate desc")
Page<Article> findAllOrderByLike(Pageable pageable);
}
그리고 JPA의 @Query
를 통해 쿼리를 생성하는데 이때 a.likeCount
를 기준으로 정렬한다. a.likeCount
는 JPA 상에 존재하는 article의 칼럼이기 때문에 정상적으로 동작한다!
Reference