아래 깃허브 링크에 프로젝트를 올려 두었다.
https://github.com/sparta-newsfeed
프로젝트 개요
https://teamsparta.notion.site/Spring-3-1232dc3ef5148085baffe0fab0439310
-> 인스타, 페이스북과 같이 사람들간의 게시물을 올리고 댓글 및 좋아요를 구현하는 프로젝트이다.




사용자 (Member) 중심으로 게시물, 댓글 및 친구관계를 형성하였다.
사용자는 여러개의 게시물, 여러개의 댓글, 여러개의 친구를 가질 수 있으며, 회원 탈퇴 테이블을 두어 다시 회원이 동일한 이메일로 등록 하려고 하면 방지하는 기능으로 구현하였다.
=> 글로 적어보니 그렇게 많은 부분을 구현한 것 같지 못해 팀원 분들께 죄송한 느낌이 있다.

=> 구조를 살펴보면 위와 같이 구현하였다. article과 comment 이 지금 다른 패키지에 존재하지만 우리팀의 튜터님의 피드백으로는 이 두 패키지를 합치는게 좀다 올바르다고 피드백을 해주셨다.
이유 : comment 가 article 에 종속적이라고 생각하심, 만약 comment 가 article 뿐만 아니라 image, member등에도 적용을 할 때 위와 같이 comment를 분리하는 게 맞다고 하셨다.
package com.sparta.spartanewsfeed.domain.comment.entity;
// import 생략
@Getter
@Builder
@Entity
@Table
@AllArgsConstructor
@NoArgsConstructor
public class Comment extends Timestamp {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String body;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
@OnDelete(action = OnDeleteAction.CASCADE)
private Article article;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
@OnDelete(action = OnDeleteAction.CASCADE)
private Member author;
//양 방향 제거
//@OneToMany(mappedBy = "comment", cascade = //CascadeType.REMOVE, orphanRemoval = true)
//@JsonIgnore // 순환 참조 방지
//private List<CommentLike> likes;
public void update(String body) {
this.body = body;
}
}
package com.sparta.spartanewsfeed.domain.comment.entity;
// import 생략
@Getter
@Builder
@Entity
@Table
@AllArgsConstructor
@NoArgsConstructor
public class CommentLike {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "comment_id")
private Comment comment;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
}
Comment 와 CommentLike 는 지금 양방향을 가지고 있다. 하지만 이 경우 외래키의 주인인 CommentLike 가 Comment 를 가지고 갈 수 있지만 Comment 의 경우는 CommentLike 를 알 필요가 없다고 피드백이 옴
=> 따라서 위 주석과 같이 해당 부분을 삭제할 수 있다.
if ("popular".equals(sortBy)) {
comments = commentRepository.findAllByArticleIdOrderByLikesCount(articleId, pageable); // 인기순 정렬
}
현재는 위와 같이 CommentService 에서 comment를 통해 commentLike 를 조회 하고 있기 때문에 위와 같이 주석을 하면 오류가 난다.
따라서 이 코드를 수정할 필요가 있다.
=> 그러면 위에서 commentRepository 대신에 commentLikeRepository 를 이용하는 것은 어떨까?
=> 적용해보자!!!
@Transactional
public Page<CommentResponseDto> getComments(Long articleId, int page, int size, String sortBy,
String authorization) {
if (!articleRepository.existsById(articleId)) {
throw new NotFoundEntityException(NOT_FOUND_ARTICLE);
}
// Pageable 객체 생성 (기본값: 최신순)
Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending());
Page<Comment> comments;
if ("popular".equals(sortBy)) {
comments = commentLikeRepository.findCommentsByArticleOrderByLikes(articleId, pageable);
//comments = commentRepository.findAllByArticleIdOrderByLikesCount(articleId, pageable); // 인기순 정렬
} else {
comments = commentRepository.findAllByArticleId(pageable, articleId); // 기본 최신순 정렬
}
Member author = findByEmail(authorization);
return comments.map(comment -> {
boolean isLiked = commentLikeRepository.existsByCommentIdAndMemberId(comment.getId(), author.getId());
return CommentResponseDto.of(comment, isLiked);
}
);
}
위와 같이 commentRepository 대신 commentLikeRepository 로 부터 불러오게 된다.
(현재 CommentLike 가 Comment 를 가리키는 단방향으로 가리키고 있기 때문에 위와 같이 불러 올 수 있다.)
@Query("SELECT c FROM Comment c " +
"LEFT JOIN CommentLike cl ON cl.comment.id = c.id " +
"WHERE c.article.id = :articleId " +
"GROUP BY c.id " +
"ORDER BY COUNT(cl.id) DESC")
Page<Comment> findCommentsByArticleOrderByLikes(@Param("articleId") Long articleId, Pageable pageable);
JPQL 을 이용해서 위와 같이 Comment 테이블과, CommentLike 테이블을 left 조인하였고 조건에 맞게 정렬하도록 하였다.
@Param은 쿼리에서 테이블에 매핑될 칼럼의 이름이 된다.

쿼리 파라미터를 통해 popular 를 주게 되면 위와 같이 true 가 있는
(현재는 좋아요 개수 1이 최대인 상태) 댓글이 먼저 출력되고 이후에 false 댓글이 출력되는 것을 알 수 있다.

그렇지 않은 경우는 디폴트로 최신순으로 출력되는 것을 확인하였다.
=> 반드시 양방향 관계여야 할 것이라고 생각 했던 것이 단방향 만으로도 구현이 되어서 되게 신기했던 것 같다. 이와 같이 JOIN 과 같이 다른 방법을 통해서 쿼리 수를 줄이면서 단방향을 되도록 유지 하는 것이 좋은 방법일 것 같다.

=> 그리고 이제와서 안 사실이지만 위 테이블에 article_id 가 있었어도 JPQL 없이 가능할 것 같다. (물론 성능 쿼리 수 개선을 위해 사용 할 수도 있을 듯)