게시글에 좋아요 기능을 추가했다.
한 회원이 여러 게시글에 좋아요
를 추가할 수 있다.
반대로
한 게시글은 여러 회원들로부터 좋아요
를 받을 수 있다.
즉, 게시글의 필드에 좋아요를 추가한다면 회원과 게시글 사이에 다대다 N:M관계가 생긴다.
따라서 M:N을 회원과 게시글 사이에 엔티티를 추가함으로서 일대다 + 다대일로 풀어낼 필요가 있다.
이유는 ORM에서 자동으로 적용할 수 있는 JoinTable을 사용할 수도 있지만 실무에서 절대 사용하지 않는다고 한다. 그 사이에 다른 로직이나 필드를 추가할 수 없기 때문이다.
결론은 Like
라는 엔티티를 새로 만들었다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "Likes")
public class Like {
@Id @GeneratedValue
@Column(name = "like_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "board_id", nullable = false)
private Board board;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
@Builder
private Like(Board board, Member member) {
this.board = board;
this.member = member;
}
public static Like createLike(Board board, Member member) {
return Like.builder()
.board(board)
.member(member)
.build();
}
}
기본적인 틀은 다음과 같다.
참고로 MySQL을 사용중인데 테이블 이름을 그대로 Like
로 사용할 경우 SQL이 꼬이게 된다. SQL 문법에 like가 존재하기 때문이다. 그래서 Likes
로 테이블명을 바꿔줬다.
그 이후로 Spring Data JPA를 사용하여 repository를 만들었다.
처음 사용한 서비스 로직은 다음과 같다.
public boolean addOrDeleteLike(Board board, Member member) {
//좋아요가 이미 존재하는 경우
if (likeRepository.existsLikeByBoardAndMember(board, member)) {
likeRepository.deleteByBoardAndMember(board, member);
return false;
} else { // 좋아요를 새로 추가하는 경우
Like newLike = Like.createLike(board, member);
likeRepository.save(newLike);
return true;
}
}
기능은 제대로 동작하지만 좋아요를 취소하는 과정에서 SQL문이 불필요하게 한번 더 사용됐다.
좋아요를 추가하는 것은 내가 예상한대로 동작했다.
메서드 이름으로 쿼리를 생성할 때 exists를 사용하면 Spring data JPA에서
limit
을 이용해 최적화를 해준다.
하지만 좋아요 삭제 쿼리문을 보자.
갑자기 board와 member로 가져오더니
PK id로 삭제를 수행한다.
실제로 보기 전까지는 생각치도 않았지만, 당연하게도 Like의 PK는 내가 따로 설정한 Long 타입의 id이다. 그러다보니 원치않은 과정으로 쿼리를 두번 날려 삭제를 수행한다.
그래서 서비스 로직을 다음과 같이 변경했다.
public boolean addOrDeleteLikeV2(Board board, Member member) {
Like like = likeRepository.findTop1ByBoardAndMember(board, member)
.orElse(null);
if (like == null) {
Like newLike = Like.createLike(board, member);
likeRepository.save(newLike);
return true;
}
else{
likeRepository.delete(like);
return false;
}
}
좋아요 추가는 이전과 같이 동작한다.
그리고 이제 중간에 탐색하는 과정 없이 바로 삭제할 수 있다.
스프링 데이터 JPA에서 exists 문법을 사용할 때 어차피 select, limit을 조합하여 boolean값을 반환한다. 따라서 좋아요 기능을 추가/삭제를 DB에서 찾아 동적으로 수행하고자 할 때, exists 문법 대신 select, limit으로 직접 엔티티를 가져왔다. 이유는 Delete를 수행할 때 불필요한 탐색 쿼리가 한번 더 나가는 것을 막기 위해서이다.