게시글에 좋아요 기능을 구현했다. 구현하고 보니, 게시글(Board) 엔티티와 좋아요(Likes) 엔티티가 양방향으로 매핑되어 있는데, Likes 객체만 생성을 하고 Board 쪽에 Likes 객체를 넣어주는 작업을 하지 않았다는 것을 발견했다. Likes 객체만 생성했을 뿐, Board에는 Likes 값을 넣지 않았는데도 Board를 리턴하면 LikesList에 Likes 값이 제대로 들어가있다. 이유가 뭘까? 지금부터 하나씩 확인해보자!
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Likes {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
@ManyToOne
@JoinColumn(name = "board_id")
private Board board;
// ...
}
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Board extends Timestamped {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// ...
@OneToMany(mappedBy = "board", cascade = CascadeType.REMOVE)
private List<Likes> likesList = new ArrayList<>();
// ...
}
@Service
@RequiredArgsConstructor
public class LikesService {
private final LikesRepository likesRepository;
private final BoardRepository boardRepository;
private final CommentRepository commentRepository;
@Transactional
public ResponseEntity<BoardResponseDto> likePost(Long id, User user) {
Optional<Board> board = boardRepository.findById(id); // 1
if (board.isEmpty()) {
throw new RestApiException(ErrorType.NOT_FOUND_WRITING);
}
// 이전에 좋아요 누른 적 있는지 확인
Optional<Likes> found = likesRepository.findByBoardAndUser(board.get(), user);
if (found.isEmpty()) { // 좋아요 누른적 없음
Likes likes = Likes.of(board.get(), user); // 2
likesRepository.save(likes); // 3
} else { // 좋아요 누른 적 있음
likesRepository.delete(found.get()); // 좋아요 눌렀던 정보를 지운다.
likesRepository.flush();
}
return ResponseEntity.ok(BoardResponseDto.from(board.get())); // 4
}
}
/* Likes.class */
@Builder
private Likes(Board board, Comment comment, User user) {
this.board = board;
this.comment = comment;
this.user = user;
}
public static Likes of(Board board, User user) {
Likes likes = Likes.builder()
.board(board)
.user(user)
.build();
// board.getLikesList().add(likes); <- 연관관계 편의 메서드 적용 부분
return likes;
}
DB에서 선택한 게시글 번호로 Board를 찾는다.
Like에 Board와 User를 넣어 객체를 만든다.
Like를 save한다.
Board를 dto로 리턴한다.
Likes class 에서 주석처리한 board.getLikesList().add(likes)
부분이 양방향 매핑에서 양쪽 객체에 값을 제대로 넣어주기 위한 연관관계 편의 메서드 적용 부분이다.
나는 처음에 양쪽 객체에 값을 넣어야한다는 생각을 못하고 해당 부분을 작성하지 않았었다.
그래도 API 요청을 했을 때 게시글에 좋아요 값이 정상적으로 잘 올라갔고 DB에도 잘 저장되었다.
그런데 잠깐 여기서 천천히 하나씩 다시 확인해보자.
board를 맨 처음에 repository에서 불러온 후에, Likes 객체를 만들 때 board를 넣어 만들고 save를 했다.
Likes를 저장만 했을 뿐, 처음 가져온 board에는 LikesList가 비어 있을 것이다.
그런데, Board를 리턴하면 LikesList에 아까 생성한 Likes 객체가 들어있다. 어떻게 된걸까?
우선 @Transactional 과 연관관계 편의 메서드 부분을 적용하면서 결과를 확인해보자.
case 1-1) likePost 메서드에 @Transactional이 걸려 있는 경우
→ (O) 리턴 시, Board의 LikeList에 2번에서 만든 Like 객체 하나가 들어있다.
case 1-2) likePost 메서드에 @Transactional이 걸려있지 않은 경우
→ (O) 리턴 시, Board의 LikeList에 2번에서 만든 Like 객체 하나가 들어있다.
case 2-1) likePost 메서드에 @Transactional이 걸려 있는 경우
→ (X) 4번에서 Board의 LikeList에 2번에서 Like 객체가 두 개 들어있다…
case 2-2) likePost 메서드에 @Transactional이 걸려있지 않은 경우
→ (O) 4번 Board의 LikeList에 2번에서 만든 Like 객체 하나가 들어있다.