기획은 위에 보이는 게시글 화면과 같습니다.(내용은 무시해주시면 됩니다..ㅎㅎ) 이 글은 위의 기능을 구현했던 과정을 작성한 글입니다.
우선 좋아요 버튼을 누르게 되면 무슨일이 일어나게 되는지 생각해보았습니다.
1. 좋아요를 누른 User가 다시 같은 글에 대해 좋아요를 누르게 된다면 좋아요 취소가 된다.
2. 게시글에 대한 좋아요 count + 1 (중복 좋아요 불가)
정도였습니다.
이 2가지 기능을 구현해보도록 하겠습니다.
자세한 코드는 chu-chu github에서도 확인하실 수 있습니다.
이글과 관련없는 코드는 생략하였습니다.
table의 관계는 아래와 같습니다.
Heart는 게시글과 멤버의 필드를 가지고 좋아요에 대한 정보를 저장합니다.
Board.java
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@DynamicInsert
@DynamicUpdate
@Table(name = "board")
public class Board extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "board_id")
private Long id;
@Column(name = "title", nullable = false)
private String title;
@Column(name = "content", nullable = false)
private String content;
@ColumnDefault("0")
@Column(name = "view_count",nullable = false)
private Integer viewCount;
...
}
Member.java
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "member")
public class Member extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
@Column(name = "email", nullable = false)
private String email;
@Column(name = "nick_name", nullable = false)
private String nickName;
@Column(name = "password", nullable = false)
private String password;
@Enumerated(EnumType.STRING)
@Column(name = "level", nullable = false)
private Level level;
@Enumerated(EnumType.STRING)
@Column(name = "user_role")
private UserRole userRole;
...
}
Heart.java
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "heart")
public class Heart {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "heart_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "board_id")
private Board board;
}
우선 좋아요를 하는 기능과 좋아요를 취소하는 기능을 분리해야하는지, 분리하지않아도되는지 고민하였습니다. 결론을 먼저 말씀드리면 분리를 하기로하였습니다.
이유는 아래와 같습니다.
이미 프론트쪽에서는 글의 상태를 알고있는 상태였습니다. 이글이 유저에 대해 좋아요가 되어있는지, 좋아요가 안되어있는지를 알수 있는 상태였고 분기를 태워서 요청만 보내게 되면되서 간단하게 구현이 가능했습니다.
이 이유 뿐만 아니라 만약 기능을 분리하지 않은채로 요청을 하게되면 백엔드 쪽에서 로직을 구성하여야하는데 그렇게 되면 Post요청을 통해 insert, delete가 모두 일어나기 때문에 가독성 측면에서도 떨어지고 restful하지 않은 방법이라고 생각했습니다.
controller는 아래와 같이 구성했습니다.
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/chuchu/heart")
public class HeartController {
private final HeartService heartService;
@PostMapping
public ResponseResult<?> insert(@RequestBody @Valid HeartRequestDTO heartRequestDTO) {
heartService.insert(heartRequestDTO);
return success(null);
}
@DeleteMapping
public ResponseResult<?> delete(@RequestBody @Valid HeartRequestDTO heartRequestDTO) {
heartService.delete(heartRequestDTO);
return success(null);
}
}
insert 메서드는 좋아요를 하는 기능입니다.
delete 메서드는 좋아요를 취소하는 기능입니다.
HeartRequestDTO.java
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class HeartRequestDTO {
private Long memberId;
private Long boardId;
public HeartRequestDTO(Long memberId, Long boardId) {
this.memberId = memberId;
this.boardId = boardId;
}
}
좋아요를 한 member의 ID와 게시글의 ID를 전달받습니다.
HeartService.java
@Service
@RequiredArgsConstructor
public class HeartService {
private final HeartRepository heartRepository;
private final MemberRepository memberRepository;
private final BoardRepository boardRepository;
@Transactional
public void insert(HeartRequestDTO heartRequestDTO) throws Exception {
Member member = memberRepository.findById(heartRequestDTO.getMemberId())
.orElseThrow(() -> new NotFoundException("Could not found member id : " + heartRequestDTO.getMemberId()));
Board board = boardRepository.findById(heartRequestDTO.getBoardId())
.orElseThrow(() -> new NotFoundException("Could not found board id : " + heartRequestDTO.getBoardId()));
// 이미 좋아요되어있으면 에러 반환
if (heartRepository.findByMemberAndBoard(member, board).isPresent()){
//TODO 409에러로 변경
throw new Exception();
}
Heart heart = Heart.builder()
.board(board)
.member(member)
.build();
heartRepository.save(heart);
}
@Transactional
public void delete(HeartRequestDTO heartRequestDTO) {
Member member = memberRepository.findById(heartRequestDTO.getMemberId())
.orElseThrow(() -> new NotFoundException("Could not found member id : " + heartRequestDTO.getMemberId()));
Board board = boardRepository.findById(heartRequestDTO.getBoardId())
.orElseThrow(() -> new NotFoundException("Could not found board id : " + heartRequestDTO.getBoardId()));
Heart heart = heartRepository.findByMemberAndBoard(member, board)
.orElseThrow(() -> new NotFoundException("Could not found heart id"));
heartRepository.delete(heart);
}
전달받은 memberId와 boardId를 통해 member와 board를 조회하고 저장혹은 삭제를 진행합니다.
이제 아래에서는 2번째 기능인 게시글에 대한 좋아요 count + 1 (중복 좋아요 불가) 에 대해 작성해보겠습니다.
좋아요를 누르면 count가 + 1이 되고 좋아요 취소를 누른다면 count - 1이 되어야합니다.
서비스에서 구성했던 insert()
와 delete()
함수에서 아래와 같은 코드를 추가해주었습니다.
HeartService.java
boardRepository.updateCount(board,true);
2번째 매개변수가 true
라면 +1 false
라면 -1 입니다.
BoardRepositoryImpl.java
@Override
public void updateCount(Board board1, boolean b) {
if (b) {
queryFactory.update(board)
.set(board.likeCount, board.likeCount.add(1))
.where(board.eq(board1))
.execute();
} else {
queryFactory.update(board)
.set(board.likeCount, board.likeCount.subtract(1))
.where(board.eq(board1))
.execute();
}
}
좋아요 기능을 구현해보았습니다. 좋아요 기능또한 상당히 많이 쓰이는 기능이므로 기억해두면 좋을 것 같습니다.