좋아요 기능을 구현하기 위해서 필요한 클래스들은 다음과 같다
LikesController 사용자의 입력을 처리하고 Service에 구현되어 있는 메서드들(비즈니스 로직)을 호출
LikesDto Controller를 통해서 받아온 사용자의 입력 값을 실제 DB에 넣기 위해, 사용자의 입력 값을 잠시 넣어두는 상자 역할
LikesEntity DB table 역할
LikesRepository Spring Data JPA 확장. DB와 통신하여 CRUD 작업 수행
LikesService 비즈니스 로직 구현
.
로그인 기능, 게시판 기능과 다르게
좋아요 기능을 구현하기 위해선 다른 repository인 BoardRepository와 UserRepository를 참조해야 한다
사용자가 특정 게시물에 좋아요를 누르면,
1. 해당 사용자가 user 테이블에 존재하는지
2. 해당 게시물이 board 테이블에 존재하는지
확인해야 하기 때문이다
package com.example.demo.Entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
// mySql like 와 이름이 겹치므로 likes 사용
@Entity
@Getter
@Setter
public class Likes {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "like_id")
private Long likeId;
// 게시판과 좋아요는 1:N 관계 (한 게시판에 좋아요 여러 개 가능하니까)
@ManyToOne(targetEntity = Board.class)
// 좋아요 테이블에서의 컬럼명 (참조하는 곳에서 이 이름으로 쓰겠다)
@JoinColumn(name = "board_id")
private Board board;
@ManyToOne(targetEntity = User.class)
@JoinColumn(name = "user_id")
private User user;
}
@ManyToOne
게시판과 좋아요는 1 : N 관계이므로 @ManyToOne 을 써준다
@JoinColumn(name = " ")
"여기"에 좋아요 테이블에서의 컬럼명을 써준다
private Board board;
private User user;
해당 테이블의 PK를 가져온다
package com.example.demo.Dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class LikesDto {
private Long userId;
private Long boardId;
}
package com.example.demo.Repository;
import com.example.demo.Entity.Board;
import com.example.demo.Entity.Likes;
import com.example.demo.Entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
/*
update, delete 처럼 트랜젝션이 깨질 수 있는 기능의 경우,
@Transactional 써줘야 함
지금까지는 find 만 했었기 때문에 문제 생길일이 없었음
이번 좋아요 기능 구현에서는, 삭제 기능이 쓰였음
user, board 레포지토리도 가져와서 있는지 확인하고, 그걸 이용했으므로,
이번엔 @Transactional 써줘야 함
*/
@Transactional
public interface LikesRepository extends JpaRepository<Likes, Long> {
// 해당하는 user, board 가 실제로 있는지 확인하기 위해서
Optional<Object> findByUserAndBoard(User user, Board board);
void deleteByUserAndBoard(User user, Board board);
}
@Transactional
로그인 기능, 게시글 기능과 다르게
좋아요 기능은 다른 레포지토리를 참조하므로 @Transactional 을 써줘야 한다
Optional
package com.example.demo.Service;
import com.example.demo.Dto.LikesDto;
import com.example.demo.Entity.Board;
import com.example.demo.Entity.Likes;
import com.example.demo.Entity.User;
import com.example.demo.Repository.BoardRepository;
import com.example.demo.Repository.LikesRepository;
import com.example.demo.Repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
/*
<@RequiredArgsConstructor 써주는 이유>
객체가 3개니까, this. 다 해주려면 코드 길어짐
이거 쓰면, 생성자를 자동으로 주입해주고 서버스와 레포지토리를 의존성 있게 연결해줌
이거 안썼으면,
public BoardService (BoardRepository boardRepository) {
this.boardRepository = boardRepository;
}
이 생성자를 Likes,User, Board 3개에 대해 다 만들어줬어야 함
*/
@RequiredArgsConstructor
public class LikesService {
private final LikesRepository likesRepository;
private final UserRepository userRepository;
private final BoardRepository boardRepository;
// 좋아요 등록
public void createLike(LikesDto likeDto) {
// Optional 로 선언해줬었기 때문에, orElseThrow 이렇게 NullPointer 날려줘야 함
// 없으면, NullPointerException 이런거 이용해서 "게시판이 없습니다" 이런 오류 알림창 띄우는 것 같은 기능을 구현할 수 있음
User user = userRepository.findById(likeDto.getUserId()).orElseThrow(NullPointerException::new);
Board board = boardRepository.findById(likeDto.getBoardId()).orElseThrow(NullPointerException::new);
// 해당 사용자가 해당 게시물에 이미 좋아요 눌렀었는지 확인
if (likesRepository.findByUserAndBoard(user,board).isEmpty()) {
Likes newLike = new Likes();
newLike.setUser(user);
newLike.setBoard(board);
likesRepository.save(newLike);
}
}
// 좋아요 삭제
public void deleteLike(LikesDto likeDto) {
User user = userRepository.findById(likeDto.getUserId()).orElseThrow(NullPointerException::new);
Board board = boardRepository.findById(likeDto.getBoardId()).orElseThrow(NullPointerException::new);
if (likesRepository.findByUserAndBoard(user, board).isPresent()) {
likesRepository.deleteByUserAndBoard(user, board);
}
}
}
@RequiredArgsConstructor
이거 사용하면, 생성자를 자동으로 주입해주고 서비스와 레포지토리를 의존성 있게 연결해줌
NullPointerException
레포지토리에서 Optional 로 선언해줬기 때문에,
Dto 에 담겨있는 user 또는 board 가 테이블에 존재하지 않는 경우, NullPointerException 을 날려야 한다
NullPointerException 를 이용하면 원하는 예외처리를 해줄 수 있다
package com.example.demo.Controller;
import com.example.demo.Dto.LikesDto;
import com.example.demo.Service.LikesService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor // 컨트롤러와 서비스의 의존성을 주입해주기 위해서
@RequestMapping("/api/likes")
public class LikesController {
private final LikesService likeService;
// 좋아요 등록
@PostMapping("/")
public ResponseEntity<String> insert(@RequestBody LikesDto likeDto) {
likeService.createLike(likeDto);
// 좋아요 기능 작동 잘 됐는지 리턴 받아야 하니까
return ResponseEntity.status(HttpStatus.CREATED).body("좋아요 등록 성공");
}
// 좋아요 취소
// 위에는 Post, 이거는 Delete 이므로 똑같은 주소 ("/") 사용해도 됨
// 단, 다른 방식인 경우 같은 주소 사용하면 안됨
@DeleteMapping("/")
// delete 는 반환 값 없으므로 void
public ResponseEntity<Void> delete(@RequestBody LikesDto likeDto) {
likeService.deleteLike(likeDto);
return ResponseEntity.noContent().build();
}
}
@PostMapping("/")
@DeleteMapping("/")
두 개가 같은 주소 ("/") 를 사용하고 있다
post, delete 로 두 개가 다른 방식이므로 같은 주소에 대한 요청을 처리하는 controller를 작성할 수 있다
하지만 같은 방식인 경우, 중복되게 같은 주소를 사용하면 안된다
| [스프링 실습] 게시판 구현 (0) | 2023.12.01 |
|---|---|
| [스프링 실습] 로그인 구현 (0) | 2023.11.25 |