1. 비지니스 파악
1. user - 좋아요 - board
user -> board (좋아요)
1:N (유저는 여러 게시글을 좋아할 수 있음)
N:1 (하나의 게시글은 여러 유저에게 좋아요 받을 수 있음)
=> N:M 관계이므로 동사 형태의 중간 테이블(love_tb)이 필요
-> love_tb(id, user_id(FK), brd_id(FK))
user - love - board
1 : N | N : 1 (N에 FK)
OneToMany | ManyToOne -> 전부 ManyToOne으로 처리2. 제약
- 한 유저가 같은 게시글에 여러 번 좋아요를 누르지 못하게 제약 조건 설정
- user_id와 board_id를 묶어 복합키 유니크 약 설정
- JPA의
@ManyToMany는 제약이 많기 때문에 사용하지 않음 - 랜덤 테이블명 등
게시글 1번으로 이동, 나는 ssar (pk는 화면에 안보여도 들고가야 update 등 써야하는 곳이 있음)
-> id title content username is_owner is_love love_count
-> 1 제목1 내용1 ssar true true 2
user - love - board
1 : N N : 1 (N에 FK)
OneToMany ManyToOne -> 그냥 ManyToOne으로 하세용2. love_tb
1. Love Table (Entity) 설계
package shop.mtcoding.blog.love;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import shop.mtcoding.blog.board.Board;
import shop.mtcoding.blog.user.User;
import java.sql.Timestamp;
@NoArgsConstructor
@Getter
@Table(
name = "love_tb",
uniqueConstraints = {
@UniqueConstraint(columnNames = {"user_id", "board_id"})
}
)
@Entity
public class Love {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@ManyToOne(fetch = FetchType.LAZY) // 항상 가지고 와야하지만(EAGER) id만으로도 조회가능하니까(LAZY)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
@CreationTimestamp
private Timestamp createdAt;
// Builder 만들기 : 생성자 만들고 @Builder 걸기
@Builder
public Love(Integer id, User user, Board board, Timestamp createdAt) {
this.id = id;
this.user = user;
this.board = board;
this.createdAt = createdAt;
}
}

2. 더미데이터 추가
insert into love_tb(board_id, user_id, created_at)
values (5, 1, now());
insert into love_tb(board_id, user_id, created_at)
values (5, 2, now());
insert into love_tb(board_id, user_id, created_at)
values (4, 1, now());3. 제약조건 위배 시 제대로 오류가 터지는 지 확인하기
- 같은 게시글에 같은 유저가 좋아요를 여러 번 했다고 상황을 가정하자.

- 에러



3. 화면과 Controller만 가지고 화면에 뿌리는 것 먼저!
- DB에는 아직 손대지 않고!!! 화면에 대한 이해가 끝나면 다음에는 DB까지 고려
- 일단 ssar이면 true → 무조건 좋아요
- 좋아요 기능 전체 X → 먼저 하트 색깔부터 제어
1. BoardResponse.DetailDTO
- isLove : 좋아요 하면 true, 좋아요를 취소하면 false
- loveCount : 게시글에 대한 좋아요 개수

2. board/detail.mustache

- 비추천 방식 (가독성이 떨어짐)

3.


4. 처음 만들어보는 것에 대해서
given 데이터를 이용하자
여러가지 중에서 하나씩 빼보고 샘플링 해보면서
기능을 분리해서 생각하자
4. 좋아요 하트 처리
1. love entity와 다름
// 나는 1번이고 5번 게시물로 갈거야
// 좋아요는 true가 나와야 됨
=> select * from love_tb where board_id=5 and user_id = 1;

근데 이 데이터를 들고 가고 싶은게 아니라 true를 들고 가고 싶은 것!

근데 love entity와 다름
true로 하면 object 배열로 받아야됨 그냥 원래 데이터 받아서 매핑 받고 처리하는게ㅔ 낫다
2. Repository
public Love findByUserIdAndBoardId(int userId, int boardId) {
Query query = em.createQuery("select lo from Love lo where lo.user.id = :userId and lo.board.id = :boardId", Love.class);
query.setParameter("userId", userId);
query.setParameter("boardId", boardId);
return (Love) query.getSingleResult(); // unique 제약조건이므로 동일한 데이터가 있을 수 없기 때문에 Single
}3. BoardService

4. BoardResponse.DetailDTO

5. 3번 게시물을 ssar로 로그인해서 들어가면


6. 5번글은 비공개이므로 더미 데이터를 잘 못 만들었음

5. 좋아요 cnt - count(*)
Respository의 메서드가 하나의 목적으로만 사용되는 것보다는 재사용 가능한 것이 좋기 때문에 count로 좋아요 수를 구하는 것보다는 List.size() 방식을 사용하자!
1. 좋아요 count하는 쿼리 먼저 짜기
SELECT count(*) FROM LOVE_TB where board_id = 5;2. LoveRepository
public int countByBoardId(int boardId) {
Query query = em.createQuery("SELECT count(lo) FROM Love lo where lo.board.id = :boardId");
query.setParameter("boardId", boardId);
Long loveCount = (Long) query.getSingleResult();
return loveCount.intValue();
}3. BoardService

4. BoardResponse.DetailDTO

5. LoveRepositoryTest
@Import(LoveRepository.class)
@DataJpaTest
public class LoveRepositoryTest {
@Autowired
private LoveRepository loveRepository;
@Test
public void findByBoardId_test() {
// given
Integer boardId = 3;
// when
Integer loveCount = loveRepository.countByBoardId(boardId);
System.out.println(loveCount);
}
}- given data인 boardId = 3인 경우

- given data인 boardId = 4인 경우

- given data인 boardId = 5인 경우

6. 좋아요 cnt - List.size()
1. LoveRepository
public List<Love> findByBoardId(int boardId) {
Query query = em.createQuery("SELECT lo FROM Love lo where lo.board.id = :boardId");
query.setParameter("boardId", boardId);
List<Love> loves = query.getResultList();
return loves;
}2. BoardService
public BoardResponse.DetailDTO 글상세보기(int id, Integer userId) {
// Board board = boardRepository.findById(id); // LAZY 로딩이므로 Board만 조회해서 board 정보 밖에 없다.
// board.getUser().getEmail(); // 원래 null인데 lazy로딩이 발동해서 해당 유저 id로 select가 발동해서 값을 넣어준다. -> 비효율적이므로 안쓴다!
Board board = boardRepository.findByIdJoinUser(id);
Love love = loveRepository.findByUserIdAndBoardId(userId, id); // (userId, boardId)
Boolean isLove = love == null ? false : true;
List<Love> loves = loveRepository.findByBoardId(id);
BoardResponse.DetailDTO detailDTO = new BoardResponse.DetailDTO(board, userId, isLove, loves.size());
return detailDTO;
}3. BoardResponse.DetailDTO
package shop.mtcoding.blog.board;
import lombok.Data;
import java.sql.Timestamp;
public class BoardResponse {
@Data
public static class DetailDTO {
private Integer id;
private String title;
private String content;
private Boolean isPublic;
private Boolean isOwner; // 값이 안 들어갈 경우: Boolean - null / boolean - 0
private Boolean isLove;
private Integer loveCount;
private String username; // User 객체를 다 들고 갈 필요X
private Timestamp createdAt;
// model에 있는 것을 옮기는 것
// 깊은 복사 : 객체를 그대로 가져와서 getId 등으로 넣는게 낫다!
public DetailDTO(Board board, Integer sessionUserId, Boolean isLove, Integer loveCount) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.isPublic = board.getIsPublic();
this.isOwner = sessionUserId == board.getUser().getId();
this.username = board.getUser().getUsername();
this.createdAt = board.getCreatedAt();
this.isLove = isLove;
this.loveCount = loveCount;
}
}
}
결과



Share article