1. 좋아요 테이블 생성
- Like
@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)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
@CreationTimestamp
private Timestamp createdAt;
@Builder
public Love(Integer id, User user, Board board, Timestamp createdAt) {
this.id = id;
this.user = user;
this.board = board;
this.createdAt = createdAt;
}
}
- data.sql
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 (4, 2, now());
insert into love_tb(board_id, user_id, created_at)
values (4, 1, now());
2. 좋아요 집계
- LikeRepository
- findByBoardId : 글에 걸린 좋아요 개수
- findByUserIdAndBoardId : 로그인 한 유저가 해당 글에 좋아요를 눌렀는지 안눌렀는지 확인
- save : 좋아요 추가 및 객체 반환 (좋아요 개수 갱신용)
- deleteById : 좋아요 id를 이용하여 좋아요 삭제
- findById : 좋아요 id를 이용하여 love 객체 반환 (좋아요 개수 갱신용)
public Long findByBoardId(int boardId) {
Query query = em.createQuery("select count(lo) from Love lo where lo.board.id = :boardId");
query.setParameter("boardId", boardId);
Long count = (Long) query.getSingleResult();
return count;
}
public Love findByUserIdAndBoardId(Integer userId, Integer 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);
try {
return (Love) query.getSingleResult();
} catch (Exception e) {
return null;
}
}
public Love save(Love love) {
em.persist(love);
return love;
}
public void deleteById(Integer id) {
em.createQuery("delete from Love lo where lo.id = :id")
.setParameter("id", id)
.executeUpdate();
}
public Love findById(Integer id) {
return em.find(Love.class, id);
}
- BoardService
- 게시글의 상세보기에 출력 → BoardResponse에 좋아요 저장
public BoardResponse.DetailDTO 글상세보기(Integer id, Integer userId) {
Board board = boardRepository.findByIdJoinUser(id);
Love love = loveRepository.findByUserIdAndBoardId(userId, id);
Long loveCount = loveRepository.findByBoardId(id);
Integer loveId = love == null ? null : love.getId();
Boolean isLove = love == null ? false : true;
BoardResponse.DetailDTO detailDTO = new BoardResponse.DetailDTO(board, userId, isLove, loveCount.intValue(), loveId);
return detailDTO;
}
- BoardResponse
- isLove : 좋아요를 누른 상태 표시
- loveCount : 해당 글의 좋아요 개수 표시
- loveId : 좋아요 추가 / 취소를 위한 Id 참조
// 상세보기 화면에 필요한 데이터
@Data
public static class DetailDTO {
private Integer id;
private String title;
private String content;
private Boolean isPublic;
private Boolean isOwner;
private Boolean isLove;
private Integer loveCount;
private String username;
private Timestamp createdAt;
private Integer loveId;
public DetailDTO(Board board, Integer sessionUserId, Boolean isLove, Integer loveCount, Integer loveId) {
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;
this.loveId = loveId;
}
3. 좋아요 DTO
- LoveRequest
public static class SaveDTO {
private Integer boardId;
public Love toEntity(User sessionUser) {
return Love.builder()
.board(Board.builder().id(boardId).build())
.user(sessionUser)
.build();
}
}
- LoveResponse
@Data
public static class SaveDTO {
private Integer loveId;
private Integer loveCount;
public SaveDTO(Integer loveId, Integer loveCount) {
this.loveId = loveId;
this.loveCount = loveCount;
}
}
@Data
public static class DeleteDTO {
private Integer loveCount;
public DeleteDTO(Integer loveCount) {
this.loveCount = loveCount;
}
}
4. 좋아요 JS
{{> layout/header}}
<input type="hidden" id="boardId" value="{{model.id}}">
<div class="container p-5">
<!-- 수정삭제버튼 -->
{{#model.isOwner}}
<div class="d-flex justify-content-end">
<a href="/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{model.content}}
</div>
</div>
<!-- AJAX 좋아요 영역 -->
<div class="my-3 d-flex align-items-center">
{{#model.isLove}}
<i id="loveIcon" class="fa fa-heart" style="font-size:20px; color:red"
onclick="deleteLove({{model.loveId}})"></i>
{{/model.isLove}}
{{^model.isLove}}
<i id="loveIcon" class="fa fa-heart" style="font-size:20px; color:black"
onclick="saveLove()"></i>
{{/model.isLove}}
<span class="ms-1"><b id="loveCount">{{model.loveCount}}</b>명이 이 글을 좋아합니다</span>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form action="/reply/save" method="post">
<textarea class="form-control" rows="2" name="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
<!-- 댓글아이템 -->
{{#model.replies}}
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">{{user.username}}</div>
<div>{{content}}</div>
</div>
<form action="/reply/{{id}}/delete" method="post">
<button class="btn">🗑</button>
</form>
</div>
{{/model.replies}}
</div>
</div>
</div>
<script>
// setInterval(()=>{
// location.reload();
// }, 1000*10);
let boardId = document.querySelector("#boardId").value;
async function saveLove() {
let requestBody = {boardId: boardId};
let response = await fetch(`/love`, {
method: "POST",
body: JSON.stringify(requestBody),
headers: {"Content-Type": "application/json"}
});
let responseBody = await response.json(); // response.text() (X)
console.log(responseBody);
// DOM 업데이트
let icon = document.querySelector('#loveIcon');
let loveCount = document.querySelector('#loveCount');
icon.style.color = 'red';
icon.setAttribute('onclick', `deleteLove(${responseBody.body.loveId})`);
loveCount.innerHTML = responseBody.body.loveCount;
}
async function deleteLove(loveId) {
let response = await fetch(`/love/${loveId}`, {
method: "DELETE"
});
let responseBody = await response.json(); // response.text() (X)
console.log(responseBody);
// DOM 업데이트
let icon = document.querySelector('#loveIcon');
let loveCount = document.querySelector('#loveCount');
icon.style.color = 'black';
icon.setAttribute('onclick', `saveLove()`);
loveCount.innerHTML = responseBody.body.loveCount;
}
</script>
{{> layout/footer}}
1. 화면의 변수를 매개변수로 전달
- 함수마다 id를 각각 받아야 함

2. input 태그에 id 숨겨놓기
- boardId의 경우 한 게시글 내에서 좋아요를 누르므로 변하지 않음
- 미리 값을 받은 후 document.querySelector(”#boardId”).value로 값을 땡겨 씀


3. dataset 활용
- 태그 내부에 data-key=”value” 형태의 dataset 추가
- document.querySelector(”#id”).dataset.key를 사용하여 해당 태그 내 dataset 사용 가능


5. 결과
- 좋아요가 걸려있지 않은 글 : 하트가 검정색으로 표시됨
- 누르면 좋아요 개수가 올라감

- 좋아요가 걸린 글 : 하트가 빨간색으로 표시됨
- 누르면 좋아요 개수가 내려감

Share article