[무한댓글 게시판] 6. 조회수 / 좋아요
조회수
게시글의 상세페이지로 이동하는 동시에 조회수가 올라가야하는 거니까 게시글 상세 이동 메서드에 로직을 추가해줬다.
동영상 시연
좋아요
좋아요 기능구현을 하기 위해 좋아요의 상태를 담기위해 따로 테이블이 필요할 것 같아서 likes 테이블 설계를 하고 테이브블 생성을 해주었다.
create table likes(
useremail varchar(25),
board_id int,
PRIMARY KEY (useremail, board_id)
);
likes 테이블은 단일 식별자가 아닌 user 테이블의 PK useremail과 board 테이블의 PK board_id 값을 Likes 테이블을 복합키로 설정해주었다.
Likes.java
Likes entity
package com.se.social.entity;
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import com.se.social.domain.LikesId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "likes")
@Data
@AllArgsConstructor
@NoArgsConstructor
@IdClass(LikesId.class)
public class Likes implements Serializable {
@Id
private String useremail;
@Id
@Column(name = "board_id")
private int board_id;
}
LikesId.java
LikesId domain
package com.se.social.domain;
import java.io.Serializable;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LikesId implements Serializable {
private static final long serialVersionUID = 1L; // 기본값(초기화)
private String useremail;
private int board_id;
}
LikesRepository.java
LikesRepository도 생성해주었다.
package com.se.social.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import com.se.social.domain.LikesId;
import com.se.social.entity.Likes;
import com.se.social.entity.QLikes;
import com.querydsl.jpa.impl.JPAQueryFactory;
public interface LikesRepository extends JpaRepository<Likes, LikesId> {
}
LikesService.java
package com.se.social.service;
import com.se.social.domain.LikesId;
import com.se.social.entity.Likes;
public interface LikesService {
// selectDetail
public Likes selectDetail(LikesId likesId);
// insert, update
public int save(Likes entity);
// delete
public LikesId delete(LikesId likesId);
}
LikesService.java
package com.se.social.service;
import org.springframework.stereotype.Service;
import com.se.social.domain.LikesId;
import com.se.social.entity.Likes;
import com.se.social.repository.LikesRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class LikesServiceImpl implements LikesService {
private final LikesRepository repository;
// selectDetail
@Override
public Likes selectDetail(LikesId likesId) {
return repository.findById(likesId).orElse(null);
}
// insert, update
@Override
public int save(Likes entity) {
repository.save(entity);
return entity.getBoard_id();
}
// delete
@Override
public LikesId delete(LikesId likesId) {
repository.deleteById(likesId);
return likesId;
}
}
LikesService와 LikesServiceImpl을 만들어주었고,
여기서 주요하게 봐야할 점은 . selectDetail 메서드.
selectDetail 메서드를 생성할때,Likes 타입으로 만들어주고 타입도 같은 타입으로 반환해준다.
매개변수는 복합키를 만들어주었던 LikesId를 받아서 복합키를 찾아서 반환해준다. 없다면 null을 반환.
BoardController.java
// 좋아요
@PostMapping("/toggle")
@ResponseBody
public ResponseEntity<?> toggleLike(@RequestBody Likes entity) {
try {
LikesId id = new LikesId(entity.getUseremail(), entity.getBoard_id());
Board boardEntity = boardService.selectDetail(entity.getBoard_id());
if (boardEntity != null) {
Likes existingLike = likesService.selectDetail(id);
if (existingLike == null) {
// 좋아요 추가
boardEntity.setBoard_likes(boardEntity.getBoard_likes() + 1);
likesService.save(entity);
boardService.save(boardEntity); // 트랜잭션 처리
System.out.println("좋아요 추가 완료. 현재 좋아요 개수: " + boardEntity.getBoard_likes());
return ResponseEntity.ok().build();
} else {
// 좋아요 취소
boardEntity.setBoard_likes(boardEntity.getBoard_likes() - 1);
likesService.delete(id);
boardService.save(boardEntity); // 트랜잭션 처리
System.out.println("좋아요 취소 완료. 현재 좋아요 개수: " + boardEntity.getBoard_likes());
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("해당 게시글을 찾을 수 없습니다.");
}
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(false);
}
}
다음은 controller에 토글 방식으로 만들었던, 좋아요 기능.
새로운 useremail과 board_id 값으로 좋아요를 누르면 likes 테이블에 데이터 추가,
또 같은 useremail과 board_id 값으로 좋아요를 누른 상태에서 한번더 누르게 된다면 likes 테이블에서 데이터가 제거
⮕ likes 테이블을 복합키로 구성한 이유
BoardDetail.jsp
<div class="likes">
<span id="likeButton" style="cursor:pointer;" onclick="toggleLikes(${requestScope.boardDetail.board_id},'${sessionScope.loginUser.useremail}')">❤︎</span>
<span id="likeCount">${requestScope.boardDetail.board_likes}</span>
</div>
onclick을 추가해서 비동기 방식으로 js를 호출을 해주었다.
toggleLikes 함수에 매개변수로는 likes 테이블에 필요한 값.
아까 복합키로 지정해둔 값들을 파라미터 값으로 전달해주었다. 아래 두 값.
${requestScope.boardDetail.board_id},'${sessionScope.loginUser.useremail}'
useremail은 string 타입이므로 '' 싱글쿼테이션으로 묶어서 전달해주어야한다.
Board.js
function toggleLikes(board_id, useremail) {
let url = "/board/toggle";
axios.post(url, {
useremail: useremail,
board_id: parseInt(board_id)
}, { headers: { 'Content-Type': 'application/json' } }
).then(response => {
let likeCountElement = document.getElementById('likeCount');
let currentLikeCount = parseInt(likeCountElement.textContent);
if (response.status === 200) {
likeCountElement.textContent = currentLikeCount + 1; // 좋아요 추가
} else if (response.status === 204) {
likeCountElement.textContent = currentLikeCount - 1; // 좋아요 삭제
}
}).catch(err => {
if (err.response && err.response.status === 502) {
alert("[삭제 오류]" + err.response.data);
alert("[시스템 오류]" + err.message);
}
});
}
js파일에서는 axios로 값을 넘겨주는 방식을 이용하였다.
동영상 시연