/* 좋아요 테이블(BOARD_LIKE) 샘플 데이터 추가 */
INSERT INTO "BOARD_LIKE"
VALUES(1, 2001); -- 1번 회원이 1998번 글에 좋아요를 클릭함
COMMIT;
SELECT * FROM BOARD_LIKE;
현재 로그인한 회원이 게시글 좋아요를 눌렀는지 안 눌렀는지 COUNT (다시 한번 체크시 DELETE)
좋아요 여부 확인 (1 : O / 0 : X)
SELECT COUNT(*) FROM BOARD_LIKE
WHERE MEMBER_NO = 1
AND BOARD_NO = 2001;
매개변수에 Session scope에 올라와있는 loginMember 추가
Spring 스펙 올라가면서 안 써도 됨
클래스 위에 @SessionAttributes({"loginMember"})
BoardController
@GetMapping("{boardCode:[0-9]+}/{boardNo:[0-9]+}")
public String boardDetail(@PathVariable("boardCode") int boardCode,
@PathVariable("boardNo") int boardNo,
Model model,
RedirectAttributes ra,
@SessionAttribute(value="loginMember", required=false) Member loginMember) {
@SessionAttribute(value="loginMember", required=false)
로그인한 상태에서 게시글 들어갔을 때 내가 누른 좋아요면 표시되게 Controller 에 memberNo 세팅
// 로그인 상태인 경우에만 memberNo 추가
if(loginMember != null) {
map.put("memberNo", loginMember.getMemberNo());
}
board-mapper.xml selectOne select 문
(SELECT COUNT(*)
FROM BOARD_LIKE
WHERE MEMBER_NO = #{memberNo}
AND BOARD_NO = #{boardNo}) LIKE_CHECK
<select id="selectOne" resultMap="board_rm">
SELECT BOARD_NO, BOARD_TITLE, BOARD_CONTENT, BOARD_CODE, READ_COUNT,
MEMBER_NO, MEMBER_NICKNAME, PROFILE_IMG,
TO_CHAR(BOARD_WRITE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') BOARD_WRITE_DATE,
TO_CHAR(BOARD_UPDATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') BOARD_UPDATE_DATE,
(SELECT COUNT(*)
FROM "BOARD_LIKE"
WHERE BOARD_NO = #{boardNo}) LIKE_COUNT,
(SELECT IMG_PATH || IMG_RENAME
FROM "BOARD_IMG"
WHERE BOARD_NO = #{boardNo}
AND IMG_ORDER = 0) THUMBNAIL,
(SELECT COUNT(*)
FROM BOARD_LIKE
WHERE MEMBER_NO = #{memberNo}
AND BOARD_NO = #{boardNo}) LIKE_CHECK
FROM "BOARD"
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
AND BOARD_NO = #{boardNo}
</select>
Board DTO 에 LIKE_CHECK 라는 필드 없음 만들어줘야함
Board 클래스
// 좋아요 여부 확인
private int likeCheck;
boardDetail.html
<!-- 비동기로 좋아요 누를 때 동작 (4/22 월요일 예정) -->
<!-- 좋아요 누른적이 있으면 fa-solid, 없으면 fa-regular 클래스 추가 -->
<i class="fa-heart" id="boardLike"
th:classappend="*{likeCheck == 1} ? fa-solid : fa-regular"></i>
좋아요 버튼(하트) 클릭 시 비동기로 좋아요 INSERT/DELETE
Thymeleaf 코드 해석 순서
1. th: 코드(java) + Spring EL
2. html 코드 (+ css, js)
1) 로그인한 회원 번호 준비
--> session 에서 얻어오기 (session은 서버에서 관리하기 때문에 JS에서 바로 얻어올 방법 없음)
2) 현재 게시글 번호 준비
3) 좋아요 여부 준비
boardDetail.html
<script th:inline="javascript">
// - loginMember 가 null 인 경우 null 반환
const loginMemberNo = /*[[${session.loginMember?.memberNo}]]*/ "로그인 회원 번호";
// 현재 게시글 번호를 전역 변수로 저장
const boardNo = /*[[${board.boardNo}]]*/ "게시글 번호";
// 현재 게시글 좋아요 여부를 전역 변수로 저장 (값이 변함)
let likeCheck = /*[[${board.likeCheck}]]*/ "좋아요 여부";
</script>
<script src="/js/board/boardDetail.js"></script>
좋아요 클릭했을 때 로그인 상태가 아닌 경우 호출한 곳으로 돌려보내고 html 에서 js에 setting 해서 보내준 값을 java 에 보내기 위해 js Object로 묶어서 보내주기 fetch로 보내주기
돌려받을 값에 대한 처리
// 1. #boardLike 가 클릭 되었을 때
document.querySelector("#boardLike").addEventListener("click", e => {
// 2. 로그인 상태가 아닌 경우 동작 X
if(loginMemberNo == null) {
alert("로그인 후 이용해주세요");
return;
}
// 3. 준비된 3개의 변수를 객체로 저장 (JSON 변환 예정)
const obj = {
"memberNo" : loginMemberNo,
"boardNo" : boardNo,
"likeCheck" : likeCheck
};
// 4. 좋아요 INSERT/DELETE 비동기 요청
fetch("/board/like", {
method : "POST",
headers : {"Content-Type" : "application/json"},
body : JSON.stringify(obj)
})
.then(resp => resp.text()) // 반환 결과 text 형태로 변환
.then(count => {
// 잘못됐을 때 -1 들어옴
if(count == -1) {
console.log("좋아요 처리 실패");
return;
}
// 5. likeCheck 값 0 <-> 1 변환
// -> 클릭 될 때 마다 INSERT/DELETE 동작을 번갈아 가면서 하게끔
likeCheck = likeCheck == 0 ? 1 : 0;
// 6. 하트를 채웠다/비웠다 바꾸기 toggle
e.target.classList.toggle("fa-regular");
e.target.classList.toggle("fa-solid");
// 7. 게시글 좋아요 수 수정 nextSibling 이 좋아요 개수 넣어둔 span 태그
e.target.nextElementSibling.innerText = count;
})
});
BoardController
/** 게시글 좋아요 체크/해제
* @param map
* @return count
*/
@ResponseBody
@PostMapping("like")
public int boardLike(@RequestBody Map<String, Integer> map) {
return service.boardLike(map);
}
BoardServiceImpl
@Override
public int boardLike(Map<String, Integer> map) {
int result = 0;
// 1. 좋아요가 체크된 상태인 경우 (likeCheck == 1)
// -> BOARD_LIKE 테이블에 DELETE
if(map.get("likeCheck") == 1) {
result = mapper.deleteBoardLike(map);
} else {
// 2. 좋아요가 해제된 상태인 경우 (likeCheck == 0)
// -> BOARD_LIKE 테이블에 INSERT
result = mapper.insertBoardLike(map);
}
<!-- 좋아요 해제 -->
<delete id="deleteBoardLike">
DELETE FROM BOARD_LIKE
WHERE MEMBER_NO = #{memberNo}
AND BOARD_NO = #{boardNo}
</delete>
<!-- 좋아요 체크 -->
<insert id="insertBoardLike">
INSERT INTO BOARD_LIKE(MEMBER_NO, BOARD_NO)
VALUES(#{memberNo}, #{boardNo})
</insert>
serviceImpl
위에 다 안 걸리면 잘못된 거라서 -1 return
// 3. 다시 해당 게시글의 좋아요 개수 조회해서 반환
if(result > 0) {
return mapper.selectLikeCount(map.get("boardNo"));
}
return -1;
}
BoardMapper 인터페이스
/** 게시글 좋아요 개수 조회
* @param integer
* @return result
*/
int selectLikeCount(int temp);
mapper.xml
<!-- 게시글 좋아요 수 조회 -->
<select id="selectLikeCount">
SELECT COUNT(*)
FROM BOARD_LIKE
WHERE BOARD_NO = #{boardNo}
</select>