
✔️webapp(WEB-INF(views(board))) → boardDetail.jsp
<!-- 좋아요 하트 -->
<span class="like-area">
<!-- 좋아요를 누른 적이 없거나, 로그인 X -->
<c:if test="${empty likeCheck}">
<i class="fa-regular fa-heart" id="boardLike"></i>
</c:if>
<!-- 좋아요를 누른 적이 있을 때 -->
<c:if test="${!empty likeCheck}">
<i class="fa-solid fa-heart" id="boardLike"></i>
</c:if>
<span>${board.likeCount}</span>
</span>
<script> // JSP에서 작성 가능한 언어/라이브러리 // -> html, css, js, java, EL, JSTL // JSP 해석 우선 순위 : Java/EL/JSTL > HTML, CSS, JS // 게시글 번호 전역 변수로 선언 const boardNo = "${board.boardNo}"; // 로그인한 회원 번호를 전역 변수로 선언 // -> 작성한 EL 구문이 null일 경우 빈칸으로 출력되어 // 변수에 값이 대입되지 않는 문제가 발생할 수 있음! // 해결 방법 : EL 구문을 '', "" 문자열로 감싸면 해결 // -> EL 값이 null이어도 ""(빈문자열)로 출력 const loginMemberNo = "${loginMember.memberNo}"; console.log(boardNo); console.log(loginMemberNo); </script>
✔️webapp(resources(js((board))) → boardDetail.js
// 좋아요 버튼이 클릭되었을 때
const boardLike = document.getElementById("boardLike");
boardLike.addEventListener("click", e => {
// 로그인 여부 검사
if(loginMemberNo == ""){
alert("로그인 후 이용해 주세요.");
return;
}
let check; // 기존에 좋아요 체크 X (빈하트) : 0
// 기존에 좋아요 체크 O (꽉찬하트) : 1
// contains("클래스명"): 클래스가 있으면 true, 없으면 false
if(e.target.classList.contains("fa-regular")){ // 좋아요 체크 X (빈하트)
check = 0;
} else{ // 좋아요 체크 O (꽉찬하트)
check = 1;
}
// ajax로 서버로 제출할 파라미터를 모아둔 JS 객체
// 로그인한 회원 번호, 게시글 번호, check
const data = { "boardNo" : boardNo,
"memberNo" : loginMemberNo,
"check" : check};
// ajax 코드 작성
fetch("/board/like", {
method : "POST",
headers : {"Content-Type" : "application/json"},
body : JSON.stringify(data)
})
.then(response => response.text()) // 응답 객체를 필요한 형태로 파싱하여 리턴
.then(count => {
console.log("count : " + count);
if(count == -1){ // INSERT, DELETE 실패 시
console.log("좋아요 처리 실패");
return;
}
// toggle() : 클래스가 있으면 없애고, 없으면 추가하고
e.target.classList.toggle("fa-regular");
e.target.classList.toggle("fa-solid");
// 현재 게시글의 좋아요 수를 화면에 출력
e.target.nextElementSibling.innerText = count;
}) // 파싱된 데이터를 받아서 처리하는 코드 작성
.catch(err => {
console.log("예외 발생");
console.log(err);
}) // 예외 발생 시 처리하는 부분
})
✔️project(board(controller)) → BoardController.java
...
// 게시글 상세 조회 서비스 호출
Board board = service.selectBoard(map);
String path = null;
if(board != null) { // 조회 결과가 있을 경우
// ------------------------------------------------
// 현재 로그인 상태인 경우
// 로그인한 회원이 해당 게시글에 좋아요를 눌렀는지 확인
if(loginMember != null) { // 로그인 상태인 경우
// 회원 번호를 map 추가
// map(boardCode, boardNo, memberNo)
map.put("memberNo", loginMember.getMemberNo());
// 좋아요 여부 확인 서비스 호출
int result = service.boardLikeCheck(map);
// 누른 적이 있을 경우
if(result > 0) model.addAttribute("likeCheck", "on");
}
// ------------------------------------------------
path = "board/boardDetail"; // forward 할 jsp 경로
model.addAttribute("board", board);
} else { // 조회 결과가 없을 경우
path = "redirect:/board/" + boardCode; // 게시판 첫 페이지로 리다이렉트
ra.addFlashAttribute("message", "해당 게시글이 존재하지 않습니다.");
}
return path;
}
// 좋아요 처리
@PostMapping("/like")
@ResponseBody // 반환되는 값이 비동기 요청한 곳으로 돌아가게 함
public int like(@RequestBody Map<String, Integer> paramMap) {
// System.out.println(paramMap);
return service.like(paramMap);
}
✔️project(board(model(service))) → BoardService.java
...
/** 좋아요 여부 확인
* @param map
* @return result
*/
int boardLikeCheck(Map<String, Object> map);
/** 좋아요 처리 서비스
* @param paramMap
* @return count
*/
int like(Map<String, Integer> paramMap);
✔️project(board(model(service))) → BoardServiceImpl.java
...
// 좋아요 여부 확인 서비스
@Override
public int boardLikeCheck(Map<String, Object> map) {
return dao.boardLikeCheck(map);
}
// 좋아요 처리 서비스
@Transactional(rollbackFor = Exception.class)
@Override
public int like(Map<String, Integer> paramMap) {
int result = 0;
if(paramMap.get("check") == 0) { // 좋아요 상태 X
// BOARD_LIKE 테이블 INSERT
result = dao.insertBoardLike(paramMap);
} else { // 좋아요 상태 O
// BOARD_LIKE 테이블 DELETE
result = dao.deleteBoardLike(paramMap);
}
// SQL 수행 결과가 0 == DB 또는 파라미터에 문제가 있다.
// 1) 에러를 나타내는 임의의 값을 반환 (-1)
if(result == 0) return -1;
// 현재 게시글의 좋아요 개수 조회
int count = dao.countBoardLike(paramMap.get("boardNo"));
return count;
}
✔️project(board(model(dao))) → BoardDAO.java
...
/** 좋아요 여부 확인
* @param map
* @return result
*/
public int boardLikeCheck(Map<String, Object> map) {
return sqlSession.selectOne("boardMapper.boardLikeCheck", map);
}
/** 좋아요 테이블 삽입
* @param paramMap
* @return result
*/
public int insertBoardLike(Map<String, Integer> paramMap) {
return sqlSession.insert("boardMapper.insertBoardLike", paramMap);
}
/** 좋아요 테이블 삭제
* @param paramMap
* @return result
*/
public int deleteBoardLike(Map<String, Integer> paramMap) {
return sqlSession.delete("boardMapper.deleteBoardLike", paramMap);
}
/** 좋아요 개수 조회
* @param boardNo
* @return count
*/
public int countBoardLike(Integer boardNo) {
return sqlSession.selectOne("boardMapper.countBoardLike", boardNo);
}
✔️resources(mappers) → board-mapper.xml
...
<!-- 좋아요 여부 확인 -->
<select id="boardLikeCheck" resultType="_int">
SELECT COUNT(*) FROM BOARD_LIKE
WHERE BOARD_NO = #{boardNo}
AND MEMBER_NO = #{memberNo}
</select>
<!-- 좋아요 테이블 삽입 -->
<insert id="insertBoardLike">
INSERT INTO BOARD_LIKE VALUES(#{boardNo}, #{memberNo})
</insert>
<!-- 좋아요 테이블 삭제 -->
<delete id="deleteBoardLike">
DELETE FROM BOARD_LIKE
WHERE BOARD_NO = #{boardNo}
AND MEMBER_NO = #{memberNo}
</delete>
<!-- 좋아요 개수 조회 -->
<select id="countBoardLike" resultType="_int">
SELECT COUNT(*) FROM BOARD_LIKE
WHERE BOARD_NO = #{boardNo}
</select>
⭐️ 쿠키로 조회수 증가시키기
✔️project(board(controller)) → BoardController.java
...
// 게시글 상세 조회
@GetMapping("/{boardCode}/{boardNo}")
public String boardDetail(
@PathVariable("boardCode") int boardCode
, @PathVariable("boardNo") int boardNo
, Model model // 데이터 전달용 객체
, RedirectAttributes ra // 리다이렉트 시 데이터 전달용 객체
, @SessionAttribute(value="loginMember", required=false) Member loginMember
// 세션에서 loginMember를 얻어오는데 없으면 null, 있으면 회원 정보 저장
// 쿠키를 이용한 조회 수 증가에서 사용
, HttpServletRequest req
, HttpServletResponse resp
) throws ParseException {
Map<String, Object> map = new HashMap<String, Object>();
map.put("boardCode", boardCode);
map.put("boardNo", boardNo);
// 게시글 상세 조회 서비스 호출
Board board = service.selectBoard(map);
String path = null;
if(board != null) { // 조회 결과가 있을 경우
...
// 쿠키를 이용한 조회 수 증가 처리
// 1) 비회원 또는 로그인한 회원의 글이 아닌 경우
if(loginMember == null || // 비회원
board.getMemberNo() != loginMember.getMemberNo()) {
// 2) 쿠키 얻어오기
Cookie c = null;
// 요청에 담겨 있는 모든 쿠키 얻어오기
Cookie[] cookies = req.getCookies();
if(cookies != null) { // 쿠키가 존재할 경우
// 쿠키 중 "readBoardNo"라는 쿠키를 찾아서 c에 대입
for(Cookie cookie : cookies) {
if(cookie.getName().equals("readBoardNo")) {
c = cookie;
break;
}
}
}
// 3) 기존 쿠키가 없거나(c == null)
// 존재는 하지만 현재 게시글 번호가
// 쿠키에 저장되지 않은 경우(오늘 해당 게시글을 본 적 없음)
int result = 0;
if(c == null) {
// 쿠키가 존재 X -> 하나 새로 생성
// 톰캣 8.5 이상부터는 쿠키에 >> , / = 띄어쓰기 << 작성 불가
c = new Cookie("readBoardNo", "|" + boardNo + "|");
// 조회 수 증가 서비스 호출
result = service.updateReadCount(boardNo);
} else {
// 현재 게시글 번호가 쿠키에 있는지 확인
// Cookie.getValue() : 쿠키에 저장된 모든 값을 읽어옴
// -> String으로 반환
// String.indexOf(문자열)
// : 찾는 문자열이 몇 번 인덱스에 존재하는지 반환
// 단, 없으면 -1 반환
if(c.getValue().indexOf("|" + boardNo + "|") == -1) {
// 쿠키에 현재 게시글 번호가 없다면
// 기존 값에 게시글 번호 추가해서 다시 세팅
c.setValue(c.getValue() + "|" + boardNo + "|");
// 조회수 증가 서비스 호출
result = service.updateReadCount(boardNo);
}
} // 3) 종료
// 4) 조회 수 증가 성공 시
// 쿠키가 적용되는 경로, 수명(당일 23시 59분 59초) 지정
if(result > 0) {
// 조회된 board 조회 수와 DB 조회 수를 동기화
board.setReadCount(board.getReadCount() +1);
// 적용 경로 설정
c.setPath("/"); // "/" 이하 경로 요청 시 쿠키 서버로 전달
// 수명 지정
Calendar cal = Calendar.getInstance(); // 싱글톤 패턴
cal.add(cal.DATE, 1);
// 날짜 표기법 변경 객체 (DB의 TO_CHAR()와 비슷)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// java.util.Date
Date a = new Date(); // 현재 시간
Date temp = new Date(cal.getTimeInMillis()); // 내일 (24시간 후)
// 2023-08-24 12:17:40
Date b = sdf.parse(sdf.format(temp)); // 내일 0시 0분 0초
// 내일 0시 0분 0초 - 현재 시간
long diff = (b.getTime() - a.getTime()) / 1000;
// -> 내일 0시 0분 0초까지 남은 시간을 초 단위로 반환
c.setMaxAge((int)diff); // 수명 설정
resp.addCookie(c); // 응답 객체를 이용해서 클라이언트에게 전달
}
}
path = "board/boardDetail"; // forward 할 jsp 경로
model.addAttribute("board", board);
} else { // 조회 결과가 없을 경우
path = "redirect:/board/" + boardCode; // 게시판 첫 페이지로 리다이렉트
ra.addFlashAttribute("message", "해당 게시글이 존재하지 않습니다.");
}
return path;
}
...
date버전이 너무 별로라서 calendar.getl으로 사용
= 더 업그레이드 버전으로 나옴
Calendar cal = Calendar.getInstance(); // 싱글톤 패턴 // 오늘 날짜 나옴
cal.add(cal.DATE, 1); // 하루 더했으니깐 내일
✔️project(board(model(service))) → BoardService.java
...
/** 조회수 증가 서비스
* @param boardNo
* @return
*/
int updateReadCount(int boardNo);
✔️project(board(model(service))) → BoardServiceImpl.java
...
// 조회 수 증가 서비스
@Transactional(rollbackFor = Exception.class)
@Override
public int updateReadCount(int boardNo) {
return dao.updateReadCount(boardNo);
}
✔️project(board(model(dao))) → BoardDAO.java
...
/** 조회 수 증가
* @param boardNo
* @return result
*/
public int updateReadCount(int boardNo) {
return sqlSession.update("boardMapper.updateReadCount", boardNo);
}
✔️resources(mappers) → board-mapper.xml
...
<update id="updateReadCount">
UPDATE BOARD SET
READ_COUNT = READ_COUNT + 1
WHERE BOARD_NO = #{boardNo}
</update>
import할 때 util로 가져오기