스프링 프레임워크/스프링 MVC

# Spring MVC 게시판 예제 19 : 게시글 북마크

edenDev 2023. 3. 19.

본 포스팅의 예제는 STS 또는 Eclipse를 사용하지 않고 Intellij를 통해 구현하고 있습니다.

그래서 기존의 STS(Spring Tool Studio)에서 생성된 Spring 프로젝트의 스프링 관련 설정 파일명과

프로젝트 구조가 약간 다를 수 있습니다.Intellij 스프링 mvc 프로젝트 생성 포스팅을 참고해주시면 감사하겠습니다.


Spring-MVC 기본 개념 및 테스트 예제 관련 포스팅 링크

순서 포스팅 제목
1 Intellij에서 Spring MVC Project 생성하기
2 Spring MVC - MariaDB 연결테스트
3 Spring MVC - Mybatis 설정 및 테스트
4 SpringMVC 구조
5 SpringMVC + Mybatis
6 Spring MVC Controller 작성 연습
7 Spring Interceptor

 

Spring-MVC 게시판 예제 이전 포스팅 링크

순서 포스팅제목
1 IntelliJ를 이용한 Spring MVC Project 생성 하기
2 Bootstrap AdminLTE Template 적용하기
3 ExceptionResovler : 예외페이지 처리
4 Spring AOP 적용하기
5 회원가입 구현하기
6 회원 유효성 검사 (Feat. JavaScript)
7 회원 유효성 검사 (Feat. Validation)
8 HttpSession을 이용한 로그인 구현하기
9 아이디 찾기 및 패스워드 찾기
10 내 프로필 페이지 및 회원정보 변경 및 탈퇴 구현
11 인터셉터 권한 체크
12 계정 복구 페이지 구현
13 자동로그인
14 게시글 목록 출력
15 게시글 작성
16 게시글 조회수 증가 및 중복 증가 방지
17 게시글 수정 및 삭제
18 게시글 좋아요

 


1. 게시글 북마크 구현

 

1.1 게시글 북마크 테이블 구현

--  게시글 북마크
drop table eden_book_mark;
create table eden_book_mark
(
    book_mark_no number primary key,
    board_no     number not null,
    user_no      number not null,
    reg_date     date default sysdate,
    constraint bookMark_boardNo foreign key (board_no) references eden_board (board_no),
    constraint bookMark_userNo foreign key (user_no) references eden_user (user_no)
);

--  게시글 북마크 시퀸스
drop sequence eden_book_mark_seq;
create sequence eden_book_mark_seq;

1.2 게시글 북마크 클래스 구현

 

src/기본패키지/bookmark/domain 패키지 안에 BookMarkVo 클래스 생성 후 아래와 같이 내용을 입력 해주세요.

import java.util.Date;

public class BookMarkVo {
    private int book_mark_no;
    private int board_no;
    private int user_no;
    private Date reg_date;

    public BookMarkVo() {
        super();
    }

    public BookMarkVo(int book_mark_no, int board_no, int user_no, Date reg_date) {
        this.book_mark_no = book_mark_no;
        this.board_no = board_no;
        this.user_no = user_no;
        this.reg_date = reg_date;
    }

    public int getBook_mark_no() {
        return book_mark_no;
    }

    public void setBook_mark_no(int book_mark_no) {
        this.book_mark_no = book_mark_no;
    }

    public int getBoard_no() {
        return board_no;
    }

    public void setBoard_no(int board_no) {
        this.board_no = board_no;
    }

    public int getUser_no() {
        return user_no;
    }

    public void setUser_no(int user_no) {
        this.user_no = user_no;
    }

    public Date getReg_date() {
        return reg_date;
    }

    public void setReg_date(Date reg_date) {
        this.reg_date = reg_date;
    }

    @Override
    public String toString() {
        return "BookMarkVo{" +
                "book_mark_no=" + book_mark_no +
                ", board_no=" + board_no +
                ", user_no=" + user_no +
                ", reg_date=" + reg_date +
                '}';
    }
}

1.3 게시글 북마크 영속 계층 (Persistence Tire) 구현

 

src/기본패키지/bookmark/persistence 패키지 안에 BookMarkDAO 인터페이스와 BookMarkDAOImpl 클래스 생성 후 아래와 같이 내용을 작성 해주세요.


import com.spring.practice.bookmark.domain.BookMarkVo;

public interface BookMarkDAO {

    //  게시글 북마크 등록
    public void doBookMark(BookMarkVo param);

    //  게시글 북마크 상태
    public int getMyBookMarkStatus(BookMarkVo param);

    //  게시글 북마크 취소
    public void deleteBookMark(int board_no, int user_no);
}

import com.spring.practice.bookmark.domain.BookMarkVo;
import com.spring.practice.commons.annotation.LogException;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.HashMap;

@Repository
public class BookMarkDAOImpl implements BookMarkDAO {

    private static final String NAMESPACE = "mappers.bookmark.BookMarkMapper";

    @Autowired
    SqlSession sqlSession;

    //  게시글 북마크 등록
    @Override
    @LogException
    public void doBookMark(BookMarkVo param) {
        sqlSession.insert(NAMESPACE + ".doBookMark", param);
    }

    //  게시글 북마크 상태
    @Override
    @LogException
    public int getMyBookMarkStatus(BookMarkVo param) {
        return sqlSession.selectOne(NAMESPACE + ".getMyBookMarkStatus", param);
    }

    //  게시글 북마크 취소
    @Override
    @LogException
    public void deleteBookMark(int board_no, int user_no) {
        HashMap<String, Object> data = new HashMap<>();
        data.put("boardNo", board_no);
        data.put("userNo", user_no);
        sqlSession.delete(NAMESPACE + ".deleteBookMark", data);
    }

/resources/mappers/bookmark 패키지 안에 BookMarkSQLMapper.xml 생성후 아래와 같이 내용을 작성 해주세요.

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mappers.bookmark.BookMarkMapper">

    <!-- 게시글 북마크 등록 -->
    <insert id="doBookMark">
        insert into eden_book_mark (book_mark_no, board_no, user_no)
        values (eden_book_mark_seq.nextval,
                #{board_no},
                #{user_no})
    </insert>

    <!-- 게시글 북마크 상태 -->
    <select id="getMyBookMarkStatus" resultType="int">
        select count(*)
        from eden_book_mark
        where board_no = #{board_no}
          and user_no = #{user_no}
    </select>

    <!-- 게시글 북마크 취소 -->
    <delete id="deleteBookMark">
        delete
        from eden_book_mark
        where board_no = #{boardNo}
          and user_no = #{userNo}
    </delete>
</mapper>

1.4 게시글 북마크 비지니스 (Business Tire) 계층 구현

 

src/기본패키지/bookmark/service 패키지안에 BookMarkService 인터페이스와 BookMarkServiceImpl 클래스 생성 후 아래와 같이 내용을 작성 해주세요.


import com.spring.practice.bookmark.domain.BookMarkVo;

public interface BookMarkService {

    //  게시글 북마크 등록
    public void doBookMark(BookMarkVo param);

    //  게시글 북마크 상태
    public int getMyBookMarkStatus(BookMarkVo param);
}

 


import com.spring.practice.board.persistence.BoardDAO;
import com.spring.practice.bookmark.domain.BookMarkVo;
import com.spring.practice.bookmark.persistence.BookMarkDAO;
import com.spring.practice.commons.annotation.LogException;
import com.spring.practice.user.persistence.UserDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookMarkServiceImpl implements BookMarkService {
    @Autowired
    BookMarkDAO bookMarkDAO;

    @Autowired
    UserDAO userDAO;

    @Autowired
    BoardDAO boardDAO;

    //  게시글 북마크 등록
    @Override
    @LogException
    public void doBookMark(BookMarkVo param) {
        int bookMarkStatus = getMyBookMarkStatus(param);
        if (bookMarkStatus > 0) {
            bookMarkDAO.deleteBookMark(param.getBoard_no(), param.getUser_no());
        } else {
            bookMarkDAO.doBookMark(param);
        }
    }

    //  게시글 북마크 상태 조회
    @Override
    @LogException
    public int getMyBookMarkStatus(BookMarkVo param) {
        return bookMarkDAO.getMyBookMarkStatus(param);
    }
}

1.5 게시글 북마크 컨트롤러 구현

 

src/기본패키지/bookmark/controller 패키지 안에 RestBookMarkController 클래스 생성 후 아래와 같이 내용을 작성 해주세요.


import com.spring.practice.bookmark.domain.BookMarkVo;
import com.spring.practice.bookmark.service.BookMarkService;
import com.spring.practice.user.domain.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;
import java.util.HashMap;

@RestController
@RequestMapping(value = "/bookmark/*")
public class RestBookMarkController {

    HashMap<String, Object> data = new HashMap<>();

    @Autowired
    BookMarkService bookMarkService;

    //  게시글 북마크 등록
    @PostMapping(value = "doBookMark")
    public HashMap<String, Object> doBookMark(BookMarkVo param, HttpSession session) {
        UserVo sessionUser = (UserVo) session.getAttribute("sessionUser");
        if (sessionUser == null) {
            data.put("result", "error");
            data.put("reason", "로그인이 필요합니다.");
            return data;
        }

        int userNo = sessionUser.getUser_no();
        int myBookMarkStatus = bookMarkService.getMyBookMarkStatus(param);

        if (myBookMarkStatus > 0) {
            data.put("status", "bookMark");
        } else {
            data.put("status", "unBookMark");
        }

        param.setUser_no(userNo);

        data.put("result", "success");

        bookMarkService.doBookMark(param);
        return data;
    }

    //  게시글 북마크 상태
    @PostMapping("getMyBookMarkStatus")
    public HashMap<String, Object> getMyBookMarkStatus(BookMarkVo param, HttpSession session) {

        UserVo sessionUser = (UserVo) session.getAttribute("sessionUser");

        int userNo = sessionUser.getUser_no();
        param.setUser_no(userNo);

        int bookMarkStatus = bookMarkService.getMyBookMarkStatus(param);

        if (bookMarkStatus > 0) {
            data.put("status", "bookMark");
        } else {
            data.put("status", "unBookMark");
        }

        return data;
    }
}

1.6 게시글 북마크 뷰페이지(JSP) 구현

 

web-inf/views/board 패키지 안에 detailsPosting.jsp 파일의 내용을 아래와 같이 수정 해주세요.


<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ include file="../include/head.jsp" %>
<%@ include file="../include/user_menu.jsp" %>

<html>
<body class="hold-transition skin-green-light sidebar-mini" oncopy="return false" oncut="return false"
      onpaste="return false">
<div class="wrapper">

    <%@ include file="../include/top_menu.jsp" %>

    <%@ include file="../include/left_menu.jsp" %>

    <div class="content-wrapper">
        <section class="content container-fluid">

            <div class="col-lg-12">


                <%--게시글 내용 영역--%>
                <div class="box box-primary">

                    <%--게시글 제목 영역--%>
                    <div class="box-header with-border">
                        <h3 class="box-title">글제목 : ${data.boardVo.board_title}</h3>
                        <ul class="list-inline pull-right">
                            <li><a href="#" class="link-black text-lg" id="likeCount">좋아요 수(${data.totalLikeCount})</a>
                            </li>
                            <li><span><i class="fa fa-eye"></i>조회수
                                (${data.boardVo.board_view_count})</span></li>
                        </ul>
                    </div>

                    <%--게시글 내용 영역--%>
                    <div class="box-body" style="height: 700px">
                        ${fn:replace(fn:replace(fn:escapeXml(data.boardVo.board_content), newLine, "<br/>") , " ", "&nbsp;")}
                    </div>

                    <%--작성자 정보 영역--%>
                    <div class="box-footer">
                        <div class="user-block">
                            <ul class="navbar-custom-menu">
                                <li class="dropdown messages-menu">
                                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                                        <img class="img-circle img-bordered-sm"
                                             src="${path}/resources/dist/img/profile/${data.userVo.user_image}" alt="user image">
                                        <span>${data.userVo.user_nickname}</span>
                                        <span class="description"><fmt:formatDate pattern="yyyy-MM-dd a HH:mm"
                                                                                  value="${data.boardVo.board_write_date}"/></span>
                                    </a>
                                    <ul class="dropdown-menu">
                                        <li><a href="#">작성 글보기</a></li>
                                    </ul>
                                </li>
                            </ul>
                        </div>
                    </div>

                    <div class="box-footer">
                        <form role="form" method="post">
                            <input type="hidden" id="boardNo" name="board_no" value="${data.boardVo.board_no}">
                            <input type="hidden" id="userNo" name="user_no" value="${sessionUser.user_no}">
                            <input type="hidden" id="categoryNo" name="category_no" value="${data.boardVo.category_no}">
                        </form>
                        <button class="btn btn-primary" onclick="postingList(${data.boardVo.category_no})"><i
                                class="fa fa-list"></i> 목록
                        </button>
                        <c:if test="${!empty sessionUser}">
                            <button type="button" class="btn btn-info text-lg-center" id="boardLike"> 좋아요</button>
                            <button type="button" class="btn btn-info text-lg-center" id="postingBookMark"> 북마크</button>
                        </c:if>
                        <c:if test="${sessionUser.user_no == data.boardVo.user_no}">
                            <div class="pull-right">
                                <button type="button" class="btn btn-warning modBtn"><i class="fa fa-edit"></i> 수정
                                </button>
                                <button type="button" class="btn btn-danger delBtn"><i class="fa fa-trash"></i> 삭제
                                </button>
                            </div>
                        </c:if>
                    </div>
                </div>
            </div>
        </section>
    </div>

    <%@ include file="../include/footer.jsp" %>

</div>

<%@ include file="../include/plugin_js.jsp" %>
</body>
</html>

1.7 게시글 북마크 자바스크립트 구현

 

webapp/resources/dist/js/bookmark 패키지 안에 relateBookMark.js 파일 생성 후 아래와 같이 내용을 작성 해주세요.


window.addEventListener("DOMContentLoaded", function () {

    let getMyBookMarkStatus = function () {
        $.ajax({
            type: "post",
            url: "../bookmark/getMyBookMarkStatus",
            data: {
                board_no: $("#boardNo").val()
            },
            dataType: "json",
            success: function (data) {
                if (data.status == "bookMark") {
                    document.getElementById('postingBookMark').innerText = '북마크 취소';
                    document.getElementById('postingBookMark').className += 'fa fa-bookmark'
                } else if (data.status == 'unBookMark') {
                    document.getElementById('postingBookMark').innerText = '북마크';
                    document.getElementById('postingBookMark').className += 'fa fa-bookmark-o';
                }
            }
        });
    }

    const url = location.pathname;

    if (url.includes('detailsPosting')) {
        getMyBookMarkStatus();
    }

    $("#postingBookMark").click(function () {
        $.ajax({
            type: "post",
            url: "../bookmark/doBookMark",
            data: {
                board_no: $("#boardNo").val()
            },
            dataType: "json",
            success: function (data) {
                if (data.status == "unBookMark") {
                    alert("게시글 북마크에 성공 하였습니다.");
                    location.reload();
                } else if (data.status == "bookMark") {
                    alert("게시글 북마크를 취소 하였습니다.");
                    location.reload();
                }
            }
        });
    });
});

1.8 게시글 북마크 화면 확인

 

 

 

댓글