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

# Spring MVC 게시판 예제 20 : 게시글 검색

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 게시글 좋아요
19 게시글 북마크

 


1. 게시글 검색 구현

1.1 게시글 검색 테이블 구현

--  게시글 검색 카테고리
drop table eden_board_search_category;
create table eden_board_search_category
(
    search_category_no number primary key,
    search_type        varchar2(200) not null unique
);

--  게시글 검색 카테고리 시퀸스
drop sequence eden_board_search_category_seq;
create sequence eden_board_search_category_seq;

insert into eden_board_search_category (search_category_no, search_type)
values (eden_board_search_category_seq.nextval, '제목');
insert into eden_board_search_category (search_category_no, search_type)
values (eden_board_search_category_seq.nextval, '내용');
insert into eden_board_search_category (search_category_no, search_type)
values (eden_board_search_category_seq.nextval, '작성자');
insert into eden_board_search_category (search_category_no, search_type)
values (eden_board_search_category_seq.nextval, '제목+내용');
insert into eden_board_search_category (search_category_no, search_type)
values (eden_board_search_category_seq.nextval, '내용+작성자');
insert into eden_board_search_category (search_category_no, search_type)
values (eden_board_search_category_seq.nextval, '전체');

게시글 검색시 쿼리 연동을 통한 타입 설정을 구현 하였습니다. search_type 컬럼에 unique 제약 조건이 사용되는 이유는 게시글 검색시 선택되는 타입이 중복되어서는 안되기 떄문입니다. 따라서 unique 제약 조건을 통해서 타입 추가시 중복이 안되도록 구현 할 수 있습니다.

 

1.2 게시글 검색 클래스 구현

 

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

public class SearchCategoryVo {
    private int search_category_no;
    private String search_type;

    public SearchCategoryVo() {
        super();
    }

    public SearchCategoryVo(int search_category_no, String search_keyword) {
        this.search_category_no = search_category_no;
        this.search_type = search_keyword;
    }

    public int getSearch_category_no() {
        return search_category_no;
    }

    public void setSearch_category_no(int search_category_no) {
        this.search_category_no = search_category_no;
    }

    public String getSearch_type() {
        return search_type;
    }

    public void setSearch_type(String search_type) {
        this.search_type = search_type;
    }

    @Override
    public String toString() {
        return "SearchCategoryVo{" +
                "search_category_no=" + search_category_no +
                ", search_type='" + search_type + '\'' +
                '}';
    }
}

1.3 게시글 검색 영속 계층 (Persistence Tire) 구현

 

src/기본패키지/board/persistence 패키지 안에 BoardDAO 인터페이스와 BoardDAOImpl 클래스 파일에 아래와 같이 내용을 수정 해주세요.

    //  게시글 목록
    public List<BoardVo> getBoardList(int search_category_no, String keyword);

    //  게시글 목록 (카테고리별 정렬)
    public List<BoardVo> getBoardByCategoryList(int category_no, int search_category_no, String keyword);

    //  게시글 검색 카테고리 목록
    public List<SearchCategoryVo> getBoardSearchCategoryList();
    //  게시글 목록
    @Override
    @LogException
    public List<BoardVo> getBoardList(int search_category_no, String keyword) {

        HashMap<String, Object> param = new HashMap<>();
        param.put("search_category_no", search_category_no);
        param.put("keyword", keyword);

        return sqlSession.selectList(NAMESPACE + ".getBoardList", param);
    }

    //  게시글 목록 (카테고리별 정렬)
    @Override
    @LogException
    public List<BoardVo> getBoardByCategoryList(int category_no, int search_category_no, String keyword) {

        HashMap<String, Object> param = new HashMap<>();
        param.put("category_no", category_no);
        param.put("search_category_no", search_category_no);
        param.put("keyword", keyword);

        return sqlSession.selectList(NAMESPACE + ".getBoardByCategoryList", param);
    }


    //  게시글 검색 카테고리 목록
    @Override
    @LogException
    public List<SearchCategoryVo> getBoardSearchCategoryList() {
        return sqlSession.selectList(NAMESPACE + ".getBoardSearchCategoryList");
    }

/resources/mappers/board/BoardSQLMapper.xml 파일을 여신 후에 아래와 같이 내용을 수정 해주세요.

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

    <!-- 게시글 목록 -->
    <select id="getBoardList" resultType="BoardVo">
        select eb.* from eden_board eb, eden_user eu where eb.user_no = eu.user_no
        <if test="search_category_no != 0">
            <choose>
                <!-- 게시글 제목 검색 -->
                <when test="search_category_no == 1">
                    and eb.board_title like '%' || #{keyword} || '%'
                </when>

                <!-- 게시글 내용 검색 -->
                <when test="search_category_no == 2">
                    and eb.board_content like '%' || #{keyword} || '%'
                </when>

                <!-- 게시글 작성자 검색 -->
                <when test="search_category_no == 3">
                    and eu.user_nickname like '%' || #{keyword} || '%'
                </when>

                <!-- 게시글 제목 + 내용 검색 -->
                <when test="search_category_no == 4">
                    and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%')
                </when>

                <!-- 게시글 작성자 + 내용 검색 -->
                <when test="search_category_no == 5">
                    and (eu.user_nickname like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%')
                </when>

                <!-- 게시글 전체 검색 -->
                <when test="search_category_no == 6">
                    and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%'
                    or eu.user_nickname like '%' || #{keyword} || '%')
                </when>

            </choose>
        </if>
    </select>

    <!-- 게시글 목록 (카테고리별 정렬) -->
    <select id="getBoardByCategoryList" resultType="BoardVo">
        select eb.* from eden_board eb, eden_user eu where eb.user_no = eu.user_no and eb.category_no = #{category_no}
        <if test="search_category_no != 0">
            <choose>
                <!-- 게시글 제목 검색 -->
                <when test="search_category_no == 1">
                    and eb.board_title like '%' || #{keyword} || '%'
                </when>

                <!-- 게시글 내용 검색 -->
                <when test="search_category_no == 2">
                    and eb.board_content like '%' || #{keyword} || '%'
                </when>

                <!-- 게시글 작성자 검색 -->
                <when test="search_category_no == 3">
                    and eu.user_nickname like '%' || #{keyword} || '%'
                </when>

                <!-- 게시글 제목 + 내용 검색 -->
                <when test="search_category_no == 4">
                    and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%')
                </when>

                <!-- 게시글 작성자 + 내용 검색 -->
                <when test="search_category_no == 5">
                    and (eu.user_nickname like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%')
                </when>

                <!-- 게시글 전체 검색 -->
                <when test="search_category_no == 6">
                    and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%'
                    or eu.user_nickname like '%' || #{keyword} || '%')
                </when>

            </choose>
        </if>
    </select>

게시글 검색 시 선택한 타입에 따라 Mybatis 동적 쿼리를 활용한 IF 문 사용으로 타입별로 쿼리문이 실행 되도록 작성 되었습니다.

또한 두 가지 이상의 타입으로 검색시에 첫번쨰 조건 이후 or 연산자를 통한 조건을 기입 하였고 추후 오작동을 방지 하기 위해 () 을 통한 우선 순위를 설정 하였습니다.

 

1.4 게시글 검색 비지니스 계층 (Business Tire) 구현

 

src/기본패키지/board/service 패키지안에 BoardService 인터페이스와 BoardServiceImpl 클래스 파일 내용을 아래와 같이 수정 해주세요.

    //  게시글 목록
    public ArrayList<HashMap<String, Object>> getBoardList(int category_no, int search_category_no, String keyword);
    
    //  게시글 검색 카테고리 목록
    public List<SearchCategoryVo> getBoardSearchCategoryList();
//  게시글 목록

@Override
@LogException
public ArrayList<HashMap<String, Object>> getBoardList(int category_no, int search_category_no, String keyword) {

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

    List<BoardVo> boardVoList;

    if (category_no != 0) {
        boardVoList = boardDAO.getBoardByCategoryList(category_no, search_category_no, keyword);
    } else {
        boardVoList = boardDAO.getBoardList(search_category_no, keyword);
    }
    for (BoardVo boardVo : boardVoList) {
        int userNo = boardVo.getUser_no();
        UserVo userVo = userDAO.getUserByNo(userNo);
        CategoryVo categoryVo = boardDAO.getCategoryByNo(boardVo.getCategory_no());
        int totalLikeCount = boardDAO.getTotalLikeCount(boardVo.getBoard_no());

        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("boardVo", boardVo);
        map.put("userVo", userVo);
        map.put("categoryVo", categoryVo);
        map.put("totalLikeCount", totalLikeCount);

        data.add(map);
    }
    return data;
}


    //  게시글 검색 카테고리 목록
    @Override
    @LogException
    public List<SearchCategoryVo> getBoardSearchCategoryList() {
        return boardDAO.getBoardSearchCategoryList();
    }

1.5 게시글 검색 컨트롤러 구현

 

src/기본패키지/board/controller 패키지안에 BoardController 클래스 파일 안에 아래와 같이 내용을 수정 해주세요.

 

//  게시글 목록 페이지
@PostMapping(value = "postingList")
@LogException
public String postingList(@RequestParam(value = "category_no", defaultValue = "0") int category_no, Model model, @RequestParam(value = "search_category_no", defaultValue = "0") int search_category_no, @RequestParam(value = "keyword", defaultValue = "") String keyword) {

    ArrayList<HashMap<String, Object>> dataList = boardService.getBoardList(category_no, search_category_no, keyword);
    model.addAttribute("category_no", category_no);
    model.addAttribute("dataList", dataList);
    model.addAttribute("list", boardService.getBoardSearchCategoryList());
    model.addAttribute("search_category_no", search_category_no);
    model.addAttribute("keyword", keyword);

    return "board/postingList";
}

위 소스코드에서 주목해서 봐야될 부분은

@RequestParam(value = "search_category_no", defaultValue = "0") int search_category_no)

@RequestParam(value = "keyword" defaultValue = "") String keyword)

 

두개의 부분입니다. 먼저 첫번쨰 부분은 게시글 검색시 옵션 입니다. (어떤 조건으로 검색할건지) 두번째 부분은 검색어 입니다.

사용자가 검색을 하지 않앗을 경우 기본적으로 각 변수값에 0과 "" 의 값이 할당 됩니다.

 

그 다음으로 주목해서 봐야될 부분은 

model.addAttribute("search_category_no", search_category_no);

model.addAttribute("keyword", keyword);

 

두개의 부분입니다. 각 부분을 설명하자면 검색 이후에 출력된 페이지에서 검색 조건을 유지 하기 위한 부분입니다.

 

1.6 게시글 검색 뷰 페이지 구현(JSP)

 

web-inf/views/board 패키지안에 postingList.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">

            <form name="detailsForm" role="form" method="post">
                <input type="hidden" id="boardNo" name="board_no" value="">
            </form>

            <form name="writeForm" role="form" method="post">
                <input type="hidden" id="category" name="category_no" value="">
            </form>

            <div class="box-header with-border">

            </div>
            <div class="box-body">
                <table class="table table-bordered">
                    <thead>
                    <tr>
                        <th class="col-xs-1">글 번호</th>
                        <th class="col-xs-1">카테고리</th>
                        <th class="col-xs-2">제목</th>
                        <th class="col-xs-2">작성자</th>
                        <th class="col-xs-2">작성일</th>
                        <th class="col-xs-1">조회수</th>
                        <th class="col-xs-1">좋아요</th>
                    </tr>
                    </thead>
                    <tbody>
                    <c:forEach items="${dataList }" var="data">
                        <tr>
                            <td>${data.boardVo.board_no}</td>
                            <td>${data.categoryVo.category_name}</td>
                            <td><a
                                    href="javascript:goPage(${data.boardVo.board_no});">${data.boardVo.board_title }</a>
                                <span class="badge bg-teal"><i class="fa fa-comment-o"></i> + ${data.totalCommentCount}</span>
                            </td>
                            <td>${data.userVo.user_nickname}</td>
                            <td><fmt:formatDate value="${data.boardVo.board_write_date }"
                                                pattern="yyyy:MM:dd: HH:mm:ss"/></td>
                            <td><span class="badge bg-red">${data.boardVo.board_view_count}</span></td>
                            <td><span class="badge bg-teal">${data.totalLikeCount}</span></td>
                        </tr>
                    </c:forEach>
                    </tbody>
                </table>
            </div>

            <div class="box-footer">
                <form id="searchForm" action="../board/postingList" method="post">
                    <input type="hidden" name="category_no" value="${category_no}">
                    <div class="form-group col-sm-2">
                        <select name="search_category_no" class="form-control" id="categoryList">
                            <c:forEach items="${list}" var="search">
                                <c:choose>
                                    <c:when test="${search_category_no == search.search_category_no}">
                                        <option value="${search.search_category_no}" selected>
                                                ${search.search_type}
                                        </option>
                                    </c:when>
                                    <c:otherwise>
                                        <option value="${search.search_category_no}">
                                                ${search.search_type}
                                        </option>
                                    </c:otherwise>
                                </c:choose>
                            </c:forEach>
                        </select>
                    </div>
                    <div class="form-group col-sm-10">
                        <div class="input-group">
                            <input type="text" class="form-control" name="keyword" id="keywordInput"
                                   value="${keyword}" placeholder="검색어">
                            <span class="input-group-btn">
                                    <button type="submit" class="btn btn-primary btn-flat" id="searchBtn">
                                        <i class="fa fa-search"></i> 검색
                                    </button>
                                </span>
                        </div>
                    </div>
                </form>
            </div>

            <div class="box-footer">
                <div class="pull-right">
                    <a class="btn btn-success btn-flat" href="javascript:writePosting(${category_no});">
                        <i class="fa fa-pencil"></i> 글쓰기
                    </a>
                </div>
            </div>
        </section>
    </div>

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

</div>

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

1.7 게시글 검색 화면 확인

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

댓글