110일차 새 프로젝트 시작

기능을 한줄씩 나누는게 힘들어보여서 기능 티켓별로 역할 나누기로결정하였다.

일단 이번 나의 프로젝트의 목표는 crud를 restfulapi로 해보기이다.

ajax요청으로 값들 불러오기

1.컨트롤러

여느때처럼 페이징관련 정보를 받고 list를 반환한다.
그러나 ResponseEntity에 담아서 noticeList만 api로 반환한다.
화면을 요청하는 것은 원래의 컨트롤러에서 요청만 한다.
NoticeController와 NoticeControllerAPI나누어서 설계를 하였다.

@Controller
@RequestMapping("/customer/notice/")
@RequiredArgsConstructor
@Slf4j
public class NoticeController {

    @GetMapping("list")
    public String list() {
        return "customer/notice/list";
    }
}

@Controller
@RequestMapping("/customer/notice/api/")
@RequiredArgsConstructor
@Slf4j
public class NoticeControllerAPI {

    private final NoticeService noticeService;

    @GetMapping("list")
    public ResponseEntity<List<Notice>> getList(
            @RequestParam(value = "search", required = false) String search,
            @RequestParam(value = "type", required = false) String type,
            @RequestParam(value = "page", defaultValue = "1") String page) {
        // 비즈니스 로직을 수행하여 Notice 리스트를 가져오는 코드
        List<Notice> noticeList = noticeService.getNoticeList(search, type);
        log.info("log: {}, {}, {}", search, type, page);
        return ResponseEntity.ok(noticeList);
    }
}

2. 서비스

@Service
@Slf4j
@RequiredArgsConstructor
public class NoticeServiceImpl implements NoticeService {

    private final NoticeMapper noticeMapper;

    @Override
    public List<Notice> getNoticeList(String search, String type) {
        List<Notice> noticeList = noticeMapper.getNoticeList(search, type);
        return noticeList;
    }
}

3. 매퍼

@Mapper
public interface NoticeMapper {

    List<Notice> getNoticeList(String search, String type);
}

<!-- notice목록 불러오는 resultMap-->
    <resultMap type="com.project.careerfair.domain.Notice"
               id="NoticeMap">
        <id column="notice_id" property="noticeId"/>
        <result column="title" property="title"/>
        <result column="content" property="content"/>
        <result column="created" property="created"/>
        <result column="member_id" property="memberId"/>
        <result column="hit" property="hit"/>
    </resultMap>

    <select id="getNoticeList" resultMap="NoticeMap">
        <bind name="pattern" value="'%' + search + '%'"/>
        SELECT * FROM NOTICE
        <where>
            <if test="type eq 'all'">
                (title LIKE #{pattern} OR content LIKE #{pattern} OR member_id LIKE #{pattern})
            </if>
            <if test="type eq 'title'">
                OR title LIKE #{pattern}
            </if>
            <if test="type eq 'content'">
                OR content LIKE #{pattern}
            </if>
            <if test="type eq 'writer'">
                OR member_id LIKE #{pattern}
            </if>
        </where>
        ORDER BY notice_id DESC LIMIT 0, 10
    </select>

4. view ajax

이번에는 jquery를 사용한 ajax처리가 아니라
자바스크립트가 기본적으로 제공하는 fetchapi를 사용해보았다.
fetch api는 promise타입 객체를 리턴하여 응답온 객체를 반환한다.
검색시 값들을 객체로 포장해서 파라미터로 사용할 것이다.

const params = new URLSearchParams(requestData).toString();
String으로 만들고
fetch(`/customer/notice/api/list?${params}`)
이런식으로 넣으면 파라미터로 담아져서 요청을 하게 된다.
주소창에는 보이진 않지만 get요청에 파라미터로 담아져서 보내진 것을 볼 수 있다.

listView();

let searchValue = "";
let typeValue = "";
let pageValue = "";

function listView(searchValue, typeValue, pageValue) {
    const search = searchValue;
    const type = typeValue;

    const requestData = {
        search: searchValue,
        type: typeValue,
        page : pageValue
    };

    // URL 매개변수로 데이터 직렬화
    const params = new URLSearchParams(requestData).toString();

    fetch(`/customer/notice/api/list?${params}`)
        .then(response => response.json())
        .then(noticeList => {
            // 이하 코드는 동일합니다.
            const tbody = document.querySelector("#noticeTable tbody");
            tbody.innerHTML = "";

            noticeList.forEach(notice => {
                const noticeHtml = `
                    <tr>
                        <td>${notice.noticeId}</td>
                        <td><a href="#">${notice.title}</a></td>
                        <td>${notice.created}</td>
                        <td>${notice.memberId}</td>
                        <td>${notice.hit}</td>
                    </tr>
                `;
                tbody.insertAdjacentHTML('beforeend', noticeHtml);
            });
        })
        .catch(error => {
            console.error("Error:", error);
        });
}

const searchBtn = document.querySelector("#search-btn");
searchBtn.addEventListener("click", function () {
    const search = document.querySelector("#search");
    searchValue = search.value;

    const type = document.querySelector("#type");
    typeValue = type.value;

    listView(searchValue, typeValue);
});

const pageLinks = document.querySelectorAll(".page-link.page-num");

pageLinks.forEach(function (linkElement) {
    linkElement.addEventListener("click", function (event) {
        const page = linkElement.getAttribute("data-page");
        pageValue = page;
        listView(searchValue, typeValue, pageValue);
        event.preventDefault();
    });
});

5. view

기본화면을 만들고 동적으로 불러온 내용을 만들어서 보여준다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
          integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
          crossorigin="anonymous" referrerpolicy="no-referrer"/>
    <style>
        .table thead th {
            text-align: left;
        }

        .table tbody td {
            text-align: left;
        }

        .table tbody p {
            text-align: left;
        }

        a {
            text-decoration: none;
            color: black;
        }

        .custom-div {
            max-width: 800px;
            margin: 0 auto;
        }

    </style>
</head>
<body>
<div class="container-lg mt-3 custom-div">
    <div class="d-flex justify-content-end">
        <div>
            <div class="input-group">
                <select id="type" class="form-select flex-grow-0" style="width: 120px;">
                    <option value="all">전체</option>
                    <option value="title">제목</option>
                    <option value="writer">작성자</option>
                    <option value="content">내용</option>
                </select>
                <input id="search" class="form-control" type="search" placeholder="Search" aria-label="Search">
                <button id="search-btn" class="btn btn-outline-success" type="submit">
                    <i class="fa-solid fa-magnifying-glass"></i>
                </button>
            </div>
        </div>
        <div class="ms-5">
            <a class="btn btn-primary" href="#">글작성하기</a>
        </div>
    </div>

    <table id="noticeTable" class="table table-bordered ">
        <thead>
        <tr>
            <th>번호</th>
            <th>제목</th>
            <th>작성일</th>
            <th>작성자</th>
            <th>조회수</th>
        </tr>
        </thead>
        <tbody class="table-group-divider">
        </tbody>
    </table>

    <div class="container-lg">
        <div class="row">
            <nav aria-label="Page navigation example">
                <ul class="pagination justify-content-center">

                    <li class="page-item">
                        <a class="page-link" href="#">
                            <i class="fa-solid fa-angle-left"></i>
                        </a>
                    </li>

                    <c:forEach begin="1" end="10" var="pageNum">
                        <li class="page-item">
                            <a class="page-link page-num" href="#" data-page="${pageNum}">${pageNum}</a>
                        </li>
                    </c:forEach>

                    <li class=" page-item">
                        <a class="page-link" href="#">
                            <i class="fa-solid fa-angle-right"></i>
                        </a>
                    </li>
                </ul>
            </nav>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe"
        crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"
        integrity="sha512-pumBsjNRGGqkPzKHndZMaAG+bir374sORyzM3uulLV14lN5LyykqNk8eEeUlUkB3U0M4FApyaHraT65ihJhDpQ=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="/js/customer/notice/list.js"></script>
</body>
</html>

2023.07.05

처음으로 fetchapi를 활용해보았다.
비슷한 문법이지만 새롭기 때문에 다루는게 익숙하지 않다.
따로 문서를 작성해서 비교할 필요가 있다.

+ Recent posts