65일차

7. create

글 삽입하기 강사님ver
전날 혼자서 작성했엇다. 수정사항만 작성하고자 한다.
insert 컨트롤러 get -> insert form -> insert 컨트롤러 post
-> service -> mapper -> list

7.1 view

add.jsp로 포워드
action은 빈값으로 두면 같은 곳으로 요청하기 때문에 작성안해도된다.

7.2 controller

board javabean으로 받거나 requestparam으로 받아주면된다.
여러값이니 Board javabean으로 받는다.
완료 시 list로 redirect

7.3 service

mapper에게 insert메소드를 실행시키게 한다.

7.4 mapper

insert문을 작성하면된다.
파라미터로 받은 javabean의 프로퍼티명을 넣어주면된다.

간단한 코드이기에 수정사항이 없이 거의 비슷햇다.

7.5 수정

mapper의 옵션에 자동증가한 컬럼의 값을 알아와서
redirect시 작성한 글로 가지게 하고싶다.
@Options어노테이션을 작성하면 알아서 매핑해준다.
Board객체 전달만 하는게아니라 실행시 다시 가져온다.
실패시 redirect할때 작성햇던 내용이 그대로 남아있게 하고 싶다면
RedirectAttributes에 board객체를 보내주고 redirect를 하면된다.

같은 참조값을 가지고 있기때문에 mapper의 메소드를 한순간 매핑을 해주는 옵션을 넣은것이다.
같은 객체이기때문에 다시 컨트롤러로 돌아왓을때 getId를 할 수 있는 것이다.

@Insert("INSERT INTO Board (title, body, writer) "
        + "VALUES (#{title}, #{body}, #{writer})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Board board);

@PostMapping("add")
public String addProcess(Board board, RedirectAttributes rttr) {
    // 새 게시물 db에 추가
    boolean ok = service.create(board);

    if (ok) {
        rttr.addFlashAttribute("success", "insertScucess");
        // return "redirect:/list";
        return "redirect:/detail/" + board.getId();
    } else {
        rttr.addFlashAttribute("fail", "insertFail");
        rttr.addFlashAttribute("board", board);
        return "redirect:/add";
    }
}

8. view 디자인

8.1 navbar

페이지 상단에 글쓰기, list로 갈수 있는 navbar를 만들것이다.
각페이지마다 작성해도되지만
자주 사용되는 element를 저장하고 사용하는 것이 tag를 사용할 수 있다.

WEB-INF에 tags폴더에 넣어서 태그를 사용할 수 있게 넣어준다.
사용하기 위해서는 view페이지에서 taglib를 추가해줘야한다.
prefix와 tag의 위치를 지정해준다.

<%@ taglib prefix="my" tagdir="/WEB-INF/tags" %>

<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="jakarta.tags.core"%>

<nav class="navbar navbar-expand-lg bg-body-tertiary">
  <div class="container-lg">
    <a class="navbar-brand" href="/list">스프링 게시판</a> <!-- 프로젝트 브랜드 -->
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link" href="/list">목록</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="/add">글쓰기</a>
        </li>
      </ul>
      <form class="d-flex" role="search">
        <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
        <button class="btn btn-outline-success" type="submit">Search</button>
      </form>
    </div>
  </div>
</nav>

현재 일치하는 메뉴가 활성화되도록하기
tag에 attribute를 넣을 수 있엇다. 쓰려면 태그에 명시를 해줘야한다.

<my:navBar current="add"/>

태그안에서는 el으로 넘어오기 때문에 그냥 사용하면된다.
if태그를 사용해도되지만 편하게 EL의 삼항연산을 사용하자.

<%@ attribute name="current" %>

<li class="nav-item">
  <a class="nav-link ${current eq 'list' ? 'active' : '' }" href="/list">목록</a>
</li>
<li class="nav-item">
  <a class="nav-link ${current eq 'add' ? 'active' : ''}" href="/add">글쓰기</a>
</li>

8.2 글쓰기 폼

https://getbootstrap.com/docs/5.3/forms/overview/
폼관련 부트 스트랩

화면크기에 따라 다른 크기 row class가진 div사용해서 상자에넣기
화면크기에따라 col몇개 칸먹을건지 지정할 수 있다.
부트스트랩의 flex박스에서 설정

https://getbootstrap.com/docs/5.3/utilities/flex/ 
<div class="row">
<div class="col-12 col-md-8 col-lg-6">

<div class="container-lg">
    <div class="row justify-content-center">
        <div class="col-12 col-md-8 col-lg-6">
            <h1>게시물 작성</h1>
            <form method="post">
                <div class="mb-3">
                    <label for="titleInput" class="form-label">제목</label>
                    <input type="text" id="titleInput" class="form-control"  name="title" value="${board.title}"/>
                </div>
                <div class="mb-3">
                    <label for="bodyInput" class="form-label">내용</label>
                    <textarea rows="10" name="body" id="bodyInput" class="form-control" >${board.body}</textarea>
                </div>
                <div class="mb-3">
                    <label for="writerInput" class="form-label">작성자</label>
                    <input type="text" id="writerInput" name="writer" class="form-control" value="${board.writer}"/>
                </div>
                <div class="mb-3">
                    <input class="btn btn-primary" type="submit" value="등록" />
                </div>
            </form>
        </div>
    </div>
</div>

8.3 게시물보기

<div class="container-lg">
    <div class="row justify-content-center">
        <div class="col-12 col-md-8 col-lg-6">
            <h1>${board.id }번게시물</h1>
            <div>
                <div class="mb-3">
                    <label for="" class="form-label">제목</label>
                    <input type="text" class="form-control" value="${board.title }" readonly />
                </div>
                <div class="mb-3">
                    <label for="" class="form-label">본문</label>
                    <textarea rows="10" name="body" class="form-control">${board.body}</textarea>
                </div>
                <div class="mb-3">
                    <label for="" class="form-label">작성자</label>
                    <input type="text" class="form-control" value="${board.writer }" readonly />
                </div>
                <div class="mb-3">
                    <label for="" class="form-label">작성일시</label>
                    <input type="text" readonly class="form-control" value="${board.inserted }" />
                </div>
                <div>
                    <a class="btn btn-secondary" href="/update/${board.id}">수정</a>
                    <button id="removeButton" class="btn btn-danger" form="removeForm" type="submit">삭제</button>
                </div>
            </div>
        </div>
    </div>
</div>

8.4 수정

<div class="container-lg">
    <div class="row justify-content-center">
        <div class="col-12 col-md-8 col-lg-6">
            <h1>${board.id } 게시물 수정</h1>
            <form method="post">
                <input type="hidden" name="id" value="${board.id }"/>
                <div class="mb-3">
                    <label for="titleInput" class="form-label">제목</label>
                    <input type="text" id="titleInput" name="title" value="${board.title }"/>
                </div>
                <div class="mb-3">
                    <label for="bodyInput" class="form-label">본문</label>
                    <textarea rows="10" name="body" id="bodyInput" class="form-control">${board.body }</textarea>
                </div>
                <div class="mb-3">
                    <label for="writerInput" class="form-label">작성자</label>
                    <input type="text" id="writerInput" name="writer" class="form-control" value="${board.writer}" />
                </div>
                <div class="mb-3">
                    <label for="writerInput" class="form-label">작성일시</label>
                    <input type="text" name="inserted" value="${board.inserted }" readonly/>
                </div>
                <div class="mb-3">
                    <input class="btn btn-secondary" type="submit" value="수정" />
                    <a class="btn btn-secondary" href="/list">목록으로가기</a>
                </div>
            </form>
        </div>
    </div>
</div>

8.5 alert

addAttribute로 requestParam으로 넘어가게 해서 새로고침하면 계속뜨게 되었다.
addFlashAttribute하면 model에 붙어가서 새로고침하면 뜨지 않는다.
나는 이미 해놓았는데 처리 결과를 다르게 할 수 있다.
두개로 넣어서 메시지마다 구분을 해주자.

수정 혹은 삭제 시 기본 alert을 사용햇는데 변경해보기
https://getbootstrap.com/docs/5.3/components/alerts/

@PostMapping("remove")
public String update(Integer id, RedirectAttributes rttr) {
    boolean ok = service.remove(id);
    if (ok) {
        rttr.addFlashAttribute("success", "removeSucess");
        rttr.addFlashAttribute("message", id + "번 게시물이 삭제되었습니다.");
        return "redirect:/list";
    } else {
        rttr.addFlashAttribute("fail", "removeFail");
        return "redirect:/detail/" + id;
    }
}

<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="jakarta.tags.core"%>

<div class="container-lg">
    <div class="alert alert-warning alert-dismissible fade show" role="alert">
      ${message}
      <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
    </div>    
</div>

<c:if test="${success eq 'removeSucess'}">
    <my:alert/>
</c:if>

8.6 confirm창

삭제 시 modal이 나오게 하기
js로 form이 작동하는 것이 아닌 modal에서 form이 작동하도록 바꿔줄수 있다.

<button id="removeButton" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteConfirmModal" >삭제</button>

<!-- Modal -->
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5" id="exampleModalLabel">삭제 확인</h1>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        삭제 하시겠습니까?
      </div>
      <div class="modal-footer">
        <button type="submit" class="btn btn-danger" form="removeForm">삭제</button>
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
      </div>
    </div>
  </div>
</div>

8.7 페이지 네이션 처리

페이지를 누르면 쿼리스트링에 페이지가 붙어서 간다.
처음들어갈땐 1페이지 인데 적혀있지 않다.

목록을 보여주는 것이다.
http://localhost:8080/list?page=? 경로로 요청하게 될 것이다.

프로젝트 이전에 연습부터 해보자.

경로 : sub26/link1/?page=3 -> 3페이지
경로 : sub26/link1/?page=1 -> 1페이지
경로 : sub26/link1 -> 1페이지

requestparam으로 page를 받아오자.
기본값으로 if문을 작성할 수 있지만 requestparam의 속성으로 defaultValue를 작성해줄수 있다.

@GetMapping("link1")
public void method1(@RequestParam(value = "page", defaultValue = "1") Integer page) {
    System.out.println(page);
}

1페이지 페이지 당 20개 레코드를 나오게하려면 LIMIT절을 사용하면된다.

SELECT * FROM Customers
ORDER BY CustomerID DESC LIMIT 0, 20; -- 1페이지

SELECT * FROM Customers
ORDER BY CustomerID DESC LIMIT 20, 20; -- 2페이지

SELECT * FROM Customers
ORDER BY CustomerID DESC LIMIT 40, 20; -- 3페이지

페이지 시작번호는 0부터시작해서 20씩늘어나는 등차수열이다
n = (page-1)*20

Mapper의 쿼리는
SELECT * FROM ORDER BY CustomerID DESC LIMIT #{startIndex}, 20이 되면된다.

@GetMapping("link1")
public String method1(@RequestParam(value = "page", defaultValue = "1") Integer page, Model model) {
    Integer startIndex = (page - 1) * 20;

    List<Customer> list = mapper.listCustomer(startIndex);
    model.addAttribute("customerList", list);

    return "/sub13/link1";
}

@Select("SELECT * FROM Customers ORDER BY CustomerID DESC LIMIT #{startIndex}, 20")
List<Customer> listCustomer(Integer startIndex);

부트 스트랩의 pagenation을 사용하면 쉽게만들 수 있다.
https://getbootstrap.com/docs/5.3/components/pagination/
각페이지로 가면되는데 forEach를 통해서 가면된다.

jstl의 url태그를 사용하면 파라미터 이름으로 값을 넣은 링크를 만들어줄 수 있다.
그냥 작성해도 되는데 후에 파라미터 추가되면 쉽게 할 수 있도록 만들어서 넣은 것이다.

<nav aria-label="Page navigation example">
  <ul class="pagination">
    <c:forEach begin="1" end="20" var="pageNumber">
        <c:url value="/sub26/link1" var="pageLink" >
            <c:param name="page" value="${pageNumber }"></c:param>
        </c:url>
        <li class="page-item">
        <a class="page-link" href="${pageLink}">${pageNumber}</a></li>
    </c:forEach>
  </ul>
</nav>

마지막 페이지와 시작 페이지를 알아야한다.
110 1120 로 begin과 end값이 계속 바뀌어야한다.

현재 페이지를 기준으로 시작하는 번화와 끝나는 번호를 알아야한다.

현재 페이지 1~ 10 왼쪽번호 1 오른쪽번호 10
현재 페이지 11~ 20 왼쪽번호 11 오른쪽번호 20
현재 페이지 21~ 30 왼쪽번호 21 오른쪽번호 30

왼쪽번호는 (현재페이지-1) / 10 * 10 + 1
오른쪽번호는 왼쪽 +9

이런일들이 서비스에서 일어나는 것이다.

begin을 왼쪽번호 end를 오른쪽 번호를 넣어주면된다.

@GetMapping("link1")
public String method1(@RequestParam(value = "page", defaultValue = "1") Integer page, Model model) {

    // 쿼리에서 사용하는 시작 인덱스
    Integer startIndex = (page - 1) * 20;

    // 페이지 네이션 가장 왼쪽번호 구하기
    Integer leftPageNumber = (page - 1) / 10 * 10 + 1;
    Integer rightPageNumber = leftPageNumber + 9;

    List<Customer> list = mapper.listCustomer(startIndex);

    model.addAttribute("customerList", list);
    model.addAttribute("leftPageNumber", leftPageNumber);
    model.addAttribute("rightPageNumber", rightPageNumber);
    return "/sub13/link1";
}

8.8 이전 다음

이전 페이지와 다음 페이지로 이동하는 것을 만들어보자.
이전 버튼을 누르면 page를 leftPageNumber -10으로 가게 해주자.
다음 버튼을 누르면 page를 rightPageNumber + 1로 가게 해주면된다.

JSP 에서 사용하기 위해 model에 담아서 보내주면된다.

<li class="page-item">
    <c:url value="/sub26/link1" var="pageLink" >
        <c:param name="page" value="${prevPageNumber }"></c:param>
    </c:url>
    <a class="page-link" href="${pageLink}">이전</a>
</li>

<li class="page-item">
    <c:url value="/sub26/link1" var="pageLink" >
        <c:param name="page" value="${nextPageNumber}"></c:param>
    </c:url>
    <a class="page-link" href="${pageLink}">다음</a>
</li>

8.9 시작 페이지와 마지막페이지

이전은 1페이지 이전으로 가려하면 못가야하고
마지막 페이지는 마지막이상으로 가려하면 못가게 해야한다.
버튼 자체를 없애버리자.

이전버튼은 prevPageNumber가 1보다 크거나 같을때만 버튼이 나오면된다.

<c:if test="${prevPageNumber ge 1 }">
    <li class="page-item">
        <c:url value="/sub26/link1" var="pageLink" >
            <c:param name="page" value="${prevPageNumber}"></c:param>
        </c:url>
        <a class="page-link" href="${pageLink}">이전</a>
    </li>
</c:if>

다음 버튼은 마지막 페이지를 구해야한다.
한페이지당 20개씩 보여주려면 레코드 수가
0~20 마지막 페이지 1
~40 마지막페이지 2
~60 마지막페이지 3
~n 마지막 페이지는 게시글 개수/20이다.
짜투리가 남는다면 (게시글 개수 -1) / 20 +1을 해주면된다.

Integer numOfRecord = mapper.countAll();

Integer lastPage = 0;
if (numOfRecord / 20 == 0) {
    lastPage = numOfRecord / 20;
} else {
    lastPage = numOfRecord / 20 + 1;
}

<c:if test="${nextPageNumber le lastPageNumber}">
     <li class="page-item">
        <c:url value="/sub26/link1" var="pageLink" >
            <c:param name="page" value="${nextPageNumber}"></c:param>
        </c:url>
        <a class="page-link" href="${pageLink}">다음</a>
    </li>
</c:if>

없는 페이지도 안나오게 해주자.
pageNumber가 lastPageNumber보다 작거나 같을때까지만 나오면된다.
service에서 처리해주자. 여기서는 컨트롤러

rightPageNumber = Math.min(rightPageNumber, lastPageNumber);

마지막으로 현재 페이지를 active로 해주자.
파라미터로 받으면 없을때를 알 수없으니 컨트롤러에서 값을 보내줘야한다.

model.addAttribute("currentPageNumber", page);

view에서는 el 3항연산자로 값을 넣어주면된다.

${pageNumber eq currentPageNumber ? 'active': '' }

맨뒤페이지와 맨앞페이지는 페이지를 각각 1과 lastPageNumber로 가면된다.

2023.04.27

인라인 element
htttp - 인라인 element목록 다지우면된다.
페이징은 기본이라 이해가 안된다면 외워야 한다.
등차수열 공식으로 되는 것을 이해해야한다.

'국비 > Project - 1 게시판' 카테고리의 다른 글

2023.05.03 69일차 Project  (0) 2023.05.03
2023.05.02 68일차 Project  (0) 2023.05.02
2023.05.01 67일차 Project  (0) 2023.05.01
2023.04.28 66일차 Project  (0) 2023.05.01
2023.04.26 64일차 Project  (0) 2023.04.26

+ Recent posts