course 2021/Spring

Spring07 - 12/10

코딩하는토끼 2021. 12. 23. 19:58

지난시간 

BoardMapperTest

package org.zerock.mapper.project1;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.zerock.domain.project1.BoardVO;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardMapperTest {
	@Autowired
	public BoardMapper mapper;

	@Test
	public void mapperTest() {
		assertNotNull(mapper);
	}
	
	@Test
	public void insertTest() { 
		BoardVO vo = new BoardVO();
		vo.setTitle("test 용 제목");
		vo.setContent("test 용 본문");
		vo.setWriter("tester");

		int cnt = mapper.insert(vo);

		assertEquals(1, cnt);
	}

}

test 메소드는 public, void 로 설정, @Test 어노테이션을 붙여주기

실행하면 모든 테스트메소드가 실행되며,

하나만 실행하고 싶은 경우 메소드 우클릭 - Run As - JUnit Test

 

insertAndSelectTest 메소드

	@Test
	public void insertAndSelectTest() {
		BoardVO vo = new BoardVO();
		vo.setTitle("test 용 제목" + Math.random());
		vo.setContent("테스트 용 본문" + Math.random());
		vo.setWriter("tester");

		mapper.insert(vo);

		assertNotNull(vo.getId());
		assertNotEquals(0, vo.getId().intValue());

		BoardVO lastInserted = mapper.read(vo.getId());

		assertEquals(vo.getTitle(), lastInserted.getTitle());
		assertEquals(vo.getContent(), lastInserted.getContent());
		assertEquals(vo.getWriter(), lastInserted.getWriter());

	}

 

* 만약 테스트 실패하면 다음의 모양을 클릭하여 콘솔창에서 로그 확인해본다

 

updateTest 메소드

	@Test
	public void updateTest() {
		BoardVO vo = new BoardVO();
		vo.setTitle("test 용 제목" + Math.random());
		vo.setContent("테스트 용 본문" + Math.random());
		vo.setWriter("tester");

		mapper.insert(vo);

		assertNotNull(vo.getId());
		assertNotEquals(0, vo.getId().intValue());
		
		String newTitle = "update 용 제목" + Math.random();
		String newContent = "update 용 본문" + Math.random();

		vo.setTitle(newTitle);
		vo.setContent(newContent);
		
		mapper.update(vo);
		
		BoardVO updatedVO = mapper.read(vo.getId());
		
		assertEquals(newTitle, updatedVO.getTitle());
		assertEquals(newContent, updatedVO.getContent());
	}

 

> inserted, updated 컬럼 not null 로 변경하기

mysql > preferences > SQL Editor 에서 Safe Updates (rejects UPDATEs and DELETEs with no restrictions) 체크 해제

USE test;

DELETE FROM Board WHERE  (id) IN (SELECT id FROM Board WHERE inserted IS NULL);
DELETE FROM Board WHERE updated IS NULL;

ALTER TABLE Board MODIFY COLUMN inserted DATETIME NOT NULL DEFAULT NOW();
ALTER TABLE Board MODIFY COLUMN updated DATETIME NOT NULL DEFAULT NOW();

mysql > preferences > SQL Editor 에서 Safe Updates (rejects UPDATEs and DELETEs with no restrictions) 다시 체크

 

insertAndDeleteTest 메소드

	@Test
	public void insertAndDeleteTest() {
		BoardVO vo = new BoardVO();
		vo.setTitle("삭제용 제목");
		vo.setContent("삭제용 본문");
		vo.setWriter("tester");
		
		int cnt = mapper.insert(vo);
		
		assertEquals(1, cnt);
		
		cnt = mapper.delete(vo.getId());
		
		assertEquals(1, cnt);
		
		BoardVO deleted = mapper.read(vo.getId());
		assertNull(deleted);
	}

 

getListTest 메소드

	@Test
	public void getListTest() {
		List<BoardVO> list = mapper.getList();
		assertNotNull(list);
		assertTrue(list.size() > 0);
		
		BoardVO vo = new BoardVO();
		vo.setTitle("list 테스트용 ");
		vo.setContent("list 테스트용 본문");
		vo.setWriter("tester");
		
		mapper.insert(vo);
		
		List<BoardVO> list2 = mapper.getList();
		
		assertEquals(list.size() + 1, list2.size());
		
		for (BoardVO item : list2) {
			assertNotNull(item.getId());
			assertNotNull(item.getTitle());
			assertNotNull(item.getContent());
			assertNotNull(item.getWriter());
			assertNotNull(item.getInserted());
			assertNotNull(item.getUpdated());
		}
	}

보통 controller 는 servlet-context.xml 에, service 와 mapper 는 root-context.xml 에 저장


org.zerock.service.project1 패키지 생성

패키지 우클릭 - new - class (name : BoardService)

BoardService

package org.zerock.service.project1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zerock.mapper.project1.BoardMapper;

import lombok.Setter;

@Service
public class BoardService {
	
	@Setter(onMethod_=@Autowired)
	private BoardMapper mapper;

}

 

src/test/java 폴더 안에 org.zerock.service.project1 패키지 생성 

패키지 우클릭 - new - JUnit Test Case (name : BoardServiceTest)

BoardServiceTest

package org.zerock.service.project1;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardServiceTest {

	@Autowired
	public BoardService service;
	
	@Test
	public void hasService() {
		assertNotNull(service);
	}


}

 

> root-context.xml 파일에 다음 코드를 추가

 

→ BoardServiceTest 실행하면 잘 작동한다

 

BoardService (작성 완료)

package org.zerock.service.project1;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zerock.domain.project1.BoardVO;
import org.zerock.mapper.project1.BoardMapper;

import lombok.Setter;

@Service
public class BoardService {
	
	@Setter(onMethod_=@Autowired)
	private BoardMapper mapper;
	
	public boolean register(BoardVO board) {
		return mapper.insert(board) == 1;
	}
	
	public BoardVO get(Integer id) {
		return mapper.read(id);
	}
	
	public boolean modify(BoardVO board) {
		return mapper.update(board) == 1;
	}
	
	public boolean remove(Integer id) {
		return mapper.delete(id) == 1;
	}
	
	public List<BoardVO> getList() {
		return mapper.getList();
	}

}

 


! 참고 : @Setter 어노테이션은

import org.springframework.beans.factory.annotation.Autowired; 

import 되어있는지 확인해야 함


org.zerock.controller.project1 패키지 생성

패키지 우클릭 - new - class (name : BoardController)

BoardController

package org.zerock.controller.project1;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.zerock.domain.project1.BoardVO;
import org.zerock.service.project1.BoardService;


import lombok.Setter;

@Controller
@RequestMapping("/board")
public class BoardController {
	
	@Setter(onMethod_=@Autowired)
	private BoardService service;
	
	@GetMapping("/list")
	public void list(Model model) {
		//3. business logic
		//게시물 (Board) 목록 조회 
		List<BoardVO> list = service.getList();
		
		//4. add attribute
		model.addAttribute("list", list);
		
		//5. forward / redirect
		//jsp path : /WEB-INF/views/board/list.jsp
	}
	
}

@GetMapping("/list") - 이번에는 RequestMapping 말고 GetMapping 사용 (get 방식으로 요청이 오면 실행)


WEB-INF > views 안에 board 폴더 생성, 그 안에 list.jsp 파일 생성 (new - other - jsp)

list.jsp

<body>
  <!-- .container>.row>.col>h1{게시물 목록} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>게시물 목록</h1>
        <!-- table.table>thead>tr>th*4^^tbody -->
        <table class="table">
          <thead>
            <tr>
              <th>
                <i class="fab fa-slack-hash"></i>
              </th>
              <th>제목</th>
              <th><i class="fas fa-user"></i></th>
              <th>작성일</th>
            </tr>
          </thead>
          <tbody>
            <c:forEach items="${list }" var="board">
              <tr>
                <td>${board.id }</td>
                <td>${board.title }</td>
                <td>${board.writer }</td>
                <td>${board.inserted }</td>
              </tr>
            </c:forEach>
          </tbody>
        </table>
      </div>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>

</body>

요청 : /board/list


BoardController 추가

    // /board/get?id=10 와 같이 요청옴
	@GetMapping("/get")
	public void get(@RequestParam("id") Integer id, Model model) {
		BoardVO board = service.get(id);
		
		model.addAttribute("board", board);	
	}

@RequestParam("id") 생략가능

경로와 이름이 같은 jsp 파일로 포워딩 ↓

 

views > board 안에 get.jsp 파일 생성

get.jsp

<body>

  <!-- .container>.row>.col>h1{게시물 조회} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>게시물 조회</h1>
        <div class="board-view">
          <!-- .form-group*3>label[for=input$]+input.form-control#input$[readonly] -->
          <div class="form-group">
            <label for="input1">제목</label>
            <input type="text" class="form-control" id="input1" readonly value="${board.title }">
          </div>
          <div class="form-group">
            <label for="input2">내용</label>
            <textarea class="form-control" id="input2" readonly>${board.content }</textarea>
          </div>
          <div class="form-group">
            <label for="input3">작성자</label>
            <input type="text" class="form-control" id="input3" readonly value="${board.nickName }">
          </div>
        </div>
      </div>
    </div>
  </div>

	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
</body>

요청 : /board/get?id=3


그런데 우리는 list 화면에서 제목을 클릭하면

get 화면으로 이어지도록 하고싶음

 

list.jsp 수정(추가)

                <td>
                  <a href="get?id=${board.id }">
                  ${board.title }
                  </a>
                </td>

이제 연결된다


WEB-INF > tags > board 폴더 생성, 그 안에 new - other - tag 검색 - JSP Tag 선택 (name : navBar.tag) 파일 생성

navBar.tag

<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>

<c:url value="/board/list" var="listUrl"></c:url>
<c:url value="/board/register" var="registerUrl"></c:url>

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <ul class="navbar-nav mr-auto">
    <li class="nav-item active">
      <a class="nav-link" href="${listUrl }">목록</a>
    </li>
    <li class="nav-item active">
      <a class="nav-link" href="${registerUrl }">글쓰기</a>
    </li>
  </ul>
</nav>

list.jsp 수정 (추가)

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

<body>
  <b:navBar></b:navBar>
</body>

get.jsp 수정 (추가)

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

<body>
  <b:navBar></b:navBar>
</body>


"글쓰기" 클릭하면 다음과 같이 뜨는데, 

주소가 board/register 이다 

 

BoardController 추가

	@GetMapping("/register")
	public void register() {
		
	}

 

views > board 폴더에 register.jsp 파일 생성

register.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="b" tagdir="/WEB-INF/tags/board"%>
<% request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="<%= request.getContextPath() %>/resource/css/icon/css/all.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">

<title>Insert title here</title>
</head>
<body>
  <b:navBar></b:navBar>
  <!-- .container>.row>.col>h1{게시물 작성} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>게시물 작성</h1>
        <!-- form>.form-group*3>label[for=input$]+input.form-control#input$ -->
        <form method="post">
          <div class="form-group">
            <label for="input1">제목</label>
            <input type="text" class="form-control" id="input1" name="title">
          </div>
          <div class="form-group">
            <label for="input2">내용</label>
            <textarea class="form-control" id="input2" name="content"></textarea>
          </div>
          <div class="form-group">
            <label for="input3">작성자</label>
            <input type="text" class="form-control" id="input3" name="writer">
          </div>

          <button class="btn btn-outline-primary" type="submit">등록</button>
        </form>
      </div>
    </div>
  </div>

	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
</body>
</html>

 

BoardController 추가

	@PostMapping("/register")
	public String register(BoardVO board, RedirectAttributes rttr) {
		
		// 3. business logic
		service.register(board);
		
		// 4. add attribute
		rttr.addFlashAttribute("result", board.getId() + "번 게시글이 등록되었습니다.");
		
		// 5. forward / redirect
		// 책: 목록으로 redirect
		return "redirect:/board/list";
	}

→ 한글도 등록이 가능하도록 하게 하기 위해 (HttpServletRequest req) 작성

	@PostMapping("/register")
	public String register(BoardVO board, RedirectAttributes rttr, HttpServletRequest req) {
		
		// 3. business logic
		service.register(board);
		
		// 4. add attribute
		rttr.addFlashAttribute("result", board.getId() + "번 게시글이 등록되었습니다.");
		
		// 5. forward / redirect
		// 책: 목록으로 redirect
		return "redirect:/board/list";
	}

 

web.xml 에 filter 작성 (교재 243p 코드 그대로 작성)

  <filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  
  <filter-mapping>
    <filter-name>encoding</filter-name>
    <servlet-name>appServlet</servlet-name>
  </filter-mapping>

 

/board/register 

등록하면 자동으로 list 화면으로 리디렉트됨


게시물 조회 창에 수정/삭제 버튼 추가하기

get.jsp 추가

          <a href="modify?id=${board.id }" class="btn btn-outline-secondary">
            수정/삭제
          </a>

버튼 클릭 시 다음과 같은 화면

주소는 board/modify?id=16 (href="modify?id=${board.id }" 로 작성했으므로)

 

수정/삭제 화면 만들기

BoardController 추가

	@GetMapping({"/get", "/modify"})
	public void get(@RequestParam("id") Integer id, Model model) {
		BoardVO board = service.get(id);
		
		model.addAttribute("board", board);
	}

/get 과 코드가 동일하여 한번에 작성하도록 함

 

modify.jsp

<body>
  <b:navBar></b:navBar>
  <!-- .container>.row>.col>h1{게시물 수정} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>게시물 수정</h1>
        <!-- form>.form-group*3>label[for=input$]+input.form-control#input$ -->
        <form method="post">
        <input type="hidden" name="id" value="${board.id }">
          <div class="form-group">
            <label for="input1">제목</label>
            <input type="text" class="form-control" value="${board.title }" id="input1" name="title">
          </div>
          <div class="form-group">
            <label for="input2">내용</label>
            <textarea class="form-control" id="input2" name="content">${board.content }</textarea>
          </div>
          <div class="form-group">
            <label for="input3">작성자</label>
            <input type="text" class="form-control" id="input3" name="writer" value="${board.writer }" readonly>
          </div>

          <button class="btn btn-outline-primary" type="submit">수정</button>
        </form>
      </div>
    </div>
  </div>


	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
</body>

 register.jsp 와 유사하기 때문에 복붙한 뒤 일부 수정했음

주목할 점은, 이미 id 가 있는 게시물이므로 id 를 같이 전달해야 함

→ <input type="hidden" name="id" value="${board.id }"> 작성

 

BoardController 추가

	@PostMapping("/modify")
	public String modify(BoardVO board, RedirectAttributes rttr) {
		
		if (service.modify(board)) {
			rttr.addFlashAttribute("result", board.getId() + "번 게시글이 수정되었습니다.");
		}
		return "redirect:/board/list";
	}

수정된 뒤에 list 로 리디렉트 (게시물 조회로 리디렉트하려면 

rttr.addAttribute("id", board.getId());

return "redirect:/board/get";

와 같이 작성)

 


게시물 수정 창에 삭제 버튼 추가하기

BoardController 추가

	@PostMapping("/remove")
	public String remove(@RequestParam("id") Integer id, RedirectAttributes rttr) {
		
		if (service.remove(id)) {
			rttr.addFlashAttribute("result", id + "번 게시글이 삭제되었습니다.");
		}
		
		return "redirect:/board/list";
	}

 

modify.jsp 추가

<button id="removeSubmitButton" class="btn btn-outline-danger" >삭제</button>

button 은 기본적으로 submit 기능을 하기 때문에 그렇게 하지 않도록 설정해야 함 

위의 button 추가하고 form, 수정 button 에도 각각 id 설정

그리고 아래와 같이 script 작성 (java script)

  <script>
  $(document).ready(function() {
    $("#removeSubmitButton").click(function(e) {
      e.preventDefault(); // 기본 동작을 진행하지 않도록 함.
      $("#modifyForm").attr("action", "remove").submit();
    });
    
    $("#modifySubmitButton").click(function(e) {
      e.preventDefault();
      $("#modifyForm").attr("action", "modify").submit();
    });
  });
  </script>

삭제 버튼을 누르면 게시물이 삭제됨


< bootstrap - modal >

https://getbootstrap.com/docs/4.6/components/modal/

 

Modal

Use Bootstrap’s JavaScript modal plugin to add dialogs to your site for lightboxes, user notifications, or completely custom content.

getbootstrap.com

 

list.jsp 추가

<c:if test="${not empty result }">
<div class="modal" tabindex="-1" id="modal1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">처리 결과</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>${result }</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">닫기</button>
      </div>
    </div>
  </div>
</div>
</c:if>

   <script>
      $(document).ready(function() {
        
        if (history.state == null) {
          $("#modal1").modal('show');
          history.replaceState({}, null);
        }
      });
    </script>

1. bootstrap 에서 코드 복사하여 일부 수정

- title 은 처리 결과, body 는 ${result }, div 태그에 id="modal1" 추가 (script 에서 설정하기 위해)

2. 모달이 뜨게 하기 위해서 script 작성

3. <c:if> 태그로 result 가 있을 때에만 모달이 뜨도록 설정 (controller 에서 result 전달)

 

글을 등록해보았음, 다음과 같이 모달이 표시됨 ↓

 

만약, 모달을 닫고 게시글을 눌렀다가 뒤로가기를 하면 모달이 떠있는 문제가 있음

해결? history - state 를 이용하여 script 작성

   <script>
      $(document).ready(function() {
          $("#modal1").modal('show');
      });
    </script>
    


-> if 문 (조건) 추가
    
    <script>
      $(document).ready(function() {
        
        if (history.state == null) {
          $("#modal1").modal('show');
          history.replaceState({}, null);
        }
      });
    </script>

if 문은 이를 해결하기 위해 추가된 코드임


< 여기까지 작성된 코드 전체 >

BoardController

package org.zerock.controller.project1;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.zerock.domain.project1.BoardVO;
import org.zerock.service.project1.BoardService;


import lombok.Setter;

@Controller
@RequestMapping("/board")
public class BoardController {
	
	@Setter(onMethod_=@Autowired)
	private BoardService service;
	
	@GetMapping("/list")
	public void list(Model model) {
		//3. business logic
		//게시물 (Board) 목록 조회 
		List<BoardVO> list = service.getList();
		
		//4. add attribute
		model.addAttribute("list", list);
		
		//5. forward / redirect
		//jsp path : /WEB-INF/views/board/list.jsp
	}
	
	// /board/get?id=10 와 같이 요청옴
	@GetMapping({"/get", "/modify"})
	public void get(@RequestParam("id") Integer id, Model model) {
		BoardVO board = service.get(id);
		
		model.addAttribute("board", board);
	}
	
	@GetMapping("/register")
	public void register() {
		
	}
	
	@PostMapping("/register")
	public String register(BoardVO board, RedirectAttributes rttr, HttpServletRequest req) {
		
		// 3. business logic
		service.register(board);
		
		// 4. add attribute
		rttr.addFlashAttribute("result", board.getId() + "번 게시글이 등록되었습니다.");
		
		// 5. forward / redirect
		// 책: 목록으로 redirect
		return "redirect:/board/list";
	}
	
	@PostMapping("/modify")
	public String modify(BoardVO board, RedirectAttributes rttr) {
		
		if (service.modify(board)) {
			rttr.addFlashAttribute("result", board.getId() + "번 게시글이 수정되었습니다.");
		}
		return "redirect:/board/list";
	}
	
	@PostMapping("/remove")
	public String remove(@RequestParam("id") Integer id, RedirectAttributes rttr) {
		
		if (service.remove(id)) {
			rttr.addFlashAttribute("result", id + "번 게시글이 삭제되었습니다.");
		}
		
		return "redirect:/board/list";
	}
	
}

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="b" tagdir="/WEB-INF/tags/board"%>
<% request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="<%= request.getContextPath() %>/resource/css/icon/css/all.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">

<title>Insert title here</title>
</head>
<body>
  <b:navBar></b:navBar>

  <!-- .container>.row>.col>h1{게시물 목록} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>게시물 목록</h1>
        <!-- table.table>thead>tr>th*4^^tbody -->
        <table class="table">
          <thead>
            <tr>
              <th>
                <i class="fab fa-slack-hash"></i>
              </th>
              <th>제목</th>
              <th><i class="fas fa-user"></i></th>
              <th>작성일</th>
            </tr>
          </thead>
          <tbody>
            <c:forEach items="${list }" var="board">
              <tr>
                <td>${board.id }</td>
                <td>
                  <a href="get?id=${board.id }">
                  ${board.title }
                  </a>
                </td>
                <td>${board.writer }</td>
                <td>${board.inserted }</td>
              </tr>
            </c:forEach>
          </tbody>
        </table>
      </div>
    </div>
  </div>

<c:if test="${not empty result }">
<div class="modal" tabindex="-1" id="modal1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">처리 결과</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>${result }</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">닫기</button>
      </div>
    </div>
  </div>
</div>
</c:if>


  <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
  
   <script>
      $(document).ready(function() {
        
        if (history.state == null) {
          $("#modal1").modal('show');
          history.replaceState({}, null);
        }
      });
    </script>
 
</body>
</html>

get.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="b" tagdir="/WEB-INF/tags/board"%>
<% request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="<%= request.getContextPath() %>/resource/css/icon/css/all.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">

<title>Insert title here</title>
</head>
<body>
  <b:navBar></b:navBar>

  <!-- .container>.row>.col>h1{게시물 조회} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>게시물 조회</h1>
        <div class="board-view">
          <!-- .form-group*3>label[for=input$]+input.form-control#input$[readonly] -->
          <div class="form-group">
            <label for="input1">제목</label>
            <input type="text" class="form-control" id="input1" readonly value="${board.title }">
          </div>
          <div class="form-group">
            <label for="input2">내용</label>
            <textarea class="form-control" id="input2" readonly>${board.content }</textarea>
          </div>
          <div class="form-group">
            <label for="input3">작성자</label>
            <input type="text" class="form-control" id="input3" readonly value="${board.nickName }">
          </div>
          <a href="modify?id=${board.id }" class="btn btn-outline-secondary">
            수정/삭제
          </a>
        </div>
      </div>
    </div>
  </div>

	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
</body>
</html>

register.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="b" tagdir="/WEB-INF/tags/board"%>
<% request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="<%= request.getContextPath() %>/resource/css/icon/css/all.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">

<title>Insert title here</title>
</head>
<body>
  <b:navBar></b:navBar>
  <!-- .container>.row>.col>h1{게시물 작성} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>게시물 작성</h1>
        <!-- form>.form-group*3>label[for=input$]+input.form-control#input$ -->
        <form method="post">
          <div class="form-group">
            <label for="input1">제목</label>
            <input type="text" class="form-control" id="input1" name="title">
          </div>
          <div class="form-group">
            <label for="input2">내용</label>
            <textarea class="form-control" id="input2" name="content"></textarea>
          </div>
          <div class="form-group">
            <label for="input3">작성자</label>
            <input type="text" class="form-control" id="input3" name="writer">
          </div>

          <button class="btn btn-outline-primary" type="submit">등록</button>
        </form>
      </div>
    </div>
  </div>

	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
</body>
</html>

modify.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="b" tagdir="/WEB-INF/tags/board"%>
<% request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="<%= request.getContextPath() %>/resource/css/icon/css/all.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">

<title>Insert title here</title>
</head>
<body>
  <b:navBar></b:navBar>
  <!-- .container>.row>.col>h1{게시물 수정} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>게시물 수정</h1>
        <!-- form>.form-group*3>label[for=input$]+input.form-control#input$ -->
        <form id="modifyForm" method="post">
        <input type="hidden" name="id" value="${board.id }">
          <div class="form-group">
            <label for="input1">제목</label>
            <input type="text" class="form-control" value="${board.title }" id="input1" name="title">
          </div>
          <div class="form-group">
            <label for="input2">내용</label>
            <textarea class="form-control" id="input2" name="content">${board.content }</textarea>
          </div>
          <div class="form-group">
            <label for="input3">작성자</label>
            <input type="text" class="form-control" id="input3" name="writer" value="${board.writer }" readonly>
          </div>

          <button id="modifySubmitButton" class="btn btn-outline-primary" type="submit">수정</button>
          <button id="removeSubmitButton" class="btn btn-outline-danger" >삭제</button>
        </form>
      </div>
    </div>
  </div>


	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>

  <script>
  $(document).ready(function() {
    $("#removeSubmitButton").click(function(e) {
      e.preventDefault(); // 기본 동작을 진행하지 않도록 함.
      $("#modifyForm").attr("action", "remove").submit();
    });
    
    $("#modifySubmitButton").click(function(e) {
      e.preventDefault();
      $("#modifyForm").attr("action", "modify").submit();
    });
  });
  </script> 

</body>
</html>

 

그 밖에도, BoardVO(domain), BoardMapper(java, xml), BoardService 가 있음

BoardVO

package org.zerock.domain.project1;

import java.time.LocalDateTime;
import java.time.ZoneId;

import lombok.Data;

@Data
public class BoardVO {
	private Integer id;
	private String title;
	private String content;
	private String writer;
	private LocalDateTime inserted;
	private LocalDateTime updated;
	private String nickName;
	
	public String getCustomInserted() {
		// 현재일시
		LocalDateTime now = LocalDateTime.now(ZoneId.of("+09:00"));
		LocalDateTime beforeOneDayFromNow = now.minusDays(1);
		
		if (inserted.isBefore(beforeOneDayFromNow)) {
			return inserted.toLocalDate().toString();
		} else {
			return inserted.toLocalTime().toString();
		}
	}
}

BoardService

package org.zerock.service.project1;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zerock.domain.project1.BoardVO;
import org.zerock.mapper.project1.BoardMapper;

import lombok.Setter;

@Service
public class BoardService {
	
	@Setter(onMethod_=@Autowired)
	private BoardMapper mapper;
	
	public boolean register(BoardVO board) {
		return mapper.insert(board) == 1;
	}
	
	public BoardVO get(Integer id) {
		return mapper.read(id);
	}
	
	public boolean modify(BoardVO board) {
		return mapper.update(board) == 1;
	}
	
	public boolean remove(Integer id) {
		return mapper.delete(id) == 1;
	}
	
	public List<BoardVO> getList() {
		return mapper.getList();
	}

}