Spring

Spring Legacy 게시판 만들기 3편 - JSP 화면 구현과 Bootstrap UI 완성


📚 Spring Legacy 게시판 만들기 시리즈

드디어 마지막 편이에요! 지금까지 뒤에서 열심히 일하는 코드들을 만들었다면, 이번에는 **눈에 보이는 화면(View)**을 만들어볼 거예요. JSP와 Bootstrap을 사용해서 멋진 게시판을 완성해봐요! 🎉


🏠 공통 레이아웃 만들기

모든 페이지에서 공통으로 사용할 **머리(header)**와 **꼬리(footer)**를 먼저 만들어요.

📁 파일 구조

graph TB
    A["📁 WEB-INF/views"]
    B["📁 includes"]
    C["📄 header.jsp"]
    D["📄 footer.jsp"]
    E["📁 board"]
    F["📄 list.jsp"]
    G["📄 register.jsp"]
    H["📄 read.jsp"]
    I["📄 modify.jsp"]
    
    A --> B
    A --> E
    B --> C
    B --> D
    E --> F
    E --> G
    E --> H
    E --> I
    
    style A fill:#e3f2fd
    style B fill:#fff3e0
    style E fill:#e8f5e9

header.jsp - 페이지 상단

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

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Admin Panel</title>
  
  <!-- 🎨 Bootstrap CSS -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" 
        rel="stylesheet">
  
  <!-- 📝 커스텀 CSS -->
  <link rel="stylesheet" 
        href="${pageContext.request.contextPath}/resources/css/style.css">
  
  <!-- 🔧 Bootstrap JS -->
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js">
  </script>
</head>
<body>

  <!-- 🔝 상단 네비게이션 바 -->
  <nav class="navbar navbar-expand-lg navbar-custom px-4">
    <a class="navbar-brand" href="#">AdminPanel</a>
    <div class="collapse navbar-collapse">
      <ul class="navbar-nav me-auto">
        <li class="nav-item"><a class="nav-link" href="#">Dashboard</a></li>
        <li class="nav-item"><a class="nav-link" href="#">Users</a></li>
        <li class="nav-item"><a class="nav-link" href="#">Settings</a></li>
      </ul>
      <span class="navbar-text">
        Logged in as <strong>admin</strong> | 
        <a href="#" class="text-white text-decoration-underline">Logout</a>
      </span>
    </div>
  </nav>

  <!-- 🏠 메인 컨텐츠 영역 시작 -->
  <div class="main-wrapper">
    <!-- 📚 사이드바 -->
    <div class="sidebar pt-3">
      <div class="list-group list-group-flush">
        <a href="#" class="list-group-item active">Dashboard</a>
        <a href="#" class="list-group-item">Analytics</a>
        <a href="#" class="list-group-item">User List</a>
        <a href="#" class="list-group-item">Roles</a>
        <a href="#" class="list-group-item">System Logs</a>
      </div>
    </div>

    <!-- 📄 메인 컨텐츠 -->
    <div class="content">

footer.jsp - 페이지 하단

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
  </div>  <!-- content 닫기 -->
  </div>  <!-- main-wrapper 닫기 -->

</body>
</html>

💡 include 지시어를 사용하면 header와 footer를 모든 페이지에서 재사용할 수 있어요!

<%@ include file="/WEB-INF/views/includes/header.jsp" %>
<!-- 페이지 내용 -->
<%@ include file="/WEB-INF/views/includes/footer.jsp" %>

📋 목록 화면 만들기 (list.jsp)

모든 글을 테이블 형태로 보여주는 화면이에요.

graph LR
    A["🌐 /board/list"] --> B["🎮 BoardController<br/>list()"]
    B --> C["📦 Model에<br/>List 담기"]
    C --> D["📄 list.jsp"]
    D --> E["👁️ 화면 출력"]
    
    style A fill:#e3f2fd
    style B fill:#4caf50,color:#fff
    style C fill:#ff9800,color:#fff
    style D fill:#9c27b0,color:#fff
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ include file="/WEB-INF/views/includes/header.jsp" %>

<div class="row justify-content-center">
   <div class="col-lg-12">
      <div class="card shadow mb-4">
         
         <!-- 📌 카드 헤더 -->
         <div class="card-header py-3">
            <h6 class="m-0 font-weight-bold text-primary">Board List</h6>
         </div>
         
         <!-- 📊 카드 본문 - 테이블 -->
         <div class="card-body">
            <table class="table table-bordered" id="dataTable">
               <thead>
                  <th>Bno</th>
                  <th>Title</th>
                  <th>Writer</th>
                  <th>RegDate</th>
               </thead>
               <tbody class="tbody">
                  
                  <!-- 🔄 반복문으로 글 목록 출력 -->
                  <c:forEach var="board" items="${list}">
                     <tr data-bno="${board.bno}">
                        <td><c:out value="${board.bno}" /></td>
                        <td>
                            <a href='/board/read/${board.bno}'>
                                <c:out value="${board.title}" />
                            </a>
                        </td>
                        <td><c:out value="${board.writer}" /></td>
                        <td><c:out value="${board.createdDate}" /></td>
                     </tr>
                  </c:forEach>
                  
               </tbody>
            </table>
         </div>   
      </div>
   </div>
</div>

<!-- 🔔 등록 성공 알림 모달 -->
<div class="modal" tabindex="-1" id="myModal">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">알림</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <p><span id="modalResult"></span>번 글이 등록되었습니다.</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
          닫기
        </button>
      </div>
    </div>
  </div>
</div>

<script type="text/javascript" defer>
   const result = '${result}'
   
   const myModal = new bootstrap.Modal(document.getElementById('myModal'));
   
   // 등록 후 리다이렉트되면 모달 표시
   if(result){
      document.getElementById('modalResult').innerText = result;
      myModal.show();
   }
</script>

<%@ include file="/WEB-INF/views/includes/footer.jsp" %>

JSTL 태그 설명

태그설명예시
<c:forEach>반복문목록의 각 항목을 출력
<c:out>안전한 출력XSS 공격 방지
<c:if>조건문특정 조건일 때만 표시

✏️ 글 등록 화면 만들기 (register.jsp)

새 글을 작성할 수 있는 폼이에요.

sequenceDiagram
    participant 👤 as 사용자
    participant 📄 as register.jsp
    participant 🎮 as Controller
    participant 🔧 as Service
    participant 💾 as DB

    👤->>📄: 폼 작성
    👤->>📄: Submit 버튼 클릭
    📄->>🎮: POST /board/register
    🎮->>🔧: register(dto)
    🔧->>💾: INSERT
    💾-->>🔧: 성공
    🔧-->>🎮: bno 반환
    🎮-->>👤: redirect /board/list
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="/WEB-INF/views/includes/header.jsp" %>

<div class="row justify-content-center">
  <div class="col-lg-12">
    <div class="card shadow mb-4">
      <div class="card-header py-3">
        <h6 class="m-0 font-weight-bold text-primary">Board Register</h6>
      </div>
      
      <div class="card-body">
        <!-- 📝 글 등록 폼 -->
        <form action="/board/register" method="post" class="p-3">
           
           <!-- 제목 입력 -->
           <div class="mb-3">
              <label class="form-label">Title</label>
              <input type="text" name="title" class="form-control">
           </div>

           <!-- 내용 입력 -->
           <div class="mb-3">
              <label class="form-label">Content</label>
              <textarea class="form-control" name="content" rows="3"></textarea>
           </div>

           <!-- 작성자 입력 -->
           <div class="mb-3">
              <label class="form-label">Writer</label>
              <input type="text" name="writer" class="form-control">
           </div>

           <!-- 등록 버튼 -->
           <div class="d-flex justify-content-end">
              <button type="submit" class="btn btn-primary btn-lg">
                Submit
              </button>
           </div>
        </form>
      </div>
    </div>
  </div>
</div>

<%@ include file="/WEB-INF/views/includes/footer.jsp" %>

📝 form 태그의 핵심 속성

  • action: 폼 데이터를 보낼 URL
  • method: HTTP 메서드 (GET/POST)
  • name: Controller에서 받을 때 사용하는 이름

📖 글 상세 보기 화면 (read.jsp)

글의 자세한 내용을 보여주는 화면이에요.

<%@ page language="java" contentType="text/html; charset=UTF-8" 
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ include file="/WEB-INF/views/includes/header.jsp" %>

<div class="row justify-content-center">
  <div class="col-lg-12">
    <div class="card shadow mb-4">
      <div class="card-header py-3">
        <h6 class="m-0 fw-bold text-primary">Board Read</h6>
      </div>
      <div class="card-body">
        
        <!-- 📌 글 번호 -->
        <div class="mb-3 input-group input-group-lg">
          <span class="input-group-text">Bno</span>
          <input type="text" class="form-control" 
                 value="<c:out value='${board.bno}'/>" readonly>
        </div>

        <!-- 📝 제목 -->
        <div class="mb-3 input-group input-group-lg">
          <span class="input-group-text">Title</span>
          <input type="text" name="title" class="form-control" 
                 value="<c:out value='${board.title}'/>" readonly>
        </div>

        <!-- 📄 내용 -->
        <div class="mb-3 input-group input-group-lg">
          <span class="input-group-text">Content</span>
          <textarea class="form-control" name="content" rows="3" readonly>
            <c:out value="${board.content}"/>
          </textarea>
        </div>

        <!-- 👤 작성자 -->
        <div class="mb-3 input-group input-group-lg">
          <span class="input-group-text">Writer</span>
          <input type="text" name="writer" class="form-control" 
                 value="<c:out value='${board.writer}'/>" readonly>
        </div>

        <!-- 📅 등록일 -->
        <div class="mb-3 input-group input-group-lg">
          <span class="input-group-text">RegDate</span>
          <input type="text" name="regDate" class="form-control" 
                 value="<c:out value='${board.regDate}'/>" readonly>
        </div>

        <!-- 🔘 버튼 영역 -->
        <div class="float-end">
           <a href='/board/list'>
             <button type="button" class="btn btn-info">LIST</button>
           </a>
          
          <!-- 삭제되지 않은 글만 수정 버튼 표시 -->
          <c:if test="${!board.delFlag}">
            <a href='/board/modify/${board.bno}'>
               <button type="button" class="btn btn-warning">MODIFY</button>
            </a>   
          </c:if>
        </div>

      </div>
    </div>
  </div>
</div>

<%@ include file="/WEB-INF/views/includes/footer.jsp" %>

🔧 글 수정 화면 (modify.jsp)

글을 수정하고 삭제할 수 있는 화면이에요.

<%@ page language="java" contentType="text/html; charset=UTF-8" 
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ include file="/WEB-INF/views/includes/header.jsp" %>

<div class="row justify-content-center">
  <div class="col-lg-12">
    <div class="card shadow mb-4">
      <div class="card-header py-3">
        <h6 class="m-0 fw-bold text-primary">Board Modify</h6>
      </div>
      
      <div class="card-body">
        <!-- 📝 수정 폼 -->
        <form id="actionForm" action="/board/modify" method="post">
        
          <!-- 글 번호 (수정 불가) -->
          <div class="mb-3 input-group input-group-lg">
            <span class="input-group-text">Bno</span>
            <input type="text" name="bno" class="form-control" 
                   value="<c:out value='${board.bno}'/>" readonly>
          </div>

          <!-- 제목 (수정 가능) -->
          <div class="mb-3 input-group input-group-lg">
            <span class="input-group-text">Title</span>
            <input type="text" name="title" class="form-control" 
                   value="<c:out value='${board.title}'/>">
          </div>

          <!-- 내용 (수정 가능) -->
          <div class="mb-3 input-group input-group-lg">
            <span class="input-group-text">Content</span>
            <textarea class="form-control" name="content" rows="3">
              <c:out value="${board.content}"/>
            </textarea>
          </div>

          <!-- 작성자 (수정 불가) -->
          <div class="mb-3 input-group input-group-lg">
            <span class="input-group-text">Writer</span>
            <input type="text" name="writer" class="form-control" 
                   value="<c:out value='${board.writer}'/>" readonly>
          </div>

          <!-- 등록일 -->
          <div class="mb-3 input-group input-group-lg">
            <span class="input-group-text">RegDate</span>
            <input type="text" class="form-control" 
                   value="<c:out value='${board.createdDate}'/>" readonly>
          </div>

          <!-- 삭제 플래그 (숨김) -->
          <input type="hidden" name="delFlag" 
                 value="<c:out value='${board.delFlag}'/>">
        </form>

        <!-- 🔘 버튼 영역 -->
        <div class="float-end">
          <button type="button" class="btn btn-info btnList">LIST</button>
          <button type="button" class="btn btn-warning btnModify">MODIFY</button>
          <button type="button" class="btn btn-danger btnRemove">REMOVE</button>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- 🔧 JavaScript - 버튼 이벤트 처리 -->
<script type="text/javascript">
  const formObj = document.querySelector("#actionForm");
  
  // ✏️ 수정 버튼 클릭
  document.querySelector(".btnModify").addEventListener("click", () => {
    formObj.action = "/board/modify";
    formObj.method = "post";
    formObj.submit();
  })

  // 📋 목록 버튼 클릭
  document.querySelector(".btnList").addEventListener("click", () => {
    formObj.action = "/board/list";
    formObj.method = "get";
    formObj.submit();
  })

  // 🗑️ 삭제 버튼 클릭
  document.querySelector(".btnRemove").addEventListener("click", () => {
    formObj.action = "/board/remove";
    formObj.method = "post";
    formObj.submit();
  })
</script>

<%@ include file="/WEB-INF/views/includes/footer.jsp" %>

🔄 전체 화면 흐름

flowchart LR
    A["📋 list.jsp<br/>목록"] --> |"제목 클릭"| B["📖 read.jsp<br/>상세보기"]
    A --> |"등록 버튼"| C["✏️ register.jsp<br/>글쓰기"]
    B --> |"수정 버튼"| D["🔧 modify.jsp<br/>수정"]
    B --> |"목록 버튼"| A
    C --> |"등록 완료"| A
    D --> |"수정 완료"| B
    D --> |"삭제 완료"| A
    D --> |"목록 버튼"| A
    
    style A fill:#4caf50,color:#fff
    style B fill:#2196f3,color:#fff
    style C fill:#ff9800,color:#fff
    style D fill:#f44336,color:#fff

🎯 프로젝트 완성 테스트

1️⃣ 서버 실행하기

Tomcat 서버를 실행하세요.

2️⃣ 테스트 시나리오

graph TB
    A["1️⃣ 목록 페이지 접속<br/>/board/list"] --> B["2️⃣ 글 등록 테스트"]
    B --> C["3️⃣ 목록에서 확인"]
    C --> D["4️⃣ 제목 클릭하여<br/>상세보기"]
    D --> E["5️⃣ 수정 테스트"]
    E --> F["6️⃣ 삭제 테스트"]
    F --> G["🎉 완성!"]
    
    style A fill:#e3f2fd
    style G fill:#4caf50,color:#fff
단계URL확인할 것
목록/board/list테이블이 보이는지
등록/board/register폼이 나오고, 등록되는지
상세/board/read/1데이터가 표시되는지
수정/board/modify/1수정이 되는지
삭제수정화면에서 REMOVE목록에서 사라지는지

🚀 추가 기능 아이디어

게시판을 더 발전시키고 싶다면 이런 기능들을 추가해볼 수 있어요!

📄 페이지네이션 (페이지 나누기)

graph LR
    A["◀ 이전"] --> B["1"]
    B --> C["2"]
    C --> D["3"]
    D --> E["4"]
    E --> F["5"]
    F --> G["다음 ▶"]
    
    style C fill:#32cd32
// PageRequestDTO.java
@Getter
@Setter
@Builder
public class PageRequestDTO {
    @Builder.Default
    private int page = 1;
    
    @Builder.Default
    private int size = 10;
    
    public int getSkip() {
        return (page - 1) * size;
    }
}

🔍 검색 기능

-- 제목 검색
SELECT * FROM tbl_board 
WHERE title LIKE CONCAT('%', #{keyword}, '%')
  AND delflag = false
ORDER BY bno DESC

🔐 로그인/회원가입

graph TB
    A["회원가입"] --> B["로그인"]
    B --> C["세션 생성"]
    C --> D["글 작성 가능"]
    D --> E["자기 글만 수정/삭제"]

💬 댓글 기능

CREATE TABLE tbl_reply (
    rno INT AUTO_INCREMENT PRIMARY KEY,
    bno INT NOT NULL,
    reply VARCHAR(500) NOT NULL,
    replyer VARCHAR(50) NOT NULL,
    replydate TIMESTAMP DEFAULT NOW(),
    FOREIGN KEY (bno) REFERENCES tbl_board(bno)
);

📚 전체 시리즈 요약

timeline
    title Spring Legacy 게시판 만들기
    
    section 1편 세팅
        프로젝트 구조 이해
        pom.xml 설정
        web.xml 설정
        Spring 설정
        DB 테이블 생성
    
    section 2편 MVC
        DTO 만들기
        Mapper 만들기
        Service 만들기
        Controller 만들기
        흐름 이해
    
    section 3편 화면
        레이아웃 구성
        목록 화면
        등록 화면
        상세 화면
        수정/삭제 화면

✅ 최종 체크리스트

모든 것이 잘 동작하나요?

  • 글 목록이 제대로 표시됨
  • 새 글을 등록할 수 있음
  • 글 상세 보기가 동작함
  • 글 수정이 가능함
  • 글 삭제(논리적 삭제)가 동작함
  • 삭제된 글은 목록에 안 보임

🎓 마무리

축하합니다! 🎉 드디어 Spring Legacy로 게시판을 완성했어요!

우리가 배운 것들을 정리하면:

키워드배운 것
1편세팅Maven, web.xml, Spring 설정, DB 설정
2편MVCController, Service, Mapper, DTO
3편화면JSP, JSTL, Bootstrap, JavaScript

이제 여러분은 Spring 웹 애플리케이션의 기본 구조를 이해하게 되었어요. 이 기초를 바탕으로 더 복잡한 기능들을 추가해나갈 수 있답니다!

💡 앞으로 공부하면 좋을 것들

  • Spring Security (보안/인증)
  • RESTful API 설계
  • JPA/Hibernate (ORM)
  • Spring Boot (더 쉬운 설정)

처음부터 완벽할 필요 없어요. 조금씩 기능을 추가하면서 실력을 키워나가세요! 화이팅! 💪