[Spring Boot+JSP] 유저 없이 게시판 간단 구현 로직 (전체 코드)

류넹·2025년 4월 18일
1

Project

목록 보기
9/14

# 개발환경

항목내용
LanguageJava 17
IDEEclipse 2023-12
Spring Boot3.4.4 (설정파일 : properties)
Tomcat10.1.18
DataBaseMySQL 8.0.37 (MyBatis)
Build ToolMaven (설정파일 : pom.xml)
View template engineJSP

📌 실행 화면

1. 게시판 목록 조회

2. 게시판 상세 조회

3. 게시글 등록

4. 게시글 수정

5. 게시글 삭제

6. 게시글 제목 검색

7. 게시글 내용 검색



📌 구현 코드

0-1. BoardDetailDto

package com.apptest.board.dto;

import java.util.Date;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Builder
public class BoardDetailDto {
	
	private int id;
	private String nickname;
	private String title;
	private String content;
	private Date createDate;
	private Date deleteDate;
	private String deleteYn;
}

0-2. BoardFormDto

package com.apptest.board.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
@Getter
@Setter
@Builder
@AllArgsConstructor
public class BoardFormDto {
	
	private int id;
	
	@NotBlank(message = "닉네임을 입력해주세요.")
	@Size(min=2, max = 20, message = "닉네임은 최대 20글자까지 가능합니다.")
	private String nickname;
	
	@NotBlank(message = "제목을 입력해주세요.")
	@Size(max = 255, message = "제목은 최대 255글자까지 가능합니다.")
	private String title;
	
	@NotBlank(message = "내용을 입력해주세요.")
	private String content;
}

0-3. BoardListDto

package com.apptest.board.dto;

import java.util.Date;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Builder
public class BoardListDto {
	
	private int id;
	private String nickname;
	private String title;
	private String content;
	private Date createDate;
	private Date deleteDate;
	private String deleteYn;
}

0-4. Criteria

package com.apptest.board.dto;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Criteria {
	
	private int page;		// 요청한 페이지 번호
	private int rows;		// 한번에 표시할 데이터 갯수
	private String sort;	// 정렬방식
	private String opt;		// 검색옵션
	private String keyword;	// 검색어
	private int begin;		// 검색시작 범위
	private int end;		// 검색종료 범위
}

0-5. ListDto

package com.apptest.board.dto;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

@AllArgsConstructor
@Getter
@ToString
public class ListDto<T> {
	
	private List<?> items;
	private Pagination paging;
}

0-6. Pagination

package com.apptest.board.dto;

public class Pagination {
	
	private int rows = 10;
	private int pages = 10;
	private int totalRows;
	private int totalPages;
	private int totalBlocks;
	private int currentPage;
	private int currentBlock;
	private int begin;
	private int end;
	private int beginPage;
	private int endPage;
	private boolean isFirst;
	private boolean isLast;

	public Pagination(int currentPage, int totalRows) {
		this.totalRows = totalRows;
		this.currentPage = currentPage;
		init();
	}


	public Pagination(int currentPage, int totalRows, int rows) {
		this.totalRows = totalRows;
		this.currentPage = currentPage;
		this.rows = rows;
		init();
	}


	public Pagination(int currentPage, int totalRows, int rows, int pages) {
		this.totalRows = totalRows;
		this.currentPage = currentPage;
		this.rows = rows;
		this.pages = pages;
		init();
	}

	private void init() {

		if (totalRows > 0) {
			this.totalPages = (int) Math.ceil((double) totalRows / rows);
			this.totalBlocks = (int) Math.ceil((double) totalPages / pages);
			this.currentBlock = (int) Math.ceil((double) currentPage / pages);
			this.begin = (currentPage - 1) * rows + 1;
			this.end = currentPage * rows;
			this.beginPage = (currentBlock - 1) * pages + 1;
			this.endPage = currentBlock * pages;
			if (currentBlock == totalBlocks) {
				this.endPage = this.totalPages;
			}
			if (currentPage == 1) {
				this.isFirst = true;
			}
			if (currentPage == totalPages) {
				this.isLast = true;
			}
		}
	}

	public int getRows() {
		return rows;
	}

	public int getPages() {
		return pages;
	}

	public int getTotalRows() {
		return totalRows;
	}

	public int getTotalPages() {
		return totalPages;
	}

	public int getTotalBlocks() {
		return totalBlocks;
	}

	public int getCurrentPage() {
		return currentPage;
	}

	public int getCurrentBlock() {
		return currentBlock;
	}

	public int getBegin() {
		return begin;
	}

	public int getEnd() {
		return end;
	}

	public int getBeginPage() {
		return beginPage;
	}

	public int getEndPage() {
		return endPage;
	}

	public boolean isFirst() {
		return isFirst;
	}

	public boolean isLast() {
		return isLast;
	}


	@Override
	public String toString() {
		return "Pagination [rows=" + rows + ", pages=" + pages + ", totalRows=" + totalRows + ", totalPages="
				+ totalPages + ", totalBlocks=" + totalBlocks + ", currentPage=" + currentPage + ", currentBlock="
				+ currentBlock + ", begin=" + begin + ", end=" + end + ", beginPage=" + beginPage + ", endPage="
				+ endPage + ", isFirst=" + isFirst + ", isLast=" + isLast + "]";
	}

}

0-7. VO

package com.apptest.board.vo;

import java.util.Date;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Builder
public class Board {
	
	private int id;
	private String nickname;
	private String title;
	private String content;
	private Date createDate;
	private Date deleteDate;
	private String deleteYn;
}

1. Controller

package com.apptest.board.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
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 com.apptest.board.dto.BoardFormDto;
import com.apptest.board.dto.BoardDetailDto;
import com.apptest.board.dto.BoardListDto;
import com.apptest.board.dto.Criteria;
import com.apptest.board.dto.ListDto;
import com.apptest.board.service.BoardService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/board")
public class BoardController {

	private final BoardService boardService;

	// 게시글 목록 조회
	@GetMapping("/list")
	public String getBoards(@RequestParam(name = "page", required = false, defaultValue = "1") int page,
			@RequestParam(name = "rows", required = false, defaultValue = "10") int rows,
			@RequestParam(name = "opt", required = false) String opt,
			@RequestParam(name = "keyword", required = false) String keyword,
			Model model) {
		
		Criteria criteria = new Criteria();
		criteria.setPage(page);
		criteria.setRows(rows);
		
		log.info("현재 페이지 : {}", criteria.getPage());
		log.info("페이지당 게시물 수 : {}", criteria.getRows());
		
		// 검색옵션(opt)과 검색어(keyword) 모두 null이나 빈 문자열이 아닐 때만 Map에 저장한다.
		if (StringUtils.hasText(opt) && StringUtils.hasText(keyword)) {
			criteria.setOpt(opt);
			criteria.setKeyword(keyword);
		}

		ListDto<BoardListDto> boards = boardService.getBoards(criteria);
		model.addAttribute("boards", boards.getItems());
		model.addAttribute("paging", boards.getPaging());
		model.addAttribute("criteria", criteria);

		return "board/list";
	}

	// 게시글 등록 폼
	@GetMapping("/create")
	public String createBoard(Model model) {

		model.addAttribute("board", new BoardFormDto());
		model.addAttribute("mode", "create");

		return "board/form";
	}

	// 게시글 등록
	@PostMapping("/create")
	public String createBoard(@Valid @ModelAttribute("board") BoardFormDto boardFormDto, BindingResult errors,
			Model model) {

		log.info("닉네임 = {}", boardFormDto.getNickname());
		log.info("벨리데이션 = {}", errors.hasErrors());

		if (errors.hasErrors()) {
			return "board/form";
		}

		boardService.createBoard(boardFormDto);
		return "redirect:/board/list";
	}

	// 게시글 상세 조회
	@GetMapping("/detail/{id}")
	public String getBoardDetail(@PathVariable int id, Model model) {

		BoardDetailDto board = boardService.getBoardDetail(id);
		model.addAttribute("board", board);

		return "board/detail";
	}

	// 게시글 수정 폼
	@GetMapping("/update/{id}")
	public String updateBoard(@PathVariable int id, Model model) {
		
		BoardDetailDto board = boardService.getBoardDetail(id);
		
		BoardFormDto boardFormDto = BoardFormDto.builder()
				.id(id)
				.nickname(board.getNickname())
				.title(board.getTitle())
				.content(board.getContent())
				.build();
		
		model.addAttribute("board", boardFormDto);
		model.addAttribute("mode", "update");
		
		return "board/form";
	}

	// 게시글 수정
	@PostMapping("/update/{id}")
	public String updateBoard(@PathVariable int id, @Valid BoardFormDto boardFormDto, Model model) {

		boardService.updateBoard(boardFormDto);

		return "redirect:/board/detail/" + id;
	}
	
	// 게시글 삭제
	@PostMapping("/delete/{id}")
	public String deleteBoard(@PathVariable int id, RedirectAttributes redirectAttributes) {
		
		boardService.deleteBoard(id);
		redirectAttributes.addFlashAttribute("msg", "게시글이 삭제되었습니다.");
		
		return "redirect:/board/list";
	}

}

2. Service

package com.apptest.board.service;

import java.util.List;

import org.springframework.stereotype.Service;

import com.apptest.board.dto.BoardFormDto;
import com.apptest.board.dto.BoardDetailDto;
import com.apptest.board.dto.BoardListDto;
import com.apptest.board.dto.Criteria;
import com.apptest.board.dto.ListDto;
import com.apptest.board.dto.Pagination;
import com.apptest.board.mapper.BoardMapper;
import com.apptest.board.vo.Board;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

@Log4j2
@Service
@RequiredArgsConstructor
public class BoardService {
	
	private final BoardMapper boardMapper;
	
	// 게시글 목록 조회
	public ListDto<BoardListDto> getBoards(Criteria criteria) {
		
		// 1. 총 데이터 개수 조회
		int totalRows = boardMapper.getTotalRows(criteria);
		
		// 2. 페이징 처리에 필요한 정보를 표현하는 객체 생성
		Pagination pagination = new Pagination(criteria.getPage(), totalRows, criteria.getRows());
		// 3. 현재 페이지번호에 해당하는 조회범위를 Criteria객체에 저장
		criteria.setBegin(pagination.getBegin());
		criteria.setEnd(pagination.getEnd());
		
		List<BoardListDto> boardList = boardMapper.getBoards(criteria);
		log.info("보드 리스트 : {}", boardList.size());
		log.info("크리테리아 : {}, {}", criteria.getOpt(), criteria.getKeyword());
		
		ListDto<BoardListDto> dto = new ListDto<BoardListDto>(boardList, pagination);
		
		return dto;
	}
	
	// 게시글 등록
	public void createBoard(BoardFormDto boardFormDto) {
		
		Board board = Board.builder()
				.nickname(boardFormDto.getNickname())
				.title(boardFormDto.getTitle())
				.content(boardFormDto.getContent())
				.build();
		
		boardMapper.createBoard(board);
	}
	
	// 게시글 상세 조회
	public BoardDetailDto getBoardDetail(int id) {
		
		return boardMapper.getBoardDetail(id);
	}
	
	// 게시글 수정
	public void updateBoard(BoardFormDto boardFormDto) {
		
		Board board = Board.builder()
				.id(boardFormDto.getId())
				.title(boardFormDto.getTitle())
				.content(boardFormDto.getContent())
				.build();
		
		boardMapper.updateBoard(board);
	}
	
	// 게시글 삭제
	public void deleteBoard(int id) {
		
		boardMapper.deleteBoard(id);
	}

}

3. Mapper

package com.apptest.board.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.apptest.board.dto.BoardDetailDto;
import com.apptest.board.dto.BoardListDto;
import com.apptest.board.dto.Criteria;
import com.apptest.board.vo.Board;

@Mapper
public interface BoardMapper {
	
	int getTotalRows(Criteria criteria);
	List<BoardListDto> getBoards(Criteria criteria);
	
	void createBoard(Board board);
	
	BoardDetailDto getBoardDetail(int id);
	
	void updateBoard(Board board);
	
	void deleteBoard(int id);
}

4. xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper   PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.apptest.board.mapper.BoardMapper">

	<select id="getTotalRows" parameterType="com.apptest.board.dto.Criteria" resultType="int">
		select
			count(*)
		from
			board
		<!-- 검색옵션이 있을 때만 아래 조회 조건 적용 -->
		<where>
			<if test="opt != null">
				<choose>
					<when test="opt == 'title' ">
						title like concat('%', #{keyword},'%')
					</when>
					<when test="opt == 'content' ">
						content like concat('%', #{keyword},'%')
					</when>
				</choose>
			</if>
		</where>
	</select>

    <select id="getBoards" parameterType="com.apptest.board.dto.Criteria" resultType="com.apptest.board.dto.BoardListDto">
        select
            b.id          as id,
            b.nickname    as nickname,
            b.title       as title,
            b.content     as content,
            b.create_date as createDate,
            b.delete_date as deleteDate,
            b.delete_yn   as deleteYn
        from
	        (select
	        	row_number() over (order by id desc) as rowNum,
	            id,
	            nickname,
	            title,
	            content,
	            create_date,
	            delete_date,
	            delete_yn
	        from
	            board
	        where
	        	delete_yn = 'N'
				<if test="opt != null">
					<choose>
						<when test="opt == 'title' ">
							and title like concat('%', #{keyword},'%')
						</when>
						<when test="opt == 'content' ">
							and content like concat('%', #{keyword},'%')
						</when>
					</choose>
				</if>
			) as b
		where
			b.rowNum between #{begin} and #{end}
    </select>
    
    <insert id="createBoard" parameterType="com.apptest.board.vo.Board">
    	insert into board
    		(nickname, title, content)
    	values
    		(#{nickname}, #{title}, #{content})
    </insert>
    
    <select id="getBoardDetail" parameterType="int" resultType="com.apptest.board.dto.BoardDetailDto">
        select
            b.id          as id,
            b.nickname    as nickname,
            b.title       as title,
            b.content     as content,
            b.create_date as createDate,
            b.delete_date as deleteDate,
            b.delete_yn   as deleteYn
        from
            board b
        where
            id = #{value}
            and delete_yn = 'N'
    </select>
    
    <update id="updateBoard" parameterType="com.apptest.board.vo.Board">
    	update
    		board
    	set
    		title   = #{title},
    		content = #{content}
    	where
    		id = #{id}
    </update>
    
    <update id="deleteBoard" parameterType="int">
    	update
    		board
    	set
    		delete_yn = 'Y'
    	where
    		id = #{id}
    </update>
</mapper>

5-1. list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" ></script>
<title>게시판 테스트</title>
</head>
<body>
<c:if test="${not empty msg}">
    <script>
        alert("${msg}");
    </script>
</c:if>
<div class="container">
	<div class="row">
		<div class="col-12 mt-3">
			<h1>게시판</h1>
			<form id="form-boards" method="get" action="list">
				<input type="hidden" name="page">
				<div class="d-flex justify-content-start mb-3">
					<div class="me-2">
						<select class="form-select" name="opt">
							<option value="title" ${param.opt eq 'title' ? 'selected' : '' }> 제목</option>
							<option value="content" ${param.opt eq 'content' ? 'selected' : '' }> 내용</option>
						</select>
					</div>
					<div class="me-2">
						<input type="text" class="form-control" name="keyword" value="${param.keyword }"/>
					</div>
					<div>
						<button type="submit" class="btn btn-outline-primary">검색</button>
					</div>
				</div>
				<table class="table table-bordered">
					<tr>
						<td>번호</td>
						<td>제목</td>
						<td>내용</td>
						<td>작성자</td>
						<td>등록일</td>
					</tr>
					<c:forEach var="board" items="${boards }">
						<c:choose>
							<c:when test="${board.deleteYn == 'Y' }">
							</c:when>
							<c:otherwise>
								<tr>
									<td>${board.id }</td>
									<td>
										<a href="/board/detail/${board.id }">${board.title }</a>
									</td>
									<td>${board.content }</td>
									<td>${board.nickname }</td>
									<td><fmt:formatDate value="${board.createDate }" pattern="yyyy.MM.dd HH:mm" /></td>
								</tr>
							</c:otherwise>
						</c:choose>
					</c:forEach>
				</table>
				<div>
					<div class="col-4">
						<c:if test="${paging.totalRows ne 0 }"> <!-- ne : not equal, totalRows가 0이 아닐 때 -->
							<nav>
								<ul class="pagination">
									<li class="page-item">
										<a href="list?page=${paging.currentPage - 1 }"
										    class="page-link ${paging.first ? 'disabled' : '' }"
										    onclick="changePage(${paging.currentPage - 1}, event)"><</a>
									</li>
									
									<c:forEach var="num" begin="${paging.beginPage }" end="${paging.endPage }">
										<li class="page-item ${paging.currentPage eq num ? 'active' : '' }">
											<a href="list?page=${num }"
											    class="page-link"
											    onclick="changePage(${num }, event)">${num }</a>
										</li>
									</c:forEach>
									
									<li class="page-item">
										<a href="list?page=${paging.currentPage + 1 }"
										    class="page-link ${paging.last ? 'disabled' : ''}"
										    onclick="changePage(${paging.currentPage + 1}, event)">></a>
									</li>
								</ul>
							</nav>
						</c:if>
					</div>
					<a class="btn btn-outline-primary mt-3 mb-5" href="create">글쓰기</a>
				</div>
			</form>
		</div>
	</div>
</div>
<script type="text/javascript">
	function changePage(page, event) {
		event.preventDefault();
		document.querySelector("input[name=page]").value = page;
		document.getElementById("form-boards").submit();
	}
</script>
</body>
</html>

5-2. detail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" ></script>
<title>게시글 상세</title>
</head>
<body>
<div class="container">
	<div class="row">
		<div class="col-12 mt-3">
			<h1>게시글 상세</h1>
			<c:choose>
				<c:when test="${empty board }">
					<p class="mt-3">존재하지 않는 게시물입니다.</p>
					<div class="d-flex justify-content-between mt-5 mb-5">
						<a class="btn btn-outline-secondary me-2" href="/board/list">목록</a>
					</div>
				</c:when>
				<c:otherwise>
					<p class="mt-3">번호 : ${board.id }</p>
					<p>작성자 : ${board.nickname }</p>
					<p>등록일 : <fmt:formatDate value="${board.createDate }" pattern="yyyy.MM.dd HH:mm"/></p>
					<p>제목 : ${board.title }</p>
					<p>내용 : ${board.content }</p>
					<div class="d-flex justify-content-between mt-5 mb-5">
						<a class="btn btn-outline-secondary me-2" href="/board/list">목록</a>
						<div class="d-flex justify-content-end">
							<a class="btn btn-outline-primary me-2" href="/board/update/${board.id }">수정</a>
							<form action="/board/delete/${board.id}" method="post" style="display:inline;">
							    <button type="submit" class="btn btn-outline-danger">삭제</button>
							</form>
						</div>
					</div>
				</c:otherwise>
			</c:choose>
		</div>
	</div>
</div>
</body>
</html>

5-3. form.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" ></script>
<title>게시물 등록</title>
</head>
<body>
<%@ include file="../common/navbar.jsp" %>
<div class="container">
	<div class="row">
		<div class="col-12 mt-3">
			<h1>게시글 등록</h1>
			<!-- 등록/수정 폼 분리 -->
			<c:choose>
				<c:when test="${mode eq 'update' }">
					<c:set var="actionUrl" value="/board/update/${board.id }"/>
				</c:when>
				<c:otherwise>
					<c:set var="actionUrl" value="/board/create"/>
				</c:otherwise>
			</c:choose>
		
			<!-- Spring form 태그 사용 -->
			<form:form method="post" modelAttribute="board" action="${actionUrl }">
				<table class="table">
					<tr>
						<td><label for="nickname">닉네임</label></td>
						<td>
							<c:choose>
								<c:when test="${mode == 'create' }">
									<form:input path="nickname" cssClass="form-control" id="nickname" />
								</c:when>
								<c:otherwise>
									<form:input readonly="true" path="nickname" cssClass="form-control" id="nickname" />
								</c:otherwise>
							</c:choose>
							<form:errors path="nickname" cssClass="text-danger" />
						</td>
					</tr>
					<tr>
						<td><label for="title">제목</label></td>
						<td>
							<form:input path="title" cssClass="form-control" id="title" />
							<form:errors path="title" cssClass="text-danger" />
						</td>
					</tr>
					<tr>
						<td><label for="content">내용</label></td>
						<td>
							<form:textarea path="content" cssClass="form-control" id="content" />
							<form:errors path="content" cssClass="text-danger" />
						</td>
					</tr>
				</table>
				<div class="d-flex justify-content-between mt-3">
					<a href="/board/list" class="btn btn-outline-secondary">목록</a>
					<button type="submit" class="btn btn-outline-primary">저장</button>
				</div>
			</form:form>
		</div>
	</div>
</div>
</body>
</html>
profile
학습용 커스터마이징 간단 개발자 사전

0개의 댓글