이 예제에는 스프링 시큐리티를 사용하였습니다. 시큐리티와 관련된 내용은  스프링 시큐리티 관련 글을 참고하세요.

 

개요

Spring Boot의 기본 기능(Thymeleaf, 시큐리티 포함)을 이용해서 전통적 형태의 웹 게시판을 만들었습니다. 게시판은 웹 프로그래밍에 있어서 필수라고 볼 수 있는 CRUD(Create, Read, Update, Delete)를 연습하기에 적당한 예제입니다.

이 게시판은 로그인 환경을 가정하여 만들었습니다. 로그인 관련 부분은 시큐리티에서 다룹니다. 이로 인한 게시판의 추가 요구사항은 다음과 같습니다.

  • 로그인이 되어있지 않다면 게시판의 글 목록만 보여주고, 내용은 볼 수 없게 한다.
  • 게시글의 수정, 삭제 기능은 해당 글을 작성한 사용자만 접근할 수 있도록 한다.

이 예제에서는 스프링 부트의 내용을 연습하기 위해 자바스크립트는 따로 사용하지 않았습니다. 그리고 목적상 고급 기능(페이징 기능 등)은 아직 구현되지 않았습니다.

 

프로젝트의 구조 (일부)

  • SimpleBoardController: 게시판 관련 컨트롤러
  • SimpleBoardDAO: 게시판 테이블 DAO, 작업 편의상 서비스, DTO는 따로 작성하지 않았습니다. DTO 대신 Map을 사용합니다.

 

데이터베이스 구조

데이터베이스 설정하는 방법은 Spring Boot: mariadb 연결하기 (JDBC-Maven 기준)를 참고하세요.

 

코드 (일부)


@charset "UTF-8";
.text-red {
color: red;
}
.text-blue {
color: blue;
}
.text-grey {
color: grey;
}

view raw

board.css

hosted with ❤ by GitHub


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>게시글 보기</title>
<link rel="stylesheet" type="text/css" href="/board.css">
</head>
<body>
<hr>
<div sec:authorize="isAuthenticated()">
<p>current username: <span class="text-red" th:text="${#authentication.getPrincipal().getUsername()}">anon</span></p>
<a href="/logout">Logout</a>
</div>
<div sec:authorize="!isAuthenticated()">
<a href="/login">Login</a>
</div>
<hr>
<h3 th:text="${article.title}">title</h3>
<p>by <span th:text="${article.username}">anon</span> <span class="text-grey" th:text="${'[' + article.writeDate + ']'}">date</span></p>
<h5>content </h5>
<pre th:text="${article.content}">….</pre>
<p th:if="${#authentication.getPrincipal().getUsername() eq article.username}">
<a th:href="${'/board/update/' + article.seq}">[modify]</a>
<a th:href="${'/board/delete/' + article.seq}">[delete]</a>
</p>
<p><a href="/board">[Back to list]</a></p>
</body>
</html>


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>delete</title>
<link rel="stylesheet" type="text/css" href="/board.css">
</head>
<body>
<hr>
<div sec:authorize="isAuthenticated()">
<p>current username: <span class="text-red" th:text="${#authentication.getPrincipal().getUsername()}">anon</span></p>
<p>articleId: <span th:text="${articleId}"></span></p>
<hr>
<h4>이 글을 정말 삭제하시겠습니까?</h4>
<form action="/board/proc/delete" method="POST">
<input type="hidden" name="articleId" th:value="${articleId}">
<p><a th:href="${'/board/read/' + articleId}">NO</a> <button>[YES]</button></p>
</form>
</div>
</body>
</html>


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>modify</title>
<link rel="stylesheet" type="text/css" href="/board.css">
</head>
<body>
<hr>
<div sec:authorize="isAuthenticated()">
<p>current username: <span class="text-red" th:text="${#authentication.getPrincipal().getUsername()}">anon</span></p>
<p>articleId: <span th:text="${article.seq}"></span></p>
<hr>
<div>
<form method="post" action="/board/proc/update">
<input type="hidden" th:value="${article.seq}" name="articleId">
<input type="text" placeholder="title…" name="title" th:value="${article.title}">
<textarea placeholder="content…" name="content" th:text="${article.content}"></textarea>
<button>submit</button>
</form>
</div>
</div>
</body>
</html>


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>초간단 게시판</title>
<link rel="stylesheet" type="text/css" href="/board.css">
</head>
<body>
<hr>
<div sec:authorize="isAuthenticated()">
<p>current username: <span class="text-red" th:text="${#authentication.getPrincipal().getUsername()}">anon</span></p>
<a href="logout">Logout</a>
<hr>
<div>
<form method="post" action="/board/proc/write">
<input type="text" placeholder="title…" name="title">
<textarea placeholder="content…" name="content"></textarea>
<button>submit</button>
</form>
</div>
</div>
<div sec:authorize="!isAuthenticated()">
<a href="/login">Login</a>
</div>
<hr>
<table>
<thead>
<tr>
<th>No.</th>
<th>username</th>
<th>title</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr th:each="article, i: ${list}">
<td th:text="${article.seq}"></td>
<td th:text="${article.username}"></td>
<td sec:authorize="isAuthenticated()">
<a th:href="${'/board/read/' + article.seq}" th:text="${article.title}"></a>
<span class="text-blue" th:if="${i.count eq i.size}">[1등]</span>
</td>
<td sec:authorize="!isAuthenticated()" th:text="${article.title}"></td>
<td th:text="${article.writeDate}"></td>
</tr>
</tbody>
</table>
</body>
</html>


package com.springboot.security.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.springboot.security.dao.SimpleBoardDAO;
@Controller
public class SimpleBoardController {
@Autowired SimpleBoardDAO sbd;
@RequestMapping("/board")
public String board(Model model) {
List<Map<String, Object>> list = sbd.getBoardList();
// System.out.println(list);
model.addAttribute("list", list);
return "simple-board";
}
@RequestMapping(value="/board/proc/write", method=RequestMethod.POST)
public String writeAnArticle(Authentication auth, String title, String content) {
int result = 0;
if(title != null && !title.equals("") && content != null && !content.equals("")) {
Map<String, Object> article = new HashMap<>();
article.put("username", auth.getName());
article.put("title", title);
article.put("content", content);
result = sbd.insertAnArticle(article);
} else {
System.err.println("Do not write blank article!");
}
return "redirect:/board?writeResult=" + result;
}
@RequestMapping("/board/read/{articleId}")
public String readAnArticle(Model model, @PathVariable("articleId") int articleId) {
Map<String, Object> article = sbd.getAnArticle(articleId);
model.addAttribute("article", article);
return "simple-board-view-content";
}
@RequestMapping("/board/delete/{articleId}")
public String deleteAnArticleView(Authentication auth, Model model, @PathVariable("articleId") int articleId) {
model.addAttribute("articleId", articleId);
return "simple-board-view-delete";
}
@RequestMapping(value="/board/proc/delete", method=RequestMethod.POST)
public String deleteAnArticleProc(Authentication auth, Model model, int articleId) {
int result = sbd.deleteAnArticle(articleId, auth.getName());
return "redirect:/board?deleteResult=" + result;
}
@RequestMapping("/board/update/{articleId}")
public String updateAnArticleView(Authentication auth, Model model, @PathVariable("articleId") int articleId) {
Map<String, Object> article = sbd.getAnArticle(articleId);
model.addAttribute("article", article);
return "simple-board-view-modify";
}
@RequestMapping(value="/board/proc/update", method=RequestMethod.POST)
public String updateAnArticleProc(Authentication auth, Model model, int articleId, String title, String content) {
int result = 0;
if(title != null && !title.equals("") && content != null && !content.equals("")) {
Map<String, Object> article = new HashMap<>();
article.put("articleId", articleId);
article.put("username", auth.getName());
article.put("title", title);
article.put("content", content);
result = sbd.updateAnArticle(article);
} else {
System.err.println("Do not write blank article!");
}
return "redirect:/board?writeResult=" + result;
}
}


package com.springboot.security.dao;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class SimpleBoardDAO {
@Autowired JdbcTemplate jt;
public int insertAnArticle(Map<String, Object> article) {
String sql = "insert into simple_board values(0, ?, sysdate(), ?, ?)";
return jt.update(sql,
article.get("username"),
article.get("title"),
article.get("content"));
}
public List<Map<String, Object>> getBoardList(){
String sql = "select * from simple_board order by write_date desc";
return jt.query(sql, (rs, rowNum) -> {
Map<String, Object> anArticle = new HashMap<>();
anArticle.put("seq", rs.getString(1));
anArticle.put("username", rs.getString(2));
anArticle.put("writeDate", rs.getString(3));
anArticle.put("title", rs.getString(4));
anArticle.put("content", rs.getString(5));
return anArticle;
});
}
public Map<String, Object> getAnArticle(int articleId){
String sql = "select * from simple_board where seq=?";
return jt.queryForObject(sql, new Object[] {articleId}, (rs, rowNum) -> {
Map<String, Object> anArticle = new HashMap<>();
anArticle.put("seq", rs.getString(1));
anArticle.put("username", rs.getString(2));
anArticle.put("writeDate", rs.getString(3));
anArticle.put("title", rs.getString(4));
anArticle.put("content", rs.getString(5));
return anArticle;
});
}
public int deleteAnArticle(int articleId, String username) {
String sql = "delete from simple_board where seq = ? and username = ?";
return jt.update(sql, articleId, username);
}
public int updateAnArticle(Map<String, Object> article) {
String sql = "update simple_board set title = ?, content = ? where seq = ? and username = ?";
return jt.update(sql,
article.get("title"),
article.get("content"),
article.get("articleId"),
article.get("username"));
}
}

 

동작 내용

메인 화면 (로그인한 경우)

 

메인 화면 (로그인하지 않은 경우)

 

게시글 보기 (내 글)

 

게시글 보기 (다른 사람의 글)

 

게시글 작성

 

게시글 수정

 

게시글 삭제

문의 | 코멘트 또는 yoonbumtae@gmail.com




3개의 댓글

최남규 · 2020년 6월 18일 1:36 오전

좋은글 감사합니다 혹시 작성하신 소스 받을수 있을까요?
제공 가능하시면 부탁드리겠습니다.
cnam9@naver.com

감사합니다.

Spring Boot 예제: 회원가입 폼 만들기 - BGSMM · 2019년 12월 10일 2:17 오전

[…] 후 게시판(Spring Boot 예제: 초간단 게시판)에 글 […]

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다