2023-12-08
작성
포스팅 순서는 아래와 같다.
- OpenJDK 17 설치
- STS 4 설치
- 스프링부트 프로젝트 생성
- 메이븐 Local Repository 설정
- DB 설계와 REST API 설계
- mybatis 연동과 게시판 목록 조회
- 페이징 (Mybatis에서 Pageable)
- 동적 정렬처리
- 게시물 조회/등록/수정/삭제 (현재 포스팅)
REST API 설계 내용을 기반으로 게시물 조회, 등록, 수정, 삭제 기능을 구현했다. 사실 크게 어려운 게 없기 때문에 소스 만으로 충분히 설명이 될 것 같다.
BoardController.java
package com.company.helloBoard.domain.board.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.company.helloBoard.domain.board.model.Board;
import com.company.helloBoard.domain.board.service.BoardService;
@RestController
@RequestMapping(value = "/boards")
public class BoardController {
@Autowired
BoardService boardService;
@GetMapping("/{boardId}")
public ResponseEntity<Board> getBoard(@PathVariable("boardId") int boardId) {
return ResponseEntity.ok(boardService.getBoard(boardId));
}
@PostMapping("")
public ResponseEntity<Integer> insertBoard(@RequestBody Board board) {
return ResponseEntity.ok(boardService.insertBoard(board));
}
@PutMapping("/{boardId}")
public ResponseEntity<Integer> updateBoard(@PathVariable("boardId") int boardId, @RequestBody Board board) {
return ResponseEntity.ok(boardService.updateBoard(boardId, board));
}
@DeleteMapping("/{boardId}")
public ResponseEntity<Integer> deleteBoard(@PathVariable("boardId") int boardId) {
return ResponseEntity.ok(boardService.deleteBoard(boardId));
}
}
BoardService.java
package com.company.helloBoard.domain.board.service;
import com.company.helloBoard.domain.board.model.Board;
public interface BoardService {
public Board getBoard(int boardId);
public int insertBoard(Board board);
public int updateBoard(int boardId, Board board);
public int deleteBoard(int boardId);
}
BoardServiceImpl.java
package com.company.helloBoard.domain.board.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.company.helloBoard.domain.board.mapper.BoardMapper;
import com.company.helloBoard.domain.board.model.Board;
@Service
public class BoardServiceImpl implements BoardService {
@Autowired
BoardMapper boardMapper;
@Override
public Board getBoard(int boardId) {
return boardMapper.getBoard(boardId);
}
@Override
public int insertBoard(Board board) {
return boardMapper.insertBoard(board);
}
@Override
public int updateBoard(int boardId, Board board) {
board.setBoardId(boardId);
return boardMapper.updateBoard(board);
}
@Override
public int deleteBoard(int boardId) {
return boardMapper.deleteBoard(boardId);
}
}
BoardMapper.java
package com.company.helloBoard.domain.board.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.company.helloBoard.domain.board.model.Board;
@Mapper
public interface BoardMapper {
Board getBoard(int boardId);
int insertBoard(Board board);
int updateBoard(Board board);
int deleteBoard(int boardId);
}
BoardMapper.xml
이전 포스팅에서 생성한 sq_board_id 시퀀스를 이용하여, 글을 등록할때마다 게시글ID를 자동 채번하도록 했다. 이때 주의할 점은 h2 DB 문법으로 NEXTVAL을 사용한 것이기 때문에 DB 종류마다 문법이 조금씩 다르다. 예를 들어 오라클은 sq_board_id. NEXTVAL 형식으로 사용해야 오류가 발생하지 않는다.
<?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.company.helloBoard.domain.board.mapper.BoardMapper">
<select id="getBoard" parameterType="integer" resultType="Board">
SELECT BOARD_ID
, WRITER
, TITLE
, CONTENT
, REG_DATE
, UPDATE_DATE
, DELETE_DATE
FROM BOARD
WHERE BOARD_ID = #{boardId}
</select>
<insert id="insertBoard" parameterType="Board">
INSERT INTO BOARD (
BOARD_ID
, WRITER
, TITLE
, CONTENT
, REG_DATE
, UPDATE_DATE
, DELETE_DATE
) VALUES (
NEXTVAL('sq_board_id')
, #{writer}
, #{title}
, #{content}
, TO_CHAR(SYSDATE, 'YYYYMMDDHH24MISS')
, ''
, ''
)
</insert>
<update id="updateBoard" parameterType="Board">
UPDATE BOARD
SET TITLE = #{title}
, CONTENT = #{content}
, UPDATE_DATE = TO_CHAR(SYSDATE, 'YYYYMMDDHH24MISS')
WHERE BOARD_ID = #{boardId}
</update>
<delete id="deleteBoard" parameterType="integer">
DELETE FROM BOARD
WHERE BOARD_ID = #{boardId}
</delete>
</mapper>
페이지 추가
list.html
게시물 제목을 클릭하면 view.html 으로 이동하고, 글쓰기 버튼 클릭시 edit.html 으로 이동한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>게시판</title>
<link type="text/css" href="/css/style.css" rel="stylesheet">
<script type="text/javascript" src="/js/com-ajax.js"></script>
<script type="text/javascript" src="/js/com-page.js"></script>
</head>
<script type="text/javascript">
// 페이지 온로드시 게시글 목록 조회
window.onload = function () {
getList();
}
// 게시글 목록 조회
function getList(pageNo) {
pageNo = pageNo || 0;
const title = document.querySelector("#title").value;
const writer = document.querySelector("#writer").value;
const sort = document.querySelector("#sort").value;
const data = "?title=" + title + "&writer=" + writer + "&page=" + pageNo + "&sort=" + sort;
AJAX.ajaxCall("GET", "/boards", data, afterGetList);
}
// 조회 후 처리
function afterGetList(response) {
PAGE.paging(response.totalPages, response.number, response.totalElements, "getList");
// 결과 테이블 생성
resultTable(response);
}
// 동적으로 테이블 생성
function resultTable(response) {
document.querySelector("#fieldListBody").innerHTML = "";
if (response.size > 0) {
const content = response.content;
// ✅ 제목 클릭시 뷰 화면 이동
for (var i = 0; i < content.length; i++) {
let element = document.querySelector("#fieldListBody");
let result = content[i];
let template = `
<td><p>${PAGE.pageRowNumber(response.number, response.size, i, response.totalElements)}</p></td>
<td><p><a href="/board/view?boardId=${result.boardId}">${result.title}</a></p></td>
<td><p>${result.writer}</p></td>
<td><p>${result.regDate}</p></td>
`;
element.insertAdjacentHTML('beforeend', template);
}
}
}
// 초기화
function resetList() {
document.querySelector("#title").value = "";
document.querySelector("#writer").value = "";
document.querySelector("#fieldListBody").innerHTML = "";
// 초기화
let reg_date = document.querySelector("#reg_date");
if (reg_date.className == 'sort_asc') {
reg_date.classList.toggle("sort_desc");
reg_date.classList.toggle("sort_asc");
}
// 정렬 초기화
document.querySelector("#sort").value = "";
getList();
}
// 동적 정렬
function dynamicSort(sortColumn) {
let sortId = sortColumn.id;
sortColumn.classList.toggle("sort_desc");
sortColumn.classList.toggle("sort_asc");
if (sortColumn.className == "sort_desc") {
document.querySelector("#sort").value = sortId + ",DESC";
} else {
document.querySelector("#sort").value = sortId + ",ASC";
}
// 조회
getList();
}
// ✅ 글쓰기 화면 이동
function insertBoard() {
location.href = "/board/edit";
}
</script>
<body>
<div>
<h2>게시판 목록</h2>
<table>
<tr>
<th>제목</th>
<td><input type="text" id="title"></td>
</tr>
<tr>
<th>작성자</th>
<td><input type="text" id="writer"></td>
</tr>
<tr>
<td><button onclick="getList()">조회</button></td>
<td><button onclick="resetList()">초기화</button></td>
<!-- ✅ 버튼 추가 -->
<td><button onclick="insertBoard()">글쓰기</button></td>
</tr>
</table>
<input type="hidden" name="page" id="page" value="0" />
<input type="hidden" name="sort" id="sort" value="" />
</div>
<div>
<table>
<colgroup>
<col width="150px" />
<col width="150px" />
<col width="150px" />
<col width="250px" />
</colgroup>
<thead>
<tr>
<th>No.</th>
<th>제목</th>
<th>작성자</th>
<th>
<p class="sort_desc" id="reg_date" onclick="dynamicSort(this)">작성시간</p>
</th>
</tr>
</thead>
<tbody id="fieldListBody">
</tbody>
</table>
<ul id="pagingArea" class="pagination"></ul>
</div>
</body>
</html>
view.html
view.html 화면에서 수정 버튼을 클릭하면 편집모드로 이동한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>게시판</title>
<script type="text/javascript" src="/js/com-ajax.js"></script>
</head>
<script type="text/javascript">
// 페이지 온로드시 게시글 조회
window.onload = function () {
getBoard();
}
// 게시글 조회
function getBoard() {
const urlParams = new URL(location.href).searchParams;
const boardId = urlParams.get('boardId');
AJAX.ajaxCall("GET", "/boards/" + boardId, "", afterGetInfo);
}
// 조회 후 처리
function afterGetInfo(response) {
document.querySelector("#boardId").value = response.boardId;
document.querySelector("#writer").innerHTML = response.writer;
document.querySelector("#title").innerHTML = response.title;
document.querySelector("#content").innerHTML = response.content;
}
// 수정 화면으로 이동
function moveUpdatePage() {
let boardId = document.querySelector("#boardId").value;
location.href = "/board/edit?boardId=" + boardId;
}
// 삭제
function deleteBoard() {
let boardId = document.querySelector("#boardId").value;
AJAX.ajaxCall("DELETE", "/boards/" + boardId, "", backList);
}
// 목록으로 이동
function backList() {
location.href = "/board";
}
</script>
<body>
<h2>게시판 상세</h2>
<input type="hidden" name="boardId" id="boardId" value="" />
<table>
<tr>
<th>작성자</th>
<td>
<div id="writer"></div>
</td>
</tr>
<tr>
<th>제목</th>
<td>
<div id="title"></div>
</td>
</tr>
<tr>
<th>내용</th>
<td>
<div id="content"></div>
</td>
</tr>
</table>
<button onclick="moveUpdatePage()">수정</button>
<button onclick="deleteBoard()">삭제</button>
<button onclick="backList()">목록</button>
</body>
</html>
edit.html
게시물 등록 또는 수정하고 다시 게시글 목록으로 돌아간다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<title>게시판</title>
<script type="text/javascript" src="/js/com-ajax.js"></script>
</head>
<script type="text/javascript">
// 페이지 온로드시 게시글 조회
window.onload = function () {
// 파라미터에서 boardId 값 추출
const urlParams = new URL(location.href).searchParams;
const boardId = urlParams.get('boardId');
// 값 세팅
document.querySelector("#boardId").value = boardId;
// 제목과 버튼 세팅
const boardTitle = document.querySelector("h2#boardTitle");
const updateBtn = document.querySelector("#updateBtn");
const insertBtn = document.querySelector("#insertBtn");
if (boardId != null) {
boardTitle.textContent = "게시물 수정";
insertBtn.style.display = 'none';
} else {
boardTitle.textContent = "게시물 등록";
updateBtn.style.display = 'none';
}
// 수정일 경우 조회
if (boardId != null) {
getBoard(boardId);
}
}
// 게시글 조회
function getBoard(boardId) {
AJAX.ajaxCall("GET", "/boards/" + boardId, "", afterGetInfo);
}
// 조회 후 처리
function afterGetInfo(response) {
document.querySelector("#boardId").value = response.boardId;
document.querySelector("#writer").value = response.writer;
document.querySelector("#title").value = response.title;
document.querySelector("#content").value = response.content;
}
// 등록
function insertBoard() {
const data = {
writer: document.querySelector("#writer").value,
title: document.querySelector("#title").value,
content: document.querySelector("#content").value
};
AJAX.ajaxCall("POST", "/boards", data, backList);
}
// 수정
function updateBoard() {
let boardId = document.querySelector("#boardId").value;
const data = {
title: document.querySelector("#title").value,
content: document.querySelector("#content").value
};
AJAX.ajaxCall("PUT", "/boards/" + boardId, data, backList);
}
// 목록으로 이동
function backList() {
location.href = "/board";
}
</script>
<body>
<h2 id="boardTitle"></h2>
<input type="hidden" name="boardId" id="boardId" value="" />
<table>
<tr>
<th>작성자</th>
<td>
<input type="text" id="writer" />
</td>
</tr>
<tr>
<th>제목</th>
<td>
<input type="text" id="title" />
</td>
</tr>
<tr>
<th>내용</th>
<td>
<input type="text" id="content" />
</td>
</tr>
</table>
<button onclick="updateBoard()" id="updateBtn">수정</button>
<button onclick="insertBoard()" id="insertBtn">등록</button>
<button onclick="backList()">목록</button>
</body>
</html>
'Backend > Spring, SpringBoot' 카테고리의 다른 글
Spring Security 3 버전에서 h2.console.enabled : true일 때 servlet 오류 해결 방법 (0) | 2023.12.18 |
---|---|
스프링부트 Scheduler 정해진 시간마다 동작 시키는법 (2) | 2023.12.05 |
Spring Boot 내장 톰캣 제외시키는 방법 (0) | 2023.12.05 |
Spring Boot에서 static 변수로 선언한 @Value 값이 NULL일 경우 (0) | 2023.12.01 |
스프링부트 EnvironmentPostProcessor로 사용자 정의 (0) | 2023.11.30 |