2023-12-08 작성

스프링부트 개발환경 구성하기 (9) 게시물 조회/등록/수정/삭제

 

포스팅 순서는 아래와 같다.


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>