[ Java / SpringBoot / MVC / RESTful] RESTful한 게시판 만들기3 / 전체 목록 조회, 게시글 작성, 게시글 조회
낫쏘링
2023. 9. 10. 19:22
728x90
1. MVC 패턴을 사용할 것이기 때문에 controller / service / mapper 패키지를 생성하고 클래스를 생성해준다. 단, BoardMapper는 클래스가 아닌 인터페이스로 생성해준다. 쿼리는 xml 파일에서 구현해줄 것이기 때문이다.
2. Controller 작성
// 해당 클래스가 Controller의 역할을 한다고 명시해주는 어노테이션
// 주의할 점은 컨트롤러든 서비스든 매퍼든 같은 이름의 Bean 등록이 불가능하다는 점이다.
// 만약 a 패키지와 b 패키지에 각각 board 클래스에 컨트롤러를 등록할 경우 오류 발생
@Controller
// 필드 중 final이 붙은 필드만을 포함하여 생성자를 생성해주는 어노테이션 (의존성 주입)
@RequiredArgsConstructor
public class BoardController {
// 의존성 주입을 위한 필드 선언(컨트롤러에서 BordService라는 서비스를 사용할 것이기 때문)
// 주의할 점(DTO는 의존성 주입해서 사용하지 않는다.)
private final BoardService boardService;
// 게시판 목록 조회 화면 (Read / 조회 HTTP Method - GET)
@GetMapping("/board")
public String openBoardList(Model model) throws Exception{
List<BoardDto> list = boardService.selectBoardList();
model.addAttribute("list", list);
return "/board/restBoardList";
}
}
3. Service 작성
// 해당 클래스가 서비스 역할을 한다고 명시하는 어노테이션
@Service
// 의존성 주입
@RequiredArgsConstructor
public class BoardService {
private final BoardMapper boardMapper;
// 게시글 내역 조회
public List<BoardDto> selectBoardList() throws Exception{
List<BoardDto> boardList = boardMapper.selectBoardList();
return boardList;
}
}
4. Mapper 인터페이스 작성
@Mapper
public interface BoardMapper {
// 게시글 내역 조회
// 인터페이스는 구현부가 존재하지 않는데 상속 받는 클래스에서 구현해야한다.
List<BoardDto> selectBoardList() throws Exception;
}
5. dao 작성 (mapper.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace : implememts와 같은 기능 -->
<!-- class 상속받은 클래스 implements 인터페이스와 같은 기능이라고 생각하면 된다. -->
<!-- 위에서 작성한 BoardMapper.java 인터페이스의 상속을 받아 구현하겠다는 뜻 -->
<!-- 주의할 점은 인터페이스를 상속 받은 경우 인터페이스의 모든 메서드를 구현해야 한다는 것-->
<!-- CDATA : 특수 문자(<, >, & 등)를 태그로 인식하지 않고 쿼리 코드라는 것을 명시 -->
<!-- 특수 문자가 없는 경우에도 넣어준 이유는 코드의 일관성과 나중에 쿼리가 수정될 수 있기 때문 -->
<mapper namespace="board.rest.api.mapper.BoardMapper">
<!-- 게시글을 '조회'하는 쿼리이기 때문에 select 사용 -->
<!-- id는 인터페이스의 메서드와 일치 -->
<!-- resultType은 리턴할 데이터의 타입 -->
<!-- 반대로 parameterType은 매개변수라고 보면되는데 매개변수가 없기 때문에 사용하지 않음-->
<!-- 처음에 mybatis를 설정할때 자바변수(카멜)와 db컬럼(스테이크)를 자동으로 매핑해주도록 했기 때문에 -->
<!-- 별칭(AS)을 따로 써주지 않아도 자동으로 DTO의 필드와 연결해준다. -->
<select id="selectBoardList" resultType="BoardDto">
/* 게시글 내역 조회 */
<![CDATA[
SELECT
board_idx,
title,
hit_cnt,
created_datetime
FROM
t_board
WHERE
deleted_yn = 'N'
ORDER BY board_idx DESC
]]>
</select>
</mapper>
6. html 작성 (타임리프)
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>board</title>
<link rel="stylesheet" th:href="@{/css/style.css}"/>
</head>
<body>
<div class="container">
<h2>게시글 목록</h2>
<table class="board_list">
<colgroup>
<col width="15%"/>
<col width="*"/>
<col width="15%"/>
<col width="20%"/>
</colgroup>
<thead>
<tr>
<th scope="col">글번호</th>
<th scope="col">제목</th>
<th scope="col">조회수</th>
<th scope="col">작성일</th>
</tr>
</thead>
<tbody>
<!-- th:if - 타임리프 문법을 사용 할 경우에 쓰는 if 문 -->
<!-- if문 내의 조건이 일치해야만 해당 tr이 생성된다. -->
<!-- th:each - 타임리프 문법을 사용할 경우에 쓰는 반복문 ( ${list}에 들어있는 값을 모두 순회하면서 tr 태그를 생성한다. )-->
<tr th:if="${#lists.size(list)} > 0" th:each="list : ${list}">
<td th:text="${list.boardIdx}"></td>
<!-- th:href - 타임리프 문법을 사용할 경우에 쓰는 href 속성 -->
<!-- href="/board/" th:attrappend="href=${list.boardIdx}" 로도 작성 가능하다. -->
<td class="title"><a th:href="'/board/' + ${list.boardIdx}" th:text="${list.title}"></a></td>
<td th:text="${list.hitCnt}"></td>
<td th:text="${list.createdDatetime}"></td>
</tr>
<!-- th:unless - 타임리프 문법을 사용할 경우에 쓴는 if 문 / 단, if와 반대로 조건이 일치하지 않을 경우 tr 생성 -->
<tr th:unless="${#lists.size(list)} > 0">
<td colspan="4">조회된 결과가 없습니다.</td>
</tr>
</tbody>
</table>
<a href="/board/write" class="btn">글 쓰기</a>
</div>
</body>
</html>
완성! 현재 작성된 글이 없기 때문에 th:unless 구문이 작동했다.
게시글 작성과 게시글 조회도 같은 방법으로 HTTP URL(자원/Resource)와 HTTP Method를 지켜서 작성하면 된다.
Controller
// 게시글 등록 화면
// 등록화면은 DB로부터 받을 데이터가 없기 때문에 로직은 따로 없다.
// 그냥 HTML 경로만 리턴해주면 된다.
@GetMapping("/board/view")
public String openBoardWrite(Model model) throws Exception{
return "/board/restBoardWrite";
}
// 게시글 등록 처리 (Create / 등록 HTTP Method - POST)
@PostMapping("/board")
public String insertBoard(BoardDto board) throws Exception{
// BoardDto에 작성한 데이터를 담아 DB에 전달해준다. (parameterType 존재)
boardService.insertBoard(board);
// 전체 목록 조회 경로로 새로고침
return "redirect:/board";
}
// 게시글 상세 조회 화면
@GetMapping("/board/view/{boardIdx}")
public String openBoardDetail(@PathVariable("boardIdx") int boardIdx, Model model) throws Exception{
// @PathVariable : 요청(Request)을 받아와서 리소스에 사용 가능
// ("매핑에 사용할 변수 이름") 변수와 묶어 줄 매개변수
// BoardDto에 boardInd(PK)를 담아 DB에 전달해준다. (parameterType 존재)
// BoardDto에 전달 받은 데이터를 담아서 view에 전달해준다.
BoardDto board = boardService.selectBoardDetail(boardIdx);
model.addAttribute("board", board);
return "/board/restBoardDetail";
}
Service
// 게시글 등록
public void insertBoard(BoardDto board) throws Exception{
boardMapper.insertBoard(board);
}
// 게시글 상세 내용 조회
public BoardDto selectBoardDetail(int boardIdx) throws Exception{
// 상세 내용 조회 -> 조회수 증가
boardMapper.updateHitCount(boardIdx);
// 상세 내용 가져오기
BoardDto boardDetail = boardMapper.selectBoardDetail(boardIdx);
return boardDetail;
}
dao
<insert id="insertBoard" parameterType="BoardDto">
/* 게시글 등록 */
<![CDATA[
INSERT INTO t_board
(
title,
contents,
created_datetime,
creator_id
)
VALUES
( #{title},
#{contents},
NOW(),
'admin'
)
]]>
</insert>
<update id="updateHitCount" parameterType="int">
/* 조회수 증가 */
<![CDATA[
UPDATE
t_board
SET
hit_cnt = hit_cnt + 1
WHERE
board_idx = #{boardIdx}
]]>
</update>
<select id="selectBoardDetail" parameterType="int" resultType="BoardDto">
/* 해당 게시글 조회 */
<![CDATA[
SELECT
board_idx,
title,
contents,
hit_cnt,
creator_id,
created_datetime
FROM
t_board
WHERE
board_idx = #{boardIdx}
]]>
</select>