

오늘 만들 것
지금까지 게시물을 작성하는 API와 호출, 수정, 삭제하는 API까지 만들었습니다.
마지막으로 게시물 목록을 호출하는 API를 구현합니다.
시작
이번 글은 Lab09의 코드에 기능을 추가하는 방식으로 진행합니다.
Lab09는 이전 글에서 확인할 수 있습니다.
별도의 프로젝트에서 진행하고자 하면, Lab09와 동일하게 프로젝트를 생성한 뒤 코드를 복사해서 준비합니다.
Lab09의 코드를 그대로 사용한다면 아래 예제 코드의 패키지명에 유의합니다.
아래 예제 코드는 별도의 프로젝트를 생성하는 방식으로 진행합니다.
DTO 추가
다음 경로의 파일을 아래와 같이 작성하여 추가합니다: /src/main/java/YOUR/DOMAIN/ARTIFACT/dto/ListDTO.java
package net.jetalab.spreinglab10.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor
@Getter
@Setter
public class ListDTO {
private int lastSeq = 0;
private int size = 10;
private int page = 1;
public int getSkip() {
return (page - 1) * size;
}
}
11번째 줄: 이전 목록의 마지막 항목의 seq입니다.
12번째 줄: 한 번에 불러올 목록의 갯수입니다. 기본값은 10으로 선언합니다.
13번째 줄: 호출할 페이지입니다. 기본값은 1페이지를 호출합니다.
15번째 줄 ~ 17번째 줄: 실제로 skip 변수가 존재하진 않지만 skip 변수를 호출하면 값을 계산하여 반환합니다. MyBatis가 Getter를 이용해 값을 호출하는 것을 이용한 코딩입니다.
DAO 수정
다음 경로의 파일을 아래와 같이 편집합니다: /src/main/java/YOUR/DOMAIN/ARTIFACT/dao/BoardDAO.java
package net.jetalab.spreinglab10.dao;
import net.jetalab.spreinglab10.dto.BoardDTO;
import net.jetalab.spreinglab10.dto.ListDTO;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository("boardDAO")
public class BoardDAO {
@Autowired
protected SqlSessionTemplate sqlSession;
public int newBoard(BoardDTO param) throws Exception {
return sqlSession.insert("newBoard", param);
}
public BoardDTO getBoard(BoardDTO param) throws Exception {
return sqlSession.selectOne("getBoard", param);
}
public int editBoard(BoardDTO param) throws Exception {
return sqlSession.update("editBoard", param);
}
public int addBoardReadCount(BoardDTO param) throws Exception {
return sqlSession.update("addBoardReadCount", param);
};
public List<BoardDTO> getBoardList(ListDTO param) throws Exception {
if (param.getLastSeq() > 0) {
return sqlSession.selectList("getBoardListNext", param);
} else {
return sqlSession.selectList("getBoardListPage", param);
}
}
}
12번째 줄: 더 이상 BoardDAO는 Interface가 아닙니다. 파라미터에 따라 다양한 작업을 수행할 수 있도록 Class로 선언합니다.
32번째 줄 ~ 38번째 줄: 파라미터의 값에 따라 알맞은 SQL을 호출하도록 구성되었습니다. lastSeq의 유무에 따라 작동이 달라집니다. 작동에 대한 자세한 내용은 아래 SQL Mapper에서 설명합니다. 같은 기능이지만 SQL이 많이 상이한 경우에는 이렇게 사용할 수 있다는 것만 알아두면 됩니다.
SQL Mapper 수정
다음 경로의 파일을 아래와 같이 편집합니다: /src/main/resources/mappers/UserMapper.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="net.jetalab.spreinglab10.dao.BoardDAO">
<insert id="newBoard" parameterType="net.jetalab.spreinglab10.dto.BoardDTO" useGeneratedKeys="true" keyProperty="seq">
INSERT INTO lab10(`title`, `contents`, `author`, `password`)
VALUES (#{title}, #{contents}, #{author}, #{password})
</insert>
<select id="getBoard" parameterType="net.jetalab.spreinglab10.dto.BoardDTO" resultType="net.jetalab.spreinglab10.dto.BoardDTO">
SELECT `seq`, `title`, `contents`, `author`, `reads`
FROM lab10
WHERE `seq` = #{seq}
<if test="password != null">
AND `password` = #{password}
</if>
AND `deleted` = 'N'
</select>
<update id="editBoard" parameterType="net.jetalab.spreinglab10.dto.BoardDTO">
UPDATE lab10
SET `password` = `password`
<if test="title != null">
, `title` = #{title}
</if>
<if test="contents != null">
, `contents` = #{contents}
</if>
<if test="author != null">
, `author` = #{author}
</if>
<if test="deleted != null">
, `deleted` = #{deleted}
</if>
WHERE `seq` = #{seq}
</update>
<update id="addBoardReadCount" parameterType="net.jetalab.spreinglab10.dto.BoardDTO">
UPDATE lab09
SET `reads` = `reads` + 1
WHERE `seq` = #{seq}
AND `deleted` = 'N'
</update>
<select id="getBoardListNext" resultType="net.jetalab.spreinglab10.dto.BoardDTO">
SELECT `seq`, `title`, `contents`, `author`, `reads`
FROM lab10
WHERE 1 = 1
AND `deleted` = 'N'
<if test="lastSeq > 0">
AND `seq` < #{lastSeq}
</if>
ORDER BY `seq` DESC
LIMIT 0, #{size}
</select>
<select id="getBoardListPage" resultType="net.jetalab.spreinglab10.dto.BoardDTO">
SELECT `seq`, `title`, `contents`, `author`, `reads`
FROM lab10
WHERE 1 = 1
AND `deleted` = 'N'
ORDER BY `seq` DESC
LIMIT #{skip}, #{size}
</select>
</mapper>
46번째 줄 ~ 56번째 줄: 만약 lastSeq 값이 있다면 실행됩니다. 지난 페이지에서 호출한 글 중 가장 마지막글 이전의 글들을 조회합니다. 이러한 조회 방식은 보통 모바일에서 많이 사용됩니다. 목록의 끝까지 내려가면 그 목록의 다음 게시물을 불러와서 덧붙이는 형태로 이용됩니다.
58번째 줄 ~ 65번째 줄: lastSeq값이 없다면 실행됩니다. 일반적인 페이징 방식으로 조회합니다. 이 때 64번째 줄에서 skip을 호출하는데, 앞서 ListDTO의 getSkip() 메소드를 실행하게 됩니다.
Controller 수정
다음 경로에 아래 코드를 작성하여 저장합니다: /src/main/java/YOUR/DOMAIN/ARTIFACT/controller/BoardController.java
package net.jetalab.spreinglab10.controller;
import net.jetalab.spreinglab10.dao.BoardDAO;
import net.jetalab.spreinglab10.dto.BoardDTO;
import net.jetalab.spreinglab10.dto.ListDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@EnableAutoConfiguration
public class BoardController {
@Autowired
private BoardDAO boardDAO;
@RequestMapping(value = "/board", method = RequestMethod.POST)
public ResponseEntity<BoardDTO> postBoard(BoardDTO board) throws Exception {
if ((board.getAuthor() == null) || (board.getContents() == null) || (board.getPassword() == null) || (board.getTitle() == null)) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
boardDAO.newBoard(board);
return new ResponseEntity<>(board, HttpStatus.OK);
}
@RequestMapping(value = "/board/{seq}", method = RequestMethod.GET)
public ResponseEntity<BoardDTO> getBoard(@PathVariable("seq") final int seq) throws Exception {
BoardDTO param = new BoardDTO();
param.setSeq(seq);
boardDAO.addBoardReadCount(param);
BoardDTO board = boardDAO.getBoard(param);
if (board == null) return new ResponseEntity<>(HttpStatus.NOT_FOUND);
else return new ResponseEntity<>(board, HttpStatus.OK);
}
@RequestMapping(value = "/board/{seq}", method = RequestMethod.PUT)
public ResponseEntity<BoardDTO> putBoard(@PathVariable("seq") final int seq, BoardDTO param) throws Exception {
if ((param.getAuthor() == null) || (param.getContents() == null) || (param.getPassword() == null) || (param.getTitle() == null)) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
param.setSeq(seq); // 조회할 게시물 번호 지정
BoardDTO board = boardDAO.getBoard(param);
if (board == null) return new ResponseEntity<>(HttpStatus.NOT_FOUND);
board.setTitle(param.getTitle());
board.setContents(param.getContents());
board.setAuthor(param.getAuthor());
boardDAO.editBoard(board);
return new ResponseEntity<>(board, HttpStatus.OK);
}
@RequestMapping(value = "/board/{seq}", method = RequestMethod.DELETE)
public ResponseEntity<BoardDTO> deleteBoard(@PathVariable("seq") final int seq, BoardDTO param) throws Exception {
if (param.getPassword() == null) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
param.setSeq(seq); // 조회할 게시물 번호 지정
BoardDTO board = boardDAO.getBoard(param);
if (board == null) return new ResponseEntity<>(HttpStatus.NOT_FOUND);
board.setDeleted("Y");
boardDAO.editBoard(board);
return new ResponseEntity<>(HttpStatus.OK);
}
@RequestMapping(value = "/board", method = RequestMethod.GET)
public ResponseEntity<List> getBoardList(ListDTO param) throws Exception {
List<BoardDTO> board = boardDAO.getBoardList(param);
return new ResponseEntity<>(board, HttpStatus.OK);
}
}
80번째 줄 ~ 85번째 줄: 게시물 조회하는 API와 크게 다르지 않습니다. 파라미터를 ListDTO 객체로 받는 점에 주의합니다.
테스트
먼저 아무 파라미터 없이 호출하면 가장 최근 글부터 순서대로 출력됩니다.

size 파라미터를 넘기면 size에 맞추어 목록 길이가 바뀝니다.

이번엔 lastSeq 변수를 넘겨서 다음 항목이 나오는지 확인합니다.

이번엔 page 변수를 넘기면 해당 페이지의 게시물을 불러옵니다.

*
이번 글까지 총 9편의 포스트로 게시판 API가 완성되었습니다.
실무에서 사용되는 것과 비교해본다면 기능도 많이 부족하고, 예외처리도 되어있지 않지만
데이터 입출력의 기본이 되는 CRUD가 모두 들어있습니다.
모든 웹 서비스는 데이터를 만들거나, 호출하거나, 수정하거나 삭제하는 과정으로 이루어져 있습니다.
이 글을 읽으신 독자가 있으시다면 게시판 기능 자체에 주목하기보단 데이터를 다루는 것을 이해하셨길 바랍니다.
지금까지 부족한 글을 읽어주셔서 감사합니다.
예제 코드
본 포스트의 예제 코드는 GitHub에 공개되어 있습니다.
https://github.com/jETA-Kor/sp-re-ing/tree/master/lab10