오늘 만들 것
지금까지 게시물을 작성하는 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