@RestController
- REST 기능을 제공하는 요청 처리 메소드(Restful API) 만 선언된 Controller 클래스를 Spring Bean 으로 등록하는 어노테이션
- 요청 처리 메소드에 @ResponseBody 어노테이션을 사용하지 않아도 문자열로 응답 처리가 가능하다.
- REST 기능을 제공하는 요청 처리 메소드는 @RequestMapping 어노테이션 대신 @GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping 어노테이션을 사용하여 요청 페이지를 요청 처리 메소드와 매핑 처리하는 것을 권장한다.
- 요청 방식 : GET(검색), POST(삽입), PUT(전체 변경), PATCH(부분 변경), DELETE(삭제) 등
REST 이용해 JSON으로 응답받아 처리할 수 있도록 해보자.
예제)

board.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SPRING</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<style type="text/css">
#btnDiv {
margin: 10px;
}
#restBoardTable {
border: 1px solid black;
border-collapse: collapse;
}
#restBoardTable td, #restBoardTable th {
border: 1px solid black;
padding: 3px;
}
.inputDiv {
width: 240px;
height: 80px;
border: 2px solid black;
background-color: gray;
position: absolute;
top: 50%;
left: 50%;
margin-top: -40px;
margin-left: -120px;
padding: 5px;
z-index: 100;
display: none;
}
</style>
</head>
<body>
<h1>RestBoard</h1>
<hr>
<div id="btnDiv">
<button type="button" id="writeBtn">글쓰기</button>
</div>
<%-- 게시글 목록을 출력하는 태그 --%>
<div id="restBoardListDiv"></div>
<%-- 페이지 번호를 출력하는 태그 --%>
<div id="pageNumDiv"></div>
<%-- 신규 게시글을 입력받기 위한 태그 --%>
<div id="insertDiv" class="inputDiv">
<table>
<tr>
<td>작성자</td>
<td><input type="text" id="insertWriter" class="insert"></td>
</tr>
<tr>
<td>내용</td>
<td><input type="text" id="insertContent" class="insert"></td>
</tr>
<tr>
<td colspan="2">
<button type="button" id="insertBtn">저장</button>
<button type="button" id="cancelInsertBtn">취소</button>
</td>
</tr>
</table>
</div>
<%-- 변경 게시글을 입력받기 위한 태그 --%>
<div id="updateDiv" class="inputDiv">
<input type="hidden" id="updateNum">
<table>
<tr>
<td>작성자</td>
<td><input type="text" id="updateWriter" class="update"></td>
</tr>
<tr>
<td>내용</td>
<td><input type="text" id="updateContent" class="update"></td>
</tr>
<tr>
<td colspan="2">
<button type="button" id="updateBtn">변경</button>
<button type="button" id="cancelUpdateBtn">취소</button>
</td>
</tr>
</table>
</div>
</body>
</html>
테이블, 시퀀스 생성
//테이블 생성
create table restboard(idx number primary key, writer varchar2(50)
, content varchar2(100), regdate date);
//시퀀스 생성
create sequence restboard_seq;
DTO 클래스 선언
@Data
public class RestBoard {
private int idx;
private String writer;
private String content;
private String regdate;
}
Mapper 생성
RestBoardMapper.xml
<mapper namespace="xyz.itwill10.mapper.RestBoardMapper">
<insert id="insertRestBoard">
<selectKey resultType="int" keyProperty="idx" order="BEFORE">
select restboard_seq.nextval from dual
</selectKey>
insert into restboard values(#{idx}, #{writer}, #{content}, sysdate)
</insert>
<update id="updateRestBoard">
update restboard set writer=#{writer}, content=#{content} where idx=#{idx}
</update>
<delete id="deleteBoard">
delete from restboard where idx=#{idx}
</delete>
<select id="selectRestBoard" resultType="RestBoard">
select idx, writer, content, regdate from restboard where idx=#{idx}
</select>
<select id="selectRestBoardCount" resultType="int">
select count(*) from restboard
</select>
<select id="selectRestBoardList" resultType="RestBoard">
select * from (select rownum rn, board.* from (select idx, writer, content, regdate
from restboard oder by idx desc) board) where rn between #{startRow} and #{endRow}
</select>
</mapper>
RestBoardMapper
public interface RestBoardMapper {
int insertRestBoard(RestBoard restBoard);
int updateRestBoard(RestBoard restBoard);
int deleteRestBoard(int idx);
RestBoard selectRestBoard(int idx);
int selectRestBoardCount();
List<RestBoard> selectRestBoardList(Map<String, Object> map);
}
DAO
public interface RestBoardDAO {
int insertRestBoard(RestBoard restBoard);
int updateRestBoard(RestBoard restBoard);
int deleteRestBoard(int idx);
RestBoard selectRestBoard(int idx);
int selectRestBoardCount();
List<RestBoard> selectRestBoardList(Map<String, Object> map);
}
@Repository
@RequiredArgsConstructor
public class RestBoardDAOImpl implements RestBoardDAO {
private final SqlSession sqlSession;
@Override
public int insertRestBoard(RestBoard restBoard) {
return sqlSession.getMapper(RestBoardMapper.class).insertRestBoard(restBoard);
}
@Override
public int updateRestBoard(RestBoard restBoard) {
return sqlSession.getMapper(RestBoardMapper.class).updateRestBoard(restBoard);
}
@Override
public int deleteRestBoard(int idx) {
return sqlSession.getMapper(RestBoardMapper.class).deleteRestBoard(idx);
}
@Override
public RestBoard selectRestBoard(int idx) {
return sqlSession.getMapper(RestBoardMapper.class).selectRestBoard(idx);
}
@Override
public int selectRestBoardCount() {
return sqlSession.getMapper(RestBoardMapper.class).selectRestBoardCount();
}
@Override
public List<RestBoard> selectRestBoardList(Map<String, Object> map) {
return sqlSession.getMapper(RestBoardMapper.class).selectRestBoardList(map);
}
}
Service
인터페이스
public interface RestBoardService {
void addRestBoard(RestBoard restBoard);
void modifyRestBoard(RestBoard restBoard);
void removeBoard(int idx);
RestBoard getRestBoard(int idx);
Map<String, Object> getRestBoardList(int pageNum);
}
서비스 클래스
@Service
@RequiredArgsConstructor
public class RestBoardServiceImpl implements RestBoardService {
private final RestBoardDAO restBoardDAO;
@Override
public void addRestBoard(RestBoard restBoard) {
restBoardDAO.insertRestBoard(restBoard);
}
@Override
public void modifyRestBoard(RestBoard restBoard) {
/*
if(restBoardDAO.selectRestBoard(restBoard.getIdx()) == null) {
throw new Exception("해당 게시글을 찾을 수 없습니다.");
}
*/
restBoardDAO.updateRestBoard(restBoard);
}
@Override
public void removeBoard(int idx) {
restBoardDAO.deleteRestBoard(idx);
}
@Override
public RestBoard getRestBoard(int idx) {
return restBoardDAO.selectRestBoard(idx);
}
@Override
public Map<String, Object> getRestBoardList(int pageNum) {
int totalBoard=restBoardDAO.selectRestBoardCount();
Pager pager=new Pager(pageNum, totalBoard, 5, 5);
Map<String, Object> pageMap=new HashMap<String, Object>();
pageMap.put("startRow", pager.getStartRow());
pageMap.put("endRow", pager.getEndRow());
List<RestBoard> restBoardList=restBoardDAO.selectRestBoardList(pageMap);
Map<String, Object> resultMap=new HashMap<String, Object>();
resultMap.put("restBoardList", restBoardList);
resultMap.put("pager", pager);
return resultMap;
}
}
컨트롤러
페이지 번호를 전달받아 RESTBOARD 테이블에 저장된 게시글 중 페이지 번호에 출력될 게시글 목록을 검색하여 JSON 형식의 문자열로 응답하는 요청 처리 메소드를 선언해보자
REST 기능을 제공하는 요청 처리 메소드는 @RequestMapping 어노테이션 대신 @GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping 어노테이션을 사용하여 요청 페이지를 요청 처리 메소드와 매핑 처리하는 것을 권장한다.
- 요청 방식 : GET(검색), POST(삽입), PUT(전체 변경), PATCH(부분 변경), DELETE(삭제) 등
RestBoardController
@RestController
@RequestMapping("/rest")
@RequiredArgsConstructor
public class RestBoardController {
private final RestBoardService restBoardService;
@GetMapping("/board_list")
public Map<String, Object> restBoardList(@RequestParam(defaultValue = "1") int pageNum) {
//자바스크립트의 Object 객체 형식의 문자열로 반환
return restBoardService.getRestBoardList(pageNum);
}
}
Restful 기능을 제공하는 요청 처리 메소드가 정상적으로 실행되는지 확인하기 위해 Restful API 테스트 프로그램인 Advanced REST Client 크롬앱을 설치하여 사용해보자.
https://www.advancedrestclient.com/


게시글을 전달받아 RESTBOARD 테이블에 게시글을 삽입 처리하고 실행결과를 문자열로 응답하는 요청 처리 메소드를 만들어보자
[application/json] 형식의 문자열로 전달된 게시글을 Java 객체로 제공받아 매개변수에 저장하기 위해 @RequestBody 어노테이션을 사용할 것이다.
HtmlUtils.htmlEscape(String str)
- 매개변수로 전달받은 문자열에 저장된 HTML 태그 관련 문자를 회피문자(Escape Character)로 변환하여 반환하는 메소드
- XSS 공격에 대한 방어를 위해 사용한다.
@PostMapping("/board_add")
public String restBoardAdd(@RequestBody RestBoard restBoard) {
//htmlEscape() 메소드로 매개변수로 전달받은 문자열에 저장된 HTML 태그 관련 문자를 회피문자로 변환하여 반환
restBoard.setContent(HtmlUtils.htmlEscape(restBoard.getContent()));
restBoardService.addRestBoard(restBoard);
return "success";
}



쿼리스트링으로 전달된 글번호를 매개변수로 제공받아 사용하는 경우를 보자
글번호를 전달받아 RESTBOARD 테이블에 저장된 게시글에서 해당 글번호의 게시글을 검색하여 JSON 형식의 문자열로 응답하는 요청 처리 메소드를 선언해보자
-> 질의 문자열로 전달된 글번호를 매개변수로 제공받아 사용할 것이다.
@GetMapping("/board_view")
public RestBoard restBoardView(@RequestParam int idx) {
return restBoardService.getRestBoard(idx);
}

이번엔 요청 URL 주소로 전달된 글번호를 매개변수로 제공받아 사용하는 메소드드를 선언해보자
요청 URL 주소로 값을 전달하기 위해서는 @GetMapping 어노테이션(@PostMapping도 가능)의 value 속성값에서 {이름} 형식으로 요청 URL 주소를 표현해야 한다.
요청 URL 주소로 값은 매개변수에 @PathVariable 어노테이션을 사용하여 전달값을 저장한다.
@PathVariable : 요청 URL 주소로 표현된 값을 요청 처리 메소드의 매개변수에 저장하기 위한 어노테이션
- @GetMapping 어노테이션의 value 속성값으로 설정된 이름과 매개변수의 이름이 같도록 작성해주어야 한다.
- 전댤값의 이름과 매개변수의 이름이 다른 경우 @PathVariable 어노테이션의 value 속성을 사용하여 요청 URL 주소로 표현하여 전달된 값을 매개변수에 저장할 수 있다.
@GetMapping("/board_view/{idx}")
public RestBoard restBoardView(@PathVariable int idx) {
return restBoardService.getRestBoard(idx);
}
/*
//전달값의 이름과 매개변수의 이름이 다른 경우
@GetMapping("/board_view/{num}")
public RestBoard restBoardView(@PathVariable("num") int idx) {
return restBoardService.getRestBoard(idx);
}
*/

게시글을 전달받아 RESTBOARD 테이블에 저장된 게시글을 변경하고 실행결과를 문자열로 응답하는 요청 처리 메소드 선언
=> [application/json] 형식의 문자열로 전달된 게시글을 Java 객체로 제공받아 매개변수에 저장하기 위해 @RequestBody 어노테이션을 사용
@PutMapping("/board_modify")
public String restBoardModify(@RequestBody RestBoard restBoard) {
restBoardService.modifyRestBoard(restBoard);
return "success";
}


글번호를 전달받아 RESTBOARD 테이블에 저장된 게시글에서 해당 글번호의 게시글을 삭제하고 실행결과를 문자열로 응답하는 요청 처리 메소드
=> 요청 URL 주소로 전달된 글번호를 매개변수로 제공받아 사용
@DeleteMapping("/board_remove/{idx}")
public String restBoardRemove(@PathVariable int idx) {
restBoardService.removeBoard(idx);
return "success";
}

board.jsp
<script type="text/javascript">
//현재 요청 페이지 번호를 저장하기 위한 전역변수
// => 모든 함수에서 사용 가능하며 프로그램 종료 전까지 값 유지
var page=1;
//게시글 목록을 제공받아 출력하는 함수 호출
boardListDisplay(page);
//매개변수로 요청 페이지 번호을 전달받아 페이지 번호에 대한 게시글 목록을 JSON 형식의
//문자열로 제공받아 HTML 태그로 변환하여 출력하는 함수
// => 게시글 목록을 JSON 형식의 문자열로 제공하는 Restful API를 비동기식으로 요청하여 응답받아 처리
function boardListDisplay(pageNum) {
page=pageNum;
$.ajax({
type: "get",
url: "<c:url value="/rest/board_list"/>",
data: {"pageNum":pageNum},
dataType: "json",
success: function(result) {
//alert(result);//[object Object] >> Map 객체가 변환되어 응답된 결과
//매개변수로 제공받은 자바스크립트의 Object 객체를 HTML 태그로 변환
if(result.restBoardList.length == 0) {//검색된 게시글이 없는 경우
var html="<table id='restBoardTable'>";
html+="<tr>";
html+="<th width='800'>검색된 게시글이 없습니다.</th>";
html+="</tr>";
html+="</table>";
$("#restBoardListDiv").html(html);
return;
}
var html="<table id='restBoardTable'>";
html+="<tr>";
html+="<th width='50'>번호</th>";
html+="<th width='100'>작성자</th>";
html+="<th width='350'>내용</th>";
html+="<th width='200'>작성일</th>";
html+="<th width='50'>변경</th>";
html+="<th width='50'>삭제</th>";
html+="</tr>";
$(result.restBoardList).each(function() {//게시글 목록(Array 객체)에 대한 반복 처리
html+="<tr>";
html+="<td align='center'>"+this.idx+"</td>";
html+="<td align='center'>"+this.writer+"</td>";
html+="<td>"+this.content+"</td>";
html+="<td align='center'>"+this.regdate+"</td>";
html+="<td align='center'><button type='button'>변경</button></td>";
html+="<td align='center'><button type='button'>삭제</button></td>";
html+="</tr>";
});
html+="</table>";
$("#restBoardListDiv").html(html);
//페이지 번호를 출력하는 함수 호출
pageNumDisplay(result.pager);
},
error: function(xhr) {
alert("에러코드(게시글 목록 검색) = "+xhr.stauts);
}
});
}
//매개변수로 페이징 처리 관련 정보(Object 객체)를 전달받아 HTML 태그로 변환하여 출력하는 함수
function pageNumDisplay(pager) {
var html="";
if(pager.startPage > pager.blockSize) {
html+="<a href='javascript:boardListDisplay("+pager.prevPage+");'>[이전]</a>";
} else {
html+="[이전]";
}
for(i = pager.startPage ; i <= pager.endPage ; i++) {
if(pager.pageNum != i) {
html+="<a href='javascript:boardListDisplay("+i+");'>["+i+"]</a>";
} else {
html+="["+i+"]";
}
}
if(pager.endPage != pager.totalPage) {
html+="<a href='javascript:boardListDisplay("+pager.nextPage+");'>[다음]</a>";
} else {
html+="[다음]";
}
$("#pageNumDiv").html(html);
}
//[글쓰기] 태그를 클릭한 경우 호출되는 이벤트 처리 함수 등록
$("#writeBtn").click(function() {
//변경 게시글을 입력받기 위한 태그 초기화
$(".update").val("");//입력태그 초기화
$("#updateDiv").hide();//태그 숨김
//신규 게시글을 입력받기 위한 태그 출력
$("#insertDiv").show();
});
//신규 게시글을 입력받기 위핸 태그에서 [저장] 태그를 클릭한 경우 호출되는 이벤트 처리 함수 등록
// => 사용자 입력값을 반환받아 RESTBOARD 테이블에 삽입 처리하는 Restful API를 비동기식을 요청하여
//실행결과를 제공받아 출력 처리
$("#insertBtn").click(function() {
var writer=$("#insertWriter").val();
var content=$("#insertContent").val();
if(writer == "") {
alert("작성자를 입력해 주세요.");
return;
}
if(content == "") {
alert("내용을 입력해 주세요.");
return;
}
$.ajax({
type: "post",
url: "<c:url value="/rest/board_add"/>",
//headers : 요청정보가 저장된 리퀘스트 메세지의 머릿부(Header)를 변경하기 위한 속성
// => 리퀘스트 메세지 몸체부에 저장되어 전달될 값의 파일형식(MimeType)을 변경
//headers: {"contentType":"application/json"},
//contentType : 리퀘스트 메세지 몸체부에 저장되어 전달될 값의 파일형식(MimeType)을 변경하기 위한 속성
// => 리퀘스트 메소드 몸체부에 JSON 형식의 문자열로 값 전달
// => 요청 처리 메소드의 매개변수에서 @RequestBody 어노테이션을 사용하여 JSON 형식의
//문자열을 Java 객체로 전달받아 사용 - 값을 Java 객체의 필드값으로 저장되어 제공
contentType: "application/json",
//JSON.stringify(object) : 자바스트립트 객체를 JSON 형식의 문자값으로 변환하여 반환하는 메소드
data: JSON.stringify({"writer":writer, "content":content}),
dateType: "text",
success: function(result) {
if(result == "success") {
//신규 게시글을 입력받기 위한 태그 초기화
$(".insert").val("");//입력태그 초기화
$("#insertDiv").hide();//태그 숨김
//게시글 목록을 제공받아 출력하는 함수 호출
boardListDisplay(page);
}
},
error: function(xhr) {
alert("에러코드(게시글 삽입) = "+xhr.stauts);
}
});
});
</script>
0818 변경/삭제
'학원 > 복기' 카테고리의 다른 글
[Spring] Spring Security (0) | 2023.09.06 |
---|---|
[Spring] 스프링 검증(Spring Validation) (0) | 2023.09.04 |
[Spring] RESTful API (0) | 2023.08.19 |
[Spring] 자료실 만들기 (0) | 2023.08.15 |
[Srping] commons fileupload 라이브러리 이용한 파일 처리 (0) | 2023.08.15 |