[SpringBoot] 게시판 구현하기 13 (파일 삭제하기) - 책 응용편
FRAMEWORK/Spring

[SpringBoot] 게시판 구현하기 13 (파일 삭제하기) - 책 응용편

반응형

들어가기 전에

하기 포스팅은 "스프링부트 시작하기(김인우 저)"를 공부하며 만든 springboot 프로젝트 내에 추가로 구현한 부분에 대해 적은 글입니다. 잘못된 부분이 있을 수 있으니 참고만 해주시면 좋을 것 같습니다. 

 

"스프링부트 시작하기" 책에는 파일 업로드 및 다운로드만 존재하여, 기존에 업로드한 파일을 삭제하는 것에 대해 알아볼 것입니다.

뷰 변경하기

boardDetail.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>
		<form id="frm" method="post">
	    	<table class="board_detail">
				<colgroup>
					<col width="15%"/>
					<col width="35%"/>
					<col width="15%"/>
					<col width="35%"/>
				</colgroup>
				<caption>게시글 상세내용</caption>
				<tbody>
					<tr>
						<th scope="row">글 번호</th>
						<td th:text="${board.boardIdx}"></td>
						<th scope="row">조회수</th>
						<td th:text="${board.hitCnt}"></td>
					</tr>
					<tr>
						<th scope="row">작성자</th>
						<td th:text="${board.creatorId}"></td>
						<th scope="row">작성일</th>
						<td th:text="${#temporals.format(board.createdDatetime, 'yyyy-MM-dd HH:mm:ss')}"></td>
					</tr>
					<tr>
						<th scope="row">제목</th>
						<td colspan="3">
							<input type="text" id="title" name="title" 
								th:value="${board.title}"/>
						</td>
					</tr>
					<tr>
						<td colspan="4" class="view_text">
							<textarea title="내용" id="contents" name="contents" 
								th:text="${board.contents}"></textarea>
						</td>
					</tr>
				</tbody>
			</table>
			<input type="hidden" name="boardIdx" th:value="${board.boardIdx}">
		</form>
		
		<!-- modify start -->
		<div class="file_list" th:each="list : ${board.fileList}">
			<a style="float:left" th:href="@{/board/downloadBoardFile.do(idx=${list.idx}, boardIdx=${list.boardIdx})}" th:text="|${list.originalFileName}(${list.fileSize} kb)|"></a>
			<input type="button" id="delete_file" th:value="파일삭제" th:onclick="|location.href='@{/board/deleteBoardFile.do(idx=${list.idx}, boardIdx=${list.boardIdx})}'|">
		</div>
		<!-- modify end -->
        
		<input type="button" id="list" value="목록으로">
		<input type="button" id="edit" value="수정하기">
		<input type="button" id="delete" value="삭제하기">
		
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
		<script type="text/javascript">
			$(document).ready(function(){
				$("#list").on("click", function(){
					location.href = "openBoardList.do";
				});
				
				$("#edit").on("click", function(){
					var frm = $("#frm")[0];
					frm.action = "updateBoard.do";
					frm.submit();
				});
				
				$("#delete").on("click", function(){
					var frm = $("#frm")[0];
					frm.action = "deleteBoard.do";
					frm.submit();
				});
			})
		</script>
	</div>
</body>
</html>
  • <div class="file_list" th:each="list : ${board.fileList}">: 기존에는 a 태그에서 th:each를 설정하였으나, 파일 삭제 버튼에서도 해당 속성을 사용할 수 있도록 div에 설정하는 것으로 변경했습니다.
  • <a style="float:left"...: a 태그에 float 값을 설정하였습니다. a 태그에 float:left를 설정함에 따라 파일 삭제 버튼을 각 파일 옆에 생성할 수 있습니다.

a 태그에 float 추가하지 않은 경우(왼쪽), a 태그에 float 추가한 경우(오른쪽)

  • <input type="button" id="delete_file" th:value="파일삭제" th:onclick="'location.href=\''+@{/board/deleteBoardFile.do(idx=${list.idx}, boardIdx=${list.boardIdx})}+'\''">
    • th:onclick에서 location.href=를 '(single quote)로 묶고 +로 concate한 이유는 location.href 구문을 파싱해서 원하는 주소로 변환을 하기 위함입니다. 
    • '(single quote)로 묶는 대신, th:onclick 값 전체를 |(pipe)로 묶는 것 역시 가능합니다.
  • th:onclick="|location.href='@{/board/deleteBoardFile.do(idx=${list.idx}, boardIdx=${list.boardIdx})}'|"
    • |(pipe)로 구문을 감싼 이유는 문자열 연산을 하기 위해서입니다. 문자열 연산은 |(pipe)로도 가능하지만, +와 '(single quote)를 이용해서도 가능합니다.
    • 예를 들어, 위 구문은 아래와 같이 나타낼 수 있습니다.
      • th:onclick="'location.href=\''+@{/board/deleteBoardFile.do(idx=${list.idx}, boardIdx=${list.boardIdx})}+'\''

'로 묶은 경우(왼쪽), '로 묶지 않은 경우(오른쪽)

컨트롤러 영역

...
		@RequestMapping("board/deleteBoardFile.do")
		public String deleteBoardFile(@RequestParam int idx, @RequestParam int boardIdx) throws Exception {
			boardService.deleteBoardFile(idx, boardIdx);
			
			return "redirect:/board/openBoardDetail.do?boardIdx="+boardIdx;
		}
...
  • 게시글 삭제했을 때와 다르게 파일 삭제시 해당 게시글을 다시 한 번 호출하도록 했습니다.
  • 파일 다운로드할 때와 같이 @RequestParam 값을 사용하여 데이터베이스 속 해당 파일에 접근하도록 했습니다.

서비스 및 매퍼 영역

// BoardService.java
...
	void deleteBoardFile(int idx, int boardIdx) throws Exception;
...

// BoardServiceImpl.java
...
	@Override
	public void deleteBoardFile(int idx, int boardIdx) throws Exception {
		boardMapper.deleteBoardFile(idx, boardIdx);
	}
...
  • selectFileInformation 메소드와 비슷하게 작성해주었습니다. SQL 실행 시 필요할 게시글 번호와 파일 번호를 넘겨 쿼리 돌리는 데 사용할 수 있도록 했습니다.
// BoardMapper.java
...
	void deleteBoardFile(@Param("idx") int idx, @Param("boardIdx") int boardIdx);
...
  • selectFileInformation 메소드와 비슷하게 작성하였습니다. 이 메소드 역시 뷰에서 넘겨준 parameter를 사용할 수 있도록 @Param 어노테이션을 이용했습니다.

SQL 작성하기

...
	<update id="deleteBoardFile" parameterType="map">
		<![CDATA[
			UPDATE
				t_file
			SET
				deleted_yn = 'Y',
				updated_datetime = NOW(),
				updater_id = 'admin'
			WHERE
				idx = #{idx}
				AND board_idx = #{boardIdx}
		]]>
	</update>
...
  • deleteBoardFile SQL은 게시글 수정 쿼리와 유사합니다. 다만, parameterType에서 필요한 값이 2가지 뿐이라 따로 DTO를 생성하지 않고 map을 사용했습니다(selectBoardFileInformation 역시 map을 사용했습니다).

파일 삭제 결과 확인하기

파란색글리터.png 삭제해보기
파란색글리터.png의 deleted_yn 값이 Y가 되고, 수정자가 생긴 것을 확인할 수 있음

마치며

다음 응용편에서는 게시글 내용 뿐 아니라 파일 역시 수정할 수 있도록 코드를 변경해볼 예정입니다. 현재까지는 게시글 생성할 때만 파일을 추가할 수 있지만, 해당 과정을 진행하면 /board/openBoardDetail.do에서 신규 파일 추가 역시 가능할 것입니다. 

반응형