2026. 4. 23. 19:51ㆍ대우개발원 수업 내용/spring boot, framework
첨부파일 처리를 포함한 게시판 CRUD(목록, 조회, 수정) 기능 완성 및 고도화
1. BoardController 및 목록 페이지(list.html)의 통합 데이터 연동
데이터 소스 변경: BoardController의 list 메서드가 댓글 수와 이미지 정보를 모두 포함하는 listWithAll 서비스를 호출하도록 수정하여 BoardListAllDTO를 반환하게 함
목록 내 썸네일 구현: list.html에서 th:if와 th:each를 활용하여 첨부 이미지가 있는 경우에만 100px 크기의 섬네일을 게시글 제목 하단에 동적으로 표시함
사용자 경험 향상: 사용자가 목록 화면에서부터 이미지 유무를 즉시 확인할 수 있도록 UI를 개선하고, 기존 제목과 댓글 수 표시 로직을 유지함
2. 게시글 상세 조회 페이지(read.html)의 이미지 시각화
이미지 갤러리 영역 추가: read.html의 카드 바디 하단에 fileNames 리스트를 순회하는 로직을 추가하여, 게시글에 등록된 모든 원본 이미지를 본문에 노출함
조건부 렌더링: th:if를 사용하여 첨부파일이 존재하는 경우에만 이미지 카드를 생성하도록 설정하여 화면의 불필요한 공백을 방지함
3. 수정 페이지(modify.html)의 기존 첨부파일 관리 UI 구축
파일명 파싱 및 출력: 타임리프의 th:with와 split('_')을 사용하여 서버에서 넘어온 파일명에서 UUID를 제외한 실제 파일 이름만 화면에 깔끔하게 표시함
실시간 관리 인터페이스: 각 이미지 카드에 삭제('X') 버튼을 배치하고, 이를 클릭 시 화면에서 즉시 제거함과 동시에 서버 삭제를 위한 데이터를 준비함
경로 최적화: 수정 화면 로딩 속도를 높이기 위해 /view/s_ 경로의 섬네일 이미지를 먼저 보여주고, data-src 속성에 원본 파일 정보를 보관함
4. JavaScript를 활용한 수정 시 파일 삭제 및 추가 로직 (modify.html)
삭제 대상 추적: removeFileList 배열을 생성하여 사용자가 'X' 버튼을 누른 파일의 정보를 수집하고, 최종 수정 버튼 클릭 시 서버에서 실제 파일이 삭제되도록 처리함
비동기 업로드 통합: register.html에서 구현했던 파일 업로드 모달과 uploadToServer 함수를 재사용하여, 수정 중에도 새로운 이미지를 즉시 추가하고 화면에 미리보기를 띄움
DOM 탐색 및 제어: closest(".card")를 사용하여 이벤트가 발생한 카드를 정확히 찾아 화면에서 제거하는 유연한 인터랙션을 구현함
5. 수정 폼 데이터 통합 및 최종 제출 프로세스 완성
데이터 동기화(appendFileData): 수정 완료 버튼 클릭 시, 현재 화면(uploadResult)에 남아 있는 이미지들의 정보를 추출하여 폼 내부에 숨겨진 <input type='hidden'> 요소로 동적 생성함
서버 통신 및 폼 제출: callRemoveFiles 함수를 통해 삭제 대상을 서버 저장소에서 먼저 지우고, 가공된 첨부파일 리스트(fileNames)를 포함한 폼을 POST 방식으로 제출함
CRUD 사이클 완성: 텍스트 정보 수정과 파일의 추가/삭제가 하나의 트랜잭션처럼 동작하게 하여, DB 테이블(board, board_image)과 실제 저장소 파일이 항상 일치하도록 구현함
[요약]
첨부파일 처리를 포함한 게시판 CRUD(목록, 조회, 수정) 기능 완성 및 고도화
1. BoardController의 목록 조회 메서드 고도화 (listWithAll 적용 및 DTO 교체)
2. 목록 및 조회 화면의 첨부 이미지 조건부 렌더링 및 시각화 구현
3. 수정 페이지 내 기존 첨부파일 파싱 출력 및 삭제 버튼 UI 구축
4. JavaScript를 이용한 삭제 대상 파일 추적 및 비동기 서버 통신 로직 연동
5. 수정 버튼 클릭 시 첨부파일 정보를 포함한 최종 폼 데이터 통합 전송 로직 완성


둘다 안에 있는 것을 삭제
BoardController.java
에서 list 메소드 부분을 확인 하고
BoardServiceImpl 에서 첨부파일까지 같이 처리하는 메소드인 listWithAll를 보고 responseDTO를 만들 수 있게
BoardController의 list 메소드 부분 수정
// PageResponseDTO<BoardListReplyCountDTO> responseDTO =
// boardService.listWithReplyCount(pageRequestDTO);
PageResponseDTO<BoardListAllDTO> responseDTO =
boardService.listWithAll(pageRequestDTO);
list.html
에서 title구간에

a태그와 span태그 부분을 주석처리 하고 아래 코드로 변경
<td>
<!-- [[${dto.title}]]-->
<!-- <a th:href="|@{/board/read(bno =${dto.bno})}&${link}|"> [[${dto.title}]] </a>-->
<!-- <span class="badge progress-bar-success" style="background-color: #0a53be">[[${dto.replyCount}]]</span>-->
<a th:href="|@{/board/read(bno =${dto.bno})}&${link}|" class="text-decoration-none"> [[${dto.title}]] </a>
<span class="badge progress-bar-success" style="background-color: #0a53be">[[${dto.replyCount}]]</span>
<div th:if="${dto.boardImages != null && dto.boardImages.size() > 0}">
<img style="width:100px" th:each="boarImage: ${dto.boardImages}"
th:src="|/view/s_${boarImage.uuid}_${boarImage.fileName}|">
</div>
실행하면
list에서 섬네일 파일처럼 사진이 표시되는 모습을 볼 수 있다.
list 페이지에서 게시글을 눌렀을때 현재는
사진이 보이지 않는다
이미 BoardDTO에서 첨부파일을 받아오게 되어 있기 때문에
read.html
만 수정하면 사진이 보인다.
<!--end card body--> 아래에
코드를 추가한다
<div class="col">
<div class="card" th:if="${dto.fileNames != null && !dto.fileNames.isEmpty()}">
<img class="card-img-top" style="width: 200px"
th:each="fileName : ${dto.fileNames}"
th:src="|/view/${fileName}|"
alt="첨부 이미지">
</div>
</div><!-- end file name -->
</div><!--end card-->
실행하면
사진이 정상적으로 read페이지에 뜬다
modify.html을 수정
게시글의 텍스트 정보만 수정하던 기존 코드에서
첨부 파일(이미지) 관리 및 비동기 처리 기능을 수정
- 이미지 미리보기 및 관리 영역 신설: 게시글 수정 폼 하단에 uploadResult 클래스를 가진 영역이 추가되어,
기존에 업로드된 파일들을 섬네일 형태로 확인하고 삭제 버튼('X')을 통해 실시간으로 관리할 수 있는 UI가 구축됨. - 파일 업로드 전용 모달창 도입: 사용자가 수정을 진행하며 추가 파일을 선택할 수 있도록 uploadModal이
새롭게 배치되었으며, multiple 속성을 가진 파일 입력 태그를 통해 여러 개의 이미지를 동시에 처리할 수 있게 됨. - 외부 라이브러리 및 전용 스크립트 로드: 비동기 HTTP 통신을 위한 Axios 라이브러리와 파일 업로드/삭제 로직이 담긴 /js/upload.js 파일을 추가로 불러와, 화면 전체를 새로고침하지 않고도 서버와 데이터를 주고받는 기능이 준비됨.
- 타임리프 기반 파일명 정밀 가공: th:with 문법을 사용하여 UUID와 원본 파일명을 분리하는 파싱 로직이 적용되었으며, 이를 통해 화면에는 깨끗한 파일명만 노출하고 삭제 시에는 정확한 UUID를 전달하는 구조를 완성함.
- 데이터 속성 및 경로 최적화: 이미지 태그에 /view/s_ 형태의 섬네일 경로를 적용하여 페이지 로딩 속도를 최적화하고, data-src 속성에 원본 파일명을 저장하여 자바스크립트에서 쉽게 데이터에 접근할 수 있도록 설계됨.
<!—- 첨부 파일 섬네일을 보여줄 부분 -->
<div class="row mt-3">
<div class="col ">
<div class="container-fluid d-flex uploadResult" style="flex-wrap: wrap;">
<th:block th:each="fileName:${dto.fileNames}">
<div class="card col-4" th:with = "arr = ${fileName.split('_')}">
<div class="card-header d-flex justify-content-center">
[[${arr[1]}]]
<button class="btn-sm btn-danger"
th:onclick="removeFile([[${arr[0]}]], [[${arr[1]}]], this)">X</button>
</div>
<div class="card-body">
<img th:src="|/view/s_${fileName}|" th:data-src="${fileName}" >
</div>
</div><!-- card -->
</th:block>
</div>
</div>
</div>
<!—- 첨부파일 추가를 위한 모달창 -->
<div class="modal uploadModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Upload File</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="input-group mb-3">
<input type="file" name="files" class="form-control"
multiple >
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary uploadBtn">Upload</button>
<button type="button" class="btn btn-outline-dark closeUploadBtn" >Close</button>
</div>
</div>
</div>
</div><!-- register modal -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="/js/upload.js"></script>
실행하면
modify버튼을 누르면 사진이 보임
이제 x버튼을 누르면 사진을 삭제할 수 있게 코드를 수정
modify.html
// 최종적으로 삭제될 파일들의 목록 (삭제할 파일 정보를 저장)
const removeFileList = [];
function removeFile(uuid, fileName, obj) {
// 삭제 여부 확인
if( !confirm("파일을 삭제하시겠습니다?")) {
return; //취소 버튼을 누르면 아무 동작도 하지 않음
}
// 삭제할 파일의 UUID와 파일명 출력
console.log(uuid);
console.log(fileName);
console.log(obj);
// 삭제할 파일 정보를 removeFileList에 추가
removeFileList.push({uuid, fileName});
// 삭제할 파일이 속한 카드 요소 찾기
const targetDiv = obj.closest(".card");
// 해당카드 요소 제거(파일 삭제 UI 처리)
targetDiv.remove();
}
modify.html에
첨부파일을 추가할 수 있는 버튼을 추가한다
<!-- // 첨부파일 추가 버튼-->
<div class="input-group mb-3">
<span class="input-group-text">Images</span>
<div class="float-end uploadHidden">
<button type="button" class="btn btn-primary uploadFileBtn">ADD Files</button>
</div>
</div>
두개를 실행하면
register.html에서 아래부분을
modify.html에 추가
//업로드 모달
const uploadModal = new bootstrap.Modal(document.querySelector(".uploadModal"))
document.querySelector(".uploadFileBtn").addEventListener("click", function(e){
e.stopPropagation()
e.preventDefault()
uploadModal.show()
}, false)
// 업로드 버튼 클릭 시 이벤트 처리
document.querySelector(".uploadBtn").addEventListener("click", function(e) {
// FormData 객체 생성 (파일 업로드를 위한 객체)
const formObj = new FormData();
// 파일 입력 요소 가져오기
const fileInput = document.querySelector("input[name='files']");
console.log(fileInput.files); // 선택된 파일 목록 출력
// 파일 리스트 가져오기
const files = fileInput.files;
// 선택된 파일들을 FormData에 추가
for (let i = 0; i < files.length; i++) {
formObj.append("files", files[i]);
}
// 서버에 파일 업로드 요청
uploadToServer(formObj).then(result => {
console.log(result); // 업로드 결과 출력
// 업로드된 파일 정보를 화면에 표시
for (const uploadResult of result) {
showUploadFile(uploadResult);
}
// 업로드 모달 닫기
uploadModal.hide();
}).catch(e => {
// 업로드 실패 시 모달 닫기
uploadModal.hide();
});
}, false);
// 업로드된 파일을 표시할 요소 가져오기
const uploadResult = document.querySelector(".uploadResult");
// 업로드된 파일 정보를 화면에 표시하는 함수
function showUploadFile({ uuid, fileName, link }) {
// 업로드된 파일을 표시할 HTML 문자열 생성
const str = `<div class="card col-4">
<div class="card-header d-flex justify-content-center">
${fileName} <!-- 파일명 표시 -->
<button class="btn-sm btn-danger"
onclick="javascript:removeFile('${uuid}', '${fileName}', this)" >X</button>
<!-- 파일 삭제 버튼 (클릭 시 removeFile 함수 호출) -->
</div>
<div class="card-body">
<img src="/view/${link}" data-src="${uuid + "_" + fileName}" >
<!-- 업로드된 이미지 표시 -->
</div>
</div><!-- card -->`;
// 업로드 결과 영역에 추가하여 화면에 표시
uploadResult.innerHTML += str;
}
실행하면
아직 x를 누르거나 파일을 업로드를 했을때 modfiy창에서는 사라지거나 추가된것을 볼 수 있다
아직 modify버튼을 처리하지 않았기 때문에 누르면 list창에 반영되지 않는다.
버튼을 눌렀을때 같이 처리될 수 있도록 코드를 수정해야한다
modify.html
코드 아래에 추가
//수정 버튼 클릭 시 이벤트 처리
document.querySelector(".modBtn").addEventListener("click",function(e) {
//기본 동작을 막고 , 이벤ㄴ트 전파를 중지
e.preventDefault();
e.stopPropagation();
//폼의 action을 수정 페이지로 설정 ( 링크 파라미터 수정)
formObj.action = `/board/modify?${link}`;
// 첨부 파일 데이터를 숨겨진 입력 요소로 추가
appendFileData();
//삭제 대상 파일들에 대하 삭제 요청
callRemoveFiles();
//폼의 method를 POSt로 설정 (수정 작업)
formObj.method = 'post';
//폼 제출
formObj.submit();
} , false);
function appendFileData() {
const target = document.querySelector(".uploadHidden")
const uploadFiles = uploadResult.querySelectorAll("img")
let str= ''
for (let i = 0; i< uploadFiles.length; i++) {
const uploadFile = uploadFiles[i]
const imgLink = uploadFile.getAttribute("data-src")
str += `<input type='hidden' name='fileNames' value="${imgLink}">`
}
target.innerHTML = str;
}
function callRemoveFiles() {
removeFileList.forEach( ({uuid , fileName}) => {
removeFileToServer({uuid, fileName})
})
}
실행하면
modify버튼을 누르면 정상적으로 실행되고
list페이지에서도 보이게 된다
'대우개발원 수업 내용 > spring boot, framework' 카테고리의 다른 글
| 자바 스프링 부트 14일차 /b01Security 회원가입(Security) 핵심 설정 및 커스텀 인증 시스템 구축 (0) | 2026.04.27 |
|---|---|
| 자바 스프링 부트 13일차 b01Upload (1) | 2026.04.24 |
| 자바 스프링 부트 12일차 b01Upload /첨부파일 비동기 업로드 구현 및 파일 정보를 포함한 게시글 등록 처리 (0) | 2026.04.22 |
| 자바 스프링 부트 11일차 b01Upload /첨부파일 이미지와 댓글 조회 및 처리 (0) | 2026.04.21 |
| 자바 스프링 부트 10일차 b01Upload (2) | 2026.04.20 |










