2026. 4. 22. 20:05ㆍ대우개발원 수업 내용/spring boot, framework
첨부파일 비동기 업로드 구현 및 파일 정보를 포함한 게시글 등록 처리
1. 부트스트랩 기반의 파일 업로드 UI 및 모달 구성 (register.html)
파일 추가 인터페이스: 사용자가 직관적으로 인지할 수 있도록 'ADD Files' 버튼을 배치하고,
클릭 시 파일 선택이 가능한 부트스트랩 모달창이 나타나도록 설계함
미리보기 영역(uploadResult): 업로드가 완료된 파일의 섬네일과 파일명을
카드(Card) 형태로 동적으로 출력하여 사용자가 업로드 상태를 즉시 확인할 수 있게 함
파일 선택 제한: 모달 내의 <input type="file">에 multiple 속성을 부여하여 한 번에 여러 개의 이미지를 선택하고 업로드할 수 있는 환경을 제공함
2. Axios를 활용한 비동기 파일 업로드 및 삭제 로직 (upload.js)
uploadToServer: FormData 객체에 담긴 파일 데이터를 multipart/form-data 형식으로
서버의 /upload 엔드포인트에 비동기 전송하고 결과를 반환받음
removeFileToServer: 섬네일의 삭제(X) 버튼 클릭 시, 해당 파일의 UUID와 이름을
파라미터로 전달하여 서버 저장소에서 실제 파일을 즉시 제거함
사용자 경험(UX) 개선: 페이지 새로고침 없이 파일의 추가와 삭제가 이루어지도록 구현하여 빠르고 부드러운 인터랙션을 제공함
3. 업로드 결과의 동적 렌더링 및 미리보기 구현 (register.html)
showUpFiles 함수: 서버로부터 전달받은 link(섬네일 경로)와 uuid, fileName 데이터를 활용하여
HTML 문자열을 생성하고 화면에 카드를 동적으로 추가함
데이터 보관(data-src): 나중에 게시글 저장 시 서버로 전송할 파일 식별자(UUID_파일명)를
<img> 태그의 속성에 미리 저장하여 데이터 추출이 용이하게 함
DOM 제어: closest(".card")를 사용하여 특정 삭제 버튼이 속한 카드 요소만 정확히 찾아내어
화면에서 제거하는 효율적인 DOM 관리 로직을 적용함
4. 폼 제출 시 첨부파일 데이터의 히든 필드 전환 처리 (register.html)
이벤트 제어: submitBtn 클릭 시 기본 제출 동작을 중단(preventDefault)하고,
현재 화면에 떠 있는 섬네일 리스트에서 파일 정보들을 먼저 수집함
input type='hidden': 수집된 파일 식별자들을 fileNames라는 이름의 숨겨진 입력 요소로 생성하여 폼(form) 내부에 동적으로 삽입
데이터 무결성: 단순한 텍스트 정보(제목, 내용)와 비동기로 미리 업로드된 파일의 경로 정보를 하나로 묶어 최종적으로 서버의 BoardDTO로 전송함
5. 데이터베이스 저장 및 최종 등록 결과 검증
멀티 테이블 저장: 서버 계층에서 전달받은 DTO를 엔티티로 변환하여 board 테이블과 board_image 테이블에 연관 관계를 유지하며 데이터를 동시 저장함
등록 완료 알림: 저장이 성공하면 리스트 페이지로 리다이렉트되며, 생성된 게시글 번호를 모달창으로 띄워 사용자에게 작업 성공을 명확히 고지함
최종 확인: DB 조회를 통해 게시글 테이블의 텍스트 데이터와 이미지 테이블의 파일 정보가 외래키(bno)를 기반으로 정확히 일치함을 확인함
[요약]
첨부파일 비동기 업로드 구현 및 파일 정보를 포함한 게시글 등록 처리
1. 부트스트랩 기반의 파일 업로드 UI 및 모달 구성 (register.html)
2. Axios를 활용한 비동기 파일 업로드 및 삭제 로직 (upload.js)
3. 업로드 결과의 동적 렌더링 및 미리보기 구현 (register.html)
4. 폼 제출 시 첨부파일 데이터의 히든 필드 전환 처리 (register.html)
5. 데이터베이스 저장 및 최종 등록 결과 검증
CRUD 등록화면 부터 확인
register.html
부트스트랩(Bootstrap) 클래스를 활용하여 사용자가 게시물에 이미지를 추가할 수 있도록 설계된 파일 업로드 버튼 영역의 UI 구조
<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>
upload.js 코드 추가
Axios 라이브러리를 활용하여 브라우저에서 서버로 파일을 업로드하거나 저장된 파일을 삭제하는 비동기 통신 로직
// 서버에 파일을 업로드 하는 비동기 함수
async function uploadToServer(fromObj) {
console.log("upload To Server...."); // 업로드 시작 로그 출력
console.log(fromObj); // 업로드할 폼 데이터 출력
// Axios를 이용하여 서버에 파일 업로드 요청
const response = await axios({
method: 'POST', // POST 요청
url: '/upload', // 업로드 API 엔드포인트
data: fromObj, //업로드할 데이터(FormData 객체)
headers: {
'Content-Type': 'multipart/form-data', // 파일 업로드를 위한 헤더 설정
},
});
return response.data; //서버 응답 데이터 반환
}
async function removeFileToServer(uuid, fileName) {
//Axios를 이용하여 파일 삭제 요청 (DELETE 메서드 사용)
const response = await axios.delete(`/remove/${uuid}_${fileName}`);
return response.data; // 서버 응답 데이터 반환
}
첨부파일 추가를 위한 모달창에 대한 코드, 첨부파일 섬네일을 보여줄 부분까지 추가
register.html 코드에 추가
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/basic.html}">
<head>
<title>Board Register</title>
</head>
<div layout:fragment="content">
<div class="row mt-3">
<div class="col">
<div class="card">
<div class="card-header">
Board Register
</div>
<div class="card-body">
<form action="/board/register" method="post">
<div class="input-group mb-3">
<span class="input-group-text">Title</span>
<input type="text" name="title" class="form-control" placeholder="Title">
</div>
<div class="input-group mb-3">
<span class="input-group-text">Content</span>
<textarea class="form-control col-sm-5" rows="5" name="content"></textarea>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Writer</span>
<input type="text" name="writer" class="form-control" placeholder="Writer">
</div>
<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>
<div class="my-4">
<div class="float-end">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="reset" class="btn btn-secondary">Reset</button>
</div>
</div>
</form>
</div><!--end card body-->
</div><!--end card-->
</div><!-- end col-->
</div><!-- end row-->
<!—- 첨부파일 섬네일을 보여줄 부분 -->
<div class="row mt-3">
<div class="col ">
<div class="container-fluid d-flex uploadResult" style="flex-wrap: wrap;">
</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>
</div>
<script layout:fragment="script" th:inline="javascript">
const errors = [[${errors}]]
console.log(errors)
let errorMsg = ''
if(errors){
for (let i = 0; i < errors.length; i++) {
errorMsg += `${errors[i].field}은(는) ${errors[i].code} \n`
}
alert(errorMsg)
}
//업로드 모달
const uploadModal = new bootstrap.Modal(document.querySelector(".uploadModal"))
document.querySelector(".uploadFileBtn").addEventListener("click", function(e){
e.stopPropagation()
e.preventDefault()
uploadModal.show()
}, false)
</script>
실행하면
업로드 버튼이 생기고 모달창이 생김
register.html
브라우저에서 사용자가 선택한 파일을 서버로 전송하고,
성공 시 화면에 미리보기 카드를 동적으로 생성하거나 삭제하는 클라이언트 측 인터랙션 로직
// 업로드 버튼 클릭 시 이벤트 처리
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 showUpFiles({ 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>
</div>
<div class="card-body">
<img src="/view/${link}" data-src="${uuid}_${fileName}">
<!-- 업로드 된 이미지 표시 -->
</div>
</div><!-- card -->`;
// 업로드 결과 영역에 추가하여 화면에 표시
uploadResult.innerHTML += str;
}
// 파일 삭제 처리 함수
function removeFile(uuid, fileName, obj) {
console.log(uuid);
console.log(fileName);
// 삭제 버튼이 속한 카드 요소 찾기. 현재 요소부터 위로 올라가면서 .card 클래스를 가진 부모 요소를 찾음
const targetDiv = obj.closest(".card") //obj : 이벤트가 발생한 요소 ("X" 버튼)
// 서버에 파일 삭제 요청 (axios 사용)
removeFileToServer(uuid, fileName).then(data => {
// 삭제 성공 시 화면에서 해당 카드 제거
targetDiv.remove()
});
}
실행하면
파일 업로드 해서 로그가 나오고 섬네일도 뜬다.
파일 삭제를 했을때도 로그와 함께 파일이 정상적으로 삭제된다.
register.html
submiyBtn
추가

그리고 아래에 코드 추가
// 제출 버튼 클릭 시 이벤트 처리
document.querySelector(".submitBtn").addEventListener("click", function(e){
// 기본 동작을 막고, 이벤트 전파를 중지
e.preventDefault();
e.stopPropagation()
// 숨겨진 업로드 파일 입력 요소 가져오기
const target = document.querySelector(".uploadHidden");
// 업로드 된 이미지 파일들 가져오기 (업로드 겨로가 영역에서 <img>요소들)
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"); // 이미지의 data-src 속성 값 가져오기
}
// 생성된 숨겨진 입력 요소 문자열에 추가
target.innerHTML = str;
// 폼 제출
document.querySelector("form").submit();
}, false);
실행하면
게시글이 등록이 되고 등록이 되었다는 모달 창이 뜨고 게시물이 list페이지에 추가된다
중간에
// 생성된 숨겨진 입력 요소 문자열에 추가
str += `<input type='hidden' name='fileNames' value="${imgLink}">`;
를 추가하면
이제
등록했을때 사진과 글 모두 테이블에 저장되는것을 확인
'대우개발원 수업 내용 > spring boot, framework' 카테고리의 다른 글
| 자바 스프링 부트 13일차 b01Upload (1) | 2026.04.24 |
|---|---|
| 자바 스프링 부트 13일차 b01Upload (1) | 2026.04.23 |
| 자바 스프링 부트 11일차 b01Upload /첨부파일 이미지와 댓글 조회 및 처리 (0) | 2026.04.21 |
| 자바 스프링 부트 10일차 b01Upload (2) | 2026.04.20 |
| 자바 스프링 부트 9일차b01Upload 파일 업로드 처리 (0) | 2026.04.16 |








