코딩항해기
[실습/Spring] 이미지 수정하기 (MultipartFile) 본문
기존 MultipartFile를 연습할 때 기능 구현은 거의 마쳤기 때문에 해당 실습에서는 흐름을 정리하고 유효성 검사 추가와 자잘한 에러를 수정하는 위주로 진행했다.
문제
먼저 3가지의 문제가 있었다.
1. 수정할 사진을 넣지 않고 사진 업로드를 누르면 500번 대 에러가 발생하는 문제
2. 작성자가 아닌 글의 사진도 수정할 수 있는 문제
3. 사진이 없는 글을 들어가면 500번 대 에러가 발생하는 문제
해결방안 고안
각각의 문제를 해결할 방법을 먼저 고민했다.
1번의 경우 view에서 입력값 검증을 거쳐 해결하고자 했다. 다양한 단계에서 처리가 가능하지만 view에서 처리하면서 aerlt창을 띄워 사용자에게 안내하면 사용자 입장에서 더욱 좋을 것 같았기 때문이다.
2번의 경우에도 view에서 처리하고자 했다. 물론 악의적인 접근을 막기 위해 다른 단계에서도 추가적인 검증로직이 필요로 하겠지만 악의적인 접근은 고려하지 않기로 했다. view를 통해 이미지 업로드 버튼을 숨기면 해당 부분도 해결된다.
3번의 경우가 조금 문제였는데, 사진 없는 글에만 들어가면 500번대 에러가 발생했다. 해당 에러는 EmptyResultDataAccessException 예외 문제였는데, JDBCTemplate를 사용해 존재하지 않는 데이터에 접근하면 발생하는 예외이다. 기존 JDBC null 처리에 익숙해서 놓쳤던 부분이었다. 해당 부분은 여러 처리로 해결할 수 있지만, try-catch를 통해 해당 예외 발생 시 null을 반환할 수 있도록 해결하기로 했다.
코드 반영
해결 방법을 정했으므로 이제 코드에 반영한다. View에서 Controller, Model 순서로 넘어가며 흐름을 훑고 해결방안을 적용할 예정이다.
View
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>${data.bid}</title>
<script src="js/preview.js"></script>
</head>
<body>
내용 : ${data.content} <br>
작성자 아이디 : ${data.writer} <br>
작성자 이름 : ${data.name} <br>
<c:if test="${empty imgPath}">
<img alt="기본 이미지" src="images/default.png" width="200px">
</c:if>
<c:if test="${not empty imgPath}">
<img alt="비워두면 안됨" src="images/${imgPath}" width="200px">
</c:if>
<c:if test="${data.writer eq userID}">
<%--MultipartFile을 다룰 것임을 알려주는 enctype 속성--%>
<form action="updateBoard.do" method="POST" enctype="multipart/form-data">
<%--현재 게시글 번호--%>
<input type="hidden" name="bid" value="${data.bid}">
<%--입력받을 이미지--%>
이미지 <input type="file" name="image" onchange="preview(event)"> <br>
<%--입력받은 이미지를 미리 보여줄 이미지 태그--%>
<img id="previewImage" width="200px" style="display:none;margin:5px;" alt="미리보기 이미지"><br>
<%--전송--%>
<button type="button" onclick="submitForm()">이미지 변경</button>
</form>
</c:if>
<br>
<a href="main.do">메인으로 돌아가기</a>
</body>
</html>
해당 페이지는 게시글 상세보기 페이지로 해당 페이지에서 기존 이미지를 보여주고 수정할 이미지를 입력받는다. 이 때 css를 따로 적용하고 있지 않기 때문에 이미지가 제각기 다른 크기로 보여지는 것을 방지하기 위해 태그 속성으로 바로 width를 지정했다. (원래는 css에서 설정한다)
그리고 3가지 문제 중 작성자가 아닌 사람도 수정할 수 있는 문제를 해결하기 위해 JSTL과 EL식을 이용해 검증 조건을 추가했다.
<c:if test="${data.writer eq userID}">
작성자와 현재 로그인된 사용자(userID)가 일치한다면 사진을 수정할 수 있는 form 태그를 보여준다. 아니라면 해당 form 태그를 보여주지 않는다.
여기서 required를 적용해도 바로 안내를 띄울 수 있지만 aerlt창을 통해 안내하고 싶고, 이미 외부 js를 사용하고 있기 때문에 해당 js에 함수를 추가하고 해당 함수를 부를 수 있도록 수정했다.
<button type="button" onclick="submitForm()">이미지 변경</button>
function submitForm(){
if(document.querySelector("input[type=file]").value){//만약 입력값이 있다면
document.querySelector("form").submit();//폼전송
}
else{
alert("이미지를 업로드해주세요.");
}
}
현재 입력값이 있는지 검사해서 없다면 alert창을 띄우고 아니라면 폼 데이터를 전송하는 간단한 js다.
이 넘겨진 데이터는 요청을 타고 Controller로 이동한다.
Controller
원래는 게시글 수정이라 board update 파트에서 처리하고 있는데 글 내용 수정은 지금 계획에는 없으므로 view에서 준비된 데이터가 없기 때문에 생략하고 이미지만 처리한다.
private final String PATH = "/images/";
@RequestMapping("/updateBoard.do")
public String updateBoard(BoardDTO boardDTO, ImageDTO imageDTO, HttpServletRequest request) throws IOException {
//boardService.update(boardDTO); //이미지 수정만 진행할 예정
log.info("log: /updateBoard.do updateBoard start");
MultipartFile file = imageDTO.getImage(); //MultipartFile타입을 가진 필드에 자동 주입된 데이터를 저장
String fileName = file.getOriginalFilename(); //MultipartFile의 이름 추출
String projectTarget = request.getServletContext().getRealPath(PATH); //서버경로 (상대경로변환)
log.info("path : [{}]", projectTarget + fileName);
file.transferTo(new File(projectTarget + fileName)); //지정된 경로에 추출한 이름으로 저장
imageDTO.setPath(fileName); //해당 이름을 DB로 전달하기 위해 저장
imageService.insert(imageDTO); //DB에 추가
//수정한 글 상세 페이지로 이동
return "redirect:/boardInfo.do?bid="+boardDTO.getBid();
}
이 때 경로는 상단이나 config 파일에 선언해서 받아와 사용하면 되는데, 팀플 프로젝트에서 개발 환경이 다다를 때를 고려하고 싶어 실습코드에서 여러가지를 시도하며 함께 적용한 부분이다. request에서 현재 프로젝트 경로의 상대경로를 절대경로로 변경해 사용하고 있다. 만약 받아오는 주소가 target으로 뜬다면 정적리소스를 추가했을 때 로드되지 않으므로 로컬 서버 설정을 변경한다.
준비된 경로에 입력된 파일 이름을 출력해 DB에 저장하고 imageService를 통해 DB에도 추가 업로드를 진행했다. 폴더 아래의 여러 이미지 중 DB와 일치하는 이미지를 불러와 보여주게 된다.
@RequestMapping("/boardInfo.do")
public String boardInfo(BoardDTO boardDTO, Model model, ImageDTO imageDTO) {
log.info("boardDTO : {}", boardDTO);
ImageDTO image = imageService.selectOne(imageDTO);
//만약 이미지가 존재한다면
if(image != null) { //NPE방지
//이미지 경로 전달
model.addAttribute("imgPath", image.getPath());
}
model.addAttribute("data", boardService.selectOne(boardDTO));
return "boardInfo";
}
Model
이제 3번 문제를 해결할 Model파트이다.
private final String SELECTONE = "SELECT IMAGEID, PATH, BID FROM IMAGE WHERE BID=? ORDER BY IMAGEID DESC LIMIT 1";
public ImageDTO selectOne(ImageDTO imageDTO) {
Object[] args = {imageDTO.getBid()};
ImageDTO data;
try {
data = jdbcTemplate.queryForObject(SELECTONE, args, new ImageMapper());
} catch (EmptyResultDataAccessException e) {
data = null;
}
log.info("image result : {}", data);
return data;
}
EmptyResultDataAccessException 발생 시 null을 반환하도록 설계해 프로그램의 비정상 종료를 방지했다.
테스트
(rabbit / 토끼 계정으로 로그인)
토끼가 작성한 게시글에서는 이미지 수정 버튼이 잘 뜨는 것을 확인할 수 있다.
관리자가 작성한 글에 들어갔을 때는 수정 버튼이 뜨지 않는 것을 확인할 수 있다.
수정할 이미지를 선택하지 않고 이미지 변경을 누르면 aerlt창으로 안내한다.
제대로 이미지를 업로드하고 변경을 누르면 정상적으로 이미지가 반영된다.
'problem solving > 과제&실습 코딩' 카테고리의 다른 글
[실습/Python] 파이썬 복습 (코드 분석) (0) | 2025.01.07 |
---|---|
[실습/React] todo list (0) | 2024.12.03 |
[실습/Spring] AOP 활용 로그 작성 (1) | 2024.10.17 |
[실습/Spring] 검색 기능 비동기(ajax)로 변경하기 (2) | 2024.10.14 |
[실습/Spring] 비동기(ajax) id 중복 검사 구현하기 (0) | 2024.10.14 |