에러해결 Log) JAVA JSP Servlet 글 내용 수정할 때 첨부파일이 사라지는 문제
사용환경
- MacBook Air (M1, 2020) 16GB
- JDK 8
- Eclipse 2021-12
- tomcat 8.5
에러의 발생
- 요즘
JAVA
와JSP
,Servlet
으로 게시판을 만들던 중 파일 업로드 기능을 추가했다. 그런데 게시글을 수정하면 등록되 있던 이미지와 첨부파일이 삭제되는 것이었다. 🥲DB
에 등록되 있던 파일명 자체가null
이 되어버림… - 구글 검색을 시도해보니 업로드 한 파일의 수정이 필요할 땐 업로드 했던 파일을 삭제하고 다시 첨부해야 한다는 내용이 많아서 그렇게 해 보았는데 여전히 문제 해결에는 도움이 되지 않았다.
- 그래서 좀 더 생각해 보니 업로드 된 파일을 삭제하는 코드를 쓴 적 없었기 때문에 업로드 되는 파일들이 저장되는 경로에 가서 확인해 보니 역시 파일들은 그대로 있었다. 내가 짠 로직은
DB
에는 업로드 한 파일명만 저장하고 게시글을 불러올 때 서버에 업로드 되는 경로로 찾아가서 해당 파일들을 불러오는 것이라서DB
에서 업데이트 될 때 파일명만 잘 보존해 주면 문제를 해결할 수 있는 것이었다. - 게시글 작성 및 수정시 폼태그의
enctype="multipart/form-data"
속성을 사용해서 전송 하기 때문에 파라미터를 받을 때Multipart
객체를 사용해서 받아야 하는데 이 과정에서 실제로 받은 파일이 없다보니 파일명을 추출하는 과정이 생략되었고,DB
업로드용DTO
객체를 생성하는 과정에서 파일명에 대한 정보가 누락된 것이었다…!! 그리고DB
접속해서 업데이트 동작을 수행하는 과정에서null
이 입력되게 된다… - 그러면
file
타입input
태그의value
값을 기존에 저장되어 있던 파일명으로 넣어놓으면 되지 않나 싶었다. 그런데 안 됨 - 구글링 해 보니
file
타입은readonly
라서 파일을 직접 등록할 때가 아니면 임의로value
값을 넣어줄 수 없다고 한다.value
수정이 가능한 경우는value
값이 있을 때 그 값을 지울 때 뿐이었음. 그래서 다른 방법을 찾아야 했다.
문제 해결
boardModify.jsp
<%
String imgPath = request.getServletContext().getRealPath("/images") + File.separator + "origins" + File.separator + dto.getImage();
System.out.println("imgPath: "+imgPath);
%>
<form name="write" action="./BoardModifyPro.bo" method="post" enctype="multipart/form-data" onsubmit="return finalCheck();">
<div class="formRow">
<label for="MOD_TEXTFORM_NameField">이미지 등록 </label><input type="file" name="image" id="image" oninput="formatCheck();">
<input type="hidden" name="oldImgPath" value="<%=imgPath%>">
<input type="hidden" name="oldImgName" value="<%=dto.getImage()%>">
<input type="hidden" name="imgUploadStatus" value="true">
</div>
<div>등록된 이미지 : <span id="imgName"> <%=dto.getImage() %> </span> <a href="javascript:void(0);" onclick="removeImg();">삭제</a></div><br>
</form>
- 글 수정 페이지의 폼태그 안에서
hidden
타입input
태그를 써서 기존에 저장되어 있던 파일명을 저장해 두었다 함께 전송했다. - 만약 삭제 버튼을 누르면
onclick
이벤트를 통해imgUploadStatus
를false
로 바꿔 기존에 등록했던 파일을 삭제할 것임을 알린다.
BoardModifyProAction.java
public class BoardModifyProAction implements Action
{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception
{
System.out.println("M : BoardModifyProAction - execute() 호출");
// 한글처리
request.setCharacterEncoding("UTF-8");
// DB에서 기존 글 정보를 찾은 다음 새 내용으로 수정
FileUpload fu = new FileUpload();
BoardDTO dto = fu.upload(request);
// .. 하략
}
}
- 글 수정 동작을 수행하는 서블릿으로 오면 파일 업로드 처리를 위한 클래스인
FileUpload
로 이동한다.
FileUpload.java
BoardDTO dto = new BoardDTO();
// 기존에 저장되 있던 파일 정보를 저장할 변수
String imgUploadStatus = null;
String oldImgPath = null;
String oldImgName = null;
...
else if (name.equals("oldImgName"))
oldImgName = value;
else if (name.equals("imgUploadStatus"))
imgUploadStatus = value;
else if (name.equals("oldImgPath"))
oldImgPath = value;
- 서블릿으로 오면
Multipart
객체에서 기존 파일과 관련된 부분이 있으면 저장한다. - 수정모드에서 새로운 파일 첨부를 하지 않고 여기로 오게 되면 업로드 된 파일이 없으니 파일 등록 과정을 거치지 않고 내려올 것이다.
BoardDTO
객체의 이미지 파일명 정보는null
인 상태
if (imgUploadStatus != null)
{
if (imgUploadStatus.equals("false"))
{
// 등록된 이미지를 삭제하는 경우
dto.setImage("없음");
File oldImg = new File(oldImgPath);
if (oldImg.exists()) oldImg.delete();
}
else
{
// 삭제하지 않는 경우
if (dto.getImage() == null)
dto.setImage(oldImgName);
}
}
- 기존 이미지 파일 정보가 있어서 그것을 어떻게 처리해라는 지시사항이 있으면 그에 맞게 처리하는 분기로 이동한다.
- 등록된 이미지를 삭제하는 경우에는 파일명을 지우고 서버에서도 해당 파일을 지운다.
- 삭제하지 않는 경우인데 만약
BoardDTO
의 파일명 필드가 비어 있으면 기존 파일명을 저장한다. -
이제
BoardDTO
를DB
로 보내 업데이트 동작을 수행하면 파일명 필드가null
이 되지 않고 잘 남아있게 된다. - 아래는
FileUpload
클래스의 전체 코드이다.
package com.project.cafe.board.action;
import java.io.File;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;
import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.MultipartParser;
import com.oreilly.servlet.multipart.ParamPart;
import com.oreilly.servlet.multipart.Part;
import com.project.cafe.board.db.BoardDTO;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.name.Rename;
public class FileUpload
{
public BoardDTO upload(HttpServletRequest request) throws Exception
{
System.out.println("upload() 호출 ");
BoardDTO dto = new BoardDTO();
ServletContext ctx = request.getServletContext();
// 파일의 저장크기
int maxSize = 10 * 1024 * 1024; // 10MB
MultipartParser mp = new MultipartParser(request, maxSize);
mp.setEncoding("utf-8");
Part part;
// 기존에 저장되 있던 파일 정보를 저장할 변수
String imgUploadStatus = null;
String oldImgPath = null;
String oldImgName = null;
String fileUploadStatus = null;
String oldFilePath = null;
String oldFileName = null;
while ((part = mp.readNextPart()) != null)
{
// form 태그로 저장된 파라미터를 읽어옴
String name = part.getName();
if (part.isParam())
{
// 파일이 아닐 때
ParamPart param = (ParamPart)part;
String value = param.getStringValue();
System.out.println("param name :" + name + " value : " + value);
// 각 파라미터에 맞춰 dto에 저장
if (name.equals("id"))
dto.setId(value);
else if (name.equals("title"))
dto.setTitle(value);
else if (name.equals("content"))
dto.setContent(value);
else if (name.equals("num"))
dto.setNum(Integer.parseInt(value));
else if (name.equals("re_lev"))
dto.setRe_lev(Integer.parseInt(value));
else if (name.equals("re_ref"))
dto.setRe_ref(Integer.parseInt(value));
else if (name.equals("re_seq"))
dto.setRe_seq(Integer.parseInt(value));
else if (name.equals("oldImgName"))
oldImgName = value;
else if (name.equals("oldFileName"))
oldFileName = value;
else if (name.equals("fileUploadStatus"))
fileUploadStatus = value;
else if (name.equals("imgUploadStatus"))
imgUploadStatus = value;
else if (name.equals("oldImgPath"))
oldImgPath = value;
else if (name.equals("oldFilePath"))
oldFilePath = value;
}
else if (part.isFile() && name.equals("image"))
{
// 이미지 파일일 때
// 이미지 저장 경로 지정
File dir = new File(ctx.getRealPath("/images"));
File originDir = new File(dir + File.separator + "origins"); // 원본 이미지 저장 경로
File thumbnailDir = new File(dir + File.separator + "thumbnails"); // 썸네일 저장 경로
// 경로가 없으면 생성
if (!dir.isDirectory()) dir.mkdir();
if (!originDir.isDirectory()) originDir.mkdir();
if (!thumbnailDir.isDirectory()) thumbnailDir.mkdir();
FilePart filePart = (FilePart) part;
if (filePart.getFileName() != null)
{
filePart.setRenamePolicy(new DefaultFileRenamePolicy());
// 지정한 경로에 파일 쓰기
String file = filePart.getFileName();
filePart.writeTo(originDir);
dto.setImage(file);
// 중복 파일명을 처리할 부분
dto.setImage_uid(filePart.getFileName());
// 원본 이미지 파일을 이용해서 썸네일을 만들어 저장
// 크기 지정 후 지정 경로에 저장
Thumbnails.of(new File(originDir.getPath() + File.separator + file))
.size(300, 400)
.toFiles(thumbnailDir, Rename.NO_CHANGE);
}
else
System.out.println("img empty!");
}
else if (part.isFile() && name.equals("file"))
{
// 일반 파일일 때
File dir = new File(ctx.getRealPath("/upload"));
// 경로 없으면 생성
if (!dir.isDirectory()) dir.mkdir();
FilePart filePart = (FilePart) part;
String file = filePart.getFileName();
if (file != null)
{
filePart.writeTo(dir); // 지정한 경로에 파일 쓰기
dto.setFile(file);
dto.setFile_uid(filePart.getFileName());
}
else
System.out.println("file empty!");
}
}
// 사용자 ip주소 저장
dto.setIp(request.getRemoteAddr());
if (imgUploadStatus != null)
{
if (imgUploadStatus.equals("false"))
{
// 등록된 이미지를 삭제하는 경우
dto.setImage("없음");
File oldImg = new File(oldImgPath);
if (oldImg.exists()) oldImg.delete();
}
else
{
// 삭제하지 않는 경우
// 만약 dto의 파일명 필드가 비어 있으면 기존 파일명을 저장한다.
if (dto.getImage() == null)
dto.setImage(oldImgName);
}
}
if (fileUploadStatus != null)
{
if (fileUploadStatus.equals("false"))
{
dto.setFile("없음");
File oldFile = new File(oldFilePath);
if (oldFile.exists()) oldFile.delete();
}
else
{
if (dto.getFile() == null)
dto.setFile(oldFileName);
}
}
return dto;
}
}
- 해결~!!