사용환경

  • MacBook Air (M1, 2020) 16GB
  • JDK 8
  • Eclipse 2021-12
  • tomcat 8.5


에러의 발생

  • 요즘 JAVAJSP, 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>&nbsp; <a href="javascript:void(0);" onclick="removeImg();">삭제</a></div><br>
</form>
  • 글 수정 페이지의 폼태그 안에서 hidden 타입 input 태그를 써서 기존에 저장되어 있던 파일명을 저장해 두었다 함께 전송했다.
  • 만약 삭제 버튼을 누르면 onclick 이벤트를 통해 imgUploadStatusfalse로 바꿔 기존에 등록했던 파일을 삭제할 것임을 알린다.

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의 파일명 필드가 비어 있으면 기존 파일명을 저장한다.
  • 이제 BoardDTODB로 보내 업데이트 동작을 수행하면 파일명 필드가 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;
    }
}
  • 해결~!!