10/08/2018

스프링 쇼핑몰 만들기 #11. 상품 이미지 첨부 기능 구현

스프링 쇼핑몰 만들기
- 깃허브 링크

쇼핑몰이라면, 어떤 상품인지 대략적으로 알 수 있는 이미지가 있어야합니다. 다른 이미지 호스팅 서비스에 업로드한 뒤 불러오는 방법도 있지만, 스프링에서 이미지를 업로드하는 방법으로 하겠습니다.

업로드하는 이미지는 하나하나가 그렇게 큰 용량이 아니겠지만, 상품 목록에서 여러개의 상품이 보여질 경우, 모든 이미지의 총 용량이 크기 때문에 서비스하는 입장에서도 사용하는 입장에서도 데이터에 대한 부담이 생기게 됩니다.

그렇기 때문에 썸네일을 사용해야하며, 이미지를 첨부하여 저장할 시 원본 이미지를 저장하고, 저장하는 원본 이미지를 썸네일화 시켜서 별도로 저장하게됩니다.

먼저 tbl_goods 테이블에 썸네일을 저장할 컬럼이 없으니 추가합니다.

alter table tbl_goods add (gdsThumbImg varchar(200));

썸네일을 저장할 gdsTumbImg 컬럼을 생성했습니다.

테이블에 gdsThumbImg가 추가되었으니 GoodsVO에도 gdsThumbImg를 추가하고, getter/setter를 추가합니다.

register.jsp에 파일 등록용 인풋박스를 추가하고, 스크립트도 추가합니다.

<div class="inputArea">
 <label for="gdsImg">이미지</label>
 <input type="file" id="gdsImg" name="file" />
 <div class="select_img"><img src="" /></div>
 
 <script>
  $("#gdsImg").change(function(){
   if(this.files && this.files[0]) {
    var reader = new FileReader;
    reader.onload = function(data) {
     $(".select_img img").attr("src", data.target.result).width(500);        
    }
    reader.readAsDataURL(this.files[0]);
   }
  });
 </script>
</div>

스크립트는 파일이 등록되면 현재화면에서 어떤 이미지인지 볼 수 있도록 해주는 역할입니다.

이제 상품 등록 페이지에서 파일 선택 버튼을 눌러서 아무 이미지를 선택해보면

이렇게 선택한 이미지가 보입니다.

등록 버튼과 너무 가까이 있으니, 스타일(CSS)을 이용해 여백을 줍니다.

여백이 생기니 조금 낫네요.

register.jsp의 파일 등록용 인풋박스 근처에 코드를 추가합니다.

<%=request.getRealPath("/") %>

이 코드는 현재 프로젝트의 실제 경로를 표시합니다. 스프링 파일이 저장되는 워크스페이스와 다르므로, 파일을 저장할 때 실제 경로를 알아야합니다.

이렇게 실제 경로가 출력됩니다. 이 경로를 기준으로 파일을 저장하고 불러올 수 있습니다.

현재 프로젝트는 개발자의 컴퓨터에서 로컬로 실행됩니다. 이 프로젝트가 완성되어 서버에 업로드하여 실행된다면 저 경로가 아닌 새로운 경로를 확인해야합니다.

servlet-context.xml에 파일 저장에 필요한 코드를 추가합니다.

<!-- 업로드 패스 설정 --> 
<beans:bean class="java.lang.String" id="uploadPath">
 <beans:constructor-arg value="D:\kubg\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\kubg\resources" />
</beans:bean>

<!-- 일반 파일 업로드 경로 -->
<resources mapping="/imgUpload/**" location="/resources/imgUpload/" />

<beans:bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
 <beans:property name="maxUploadSize" value="10485760"/>
</beans:bean>

uploadPath는 업로드할 패스를 빈(bean)에 저장하여 의존주입하여 사용하는 역할입니다.

업로드 경로는 주소가 /imgUpload/** 로 접속될 때, 실제 처리는 /resources/imgUpload/ 로 처리합니다. 긴 문자를 짧게 줄일 수 있으며 실제 경로를 숨길 수 있습니다.

마지막은 업로드하는 파일의 크기를 제한하는 빈입니다. 10485660은 바이트로서 메가로 변환하면 10메가가 됩니다. 상품에 등록되는 이미지의 크기는 1~3메가 정도면 충분하니 필요에 따라 줄이거나 늘리면 됩니다.

썸네일과 파일 업로드에 관련된 라이브러리를 가져옵니다.

thumbnailator - 링크
commons-fileupload - 링크

pom.xml의 디펜던시(dependencies)내부에 추가합니다.

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.8</version>
</dependency>

파일을 저장하고, 파일이 저장될 폴더를 생성할 새로운 패키지를 생성합니다.

이렇게 패키지를 따로 만드는 이유는, 컨트롤러에서 처리하기에 기능적인 성격이 다르기 때문입니다.

패키지가 추가되었으니, root-context.xml에 패키지를 추가합니다.

추가한 패키지에 UploadFileUtils.java를 생성하고 코드를 추가합니다.

package com.kubg.utils;

import java.io.File;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.UUID;

import org.springframework.util.FileCopyUtils;
import net.coobird.thumbnailator.Thumbnails;

public class UploadFileUtils {
  
 static final int THUMB_WIDTH = 300;
 static final int THUMB_HEIGHT = 300;
 
 public static String fileUpload(String uploadPath,
         String fileName,
         byte[] fileData, String ymdPath) throws Exception {

  UUID uid = UUID.randomUUID();
  
  String newFileName = uid + "_" + fileName;
  String imgPath = uploadPath + ymdPath;

  File target = new File(imgPath, newFileName);
  FileCopyUtils.copy(fileData, target);
  
  String thumbFileName = "s_" + newFileName;
  File image = new File(imgPath + File.separator + newFileName);

  File thumbnail = new File(imgPath + File.separator + "s" + File.separator + thumbFileName);

  if (image.exists()) {
   thumbnail.getParentFile().mkdirs();
   Thumbnails.of(image).size(THUMB_WIDTH, THUMB_HEIGHT).toFile(thumbnail);
  }
  return newFileName;
 }

 public static String calcPath(String uploadPath) {
  Calendar cal = Calendar.getInstance();
  String yearPath = File.separator + cal.get(Calendar.YEAR);
  String monthPath = yearPath + File.separator + new DecimalFormat("00").format(cal.get(Calendar.MONTH) + 1);
  String datePath = monthPath + File.separator + new DecimalFormat("00").format(cal.get(Calendar.DATE));

  makeDir(uploadPath, yearPath, monthPath, datePath);
  makeDir(uploadPath, yearPath, monthPath, datePath + "\\s");

  return datePath;
 }

 private static void makeDir(String uploadPath, String... paths) {

  if (new File(paths[paths.length - 1]).exists()) { return; }

  for (String path : paths) {
   File dirPath = new File(uploadPath + path);

   if (!dirPath.exists()) {
    dirPath.mkdir();
   }
  }
 }
}

이 코드는 폴더 생성과 파일 저장, 썸내일 생성의 작업을 합니다.

날짜(연/월/일)로 구성된 폴더를 생성하고, 같은 파일명이라도 중복되지 않도록 랜덤문자와 파일명을 조합한 뒤 생성된 폴더에 저장하며, 썸네일을 생성하여 별도의 폴더에 저장하도록 합니다.

컨트롤러에 servlet-context.xml에서 설정했던 uploadPath를 추가합니다.

상품 등록용 메서드에 코드를 추가합니다. 이때 주의할점은, 메서드의 매개변수에 MultipartFile file이 추가된 점입니다.

String imgUploadPath = uploadPath + File.separator + "imgUpload";
String ymdPath = UploadFileUtils.calcPath(imgUploadPath);
String fileName = null;

if(file != null) {
 fileName =  UploadFileUtils.fileUpload(imgUploadPath, file.getOriginalFilename(), file.getBytes(), ymdPath); 
} else {
 fileName = uploadPath + File.separator + "images" + File.separator + "none.png";
}

vo.setGdsImg(File.separator + "imgUpload" + ymdPath + File.separator + fileName);
vo.setGdsThumbImg(File.separator + "imgUpload" + ymdPath + File.separator + "s" + File.separator + "s_" + fileName);

파일용 인풋박스에 등록된 파일의 정보를 가져오고, UploadFileUtils.java를 통해 폴더를 생성한 후 원본 파일과 썸네일을 저장한 뒤, 이 경로를 데이터 베이스에 전하기 위해 GoodsVO에 입력(set)합니다.

파일이 첨부되었는지 확인하는 조건문 file != null 을 그대로 사용하실 경우, 파일을 첨부하지 않으면 에러가 발생합니다. 이 에러의 해결 방법은 이 게시물에서 확인할 수 있습니다.

파일을 첨부하지 않았을 경우 미리 준비된 none.png의 출력이 안되는 에러가 있습니다. 이 에러의 해결 방법은 이 게시물에서 확인할 수 있습니다.

resources폴더에 imgUpload 폴더를 생성합니다. 이 폴더는 파일이 저장될 기본이 되는 폴더입니다.

이 폴더를 생성하더라도 실제 경로, 즉 uploadPath에 등록된 폴더에는 바로 생성되지 않으며 프로젝트를 한번 실행해야 폴더가 생성되는데, 어차피 상품을 등록하려면 프로젝트를 실행해야하니 신경쓰지 않아도 됩니다.

이제 매퍼의 상품 등록 쿼리에 이미지(gdsImg)와 썸네일(gdsThumbImg)를 추가합니다.

<!-- 상품 등록 -->
<insert id="register">
 insert into tbl_goods (gdsNum, gdsName, cateCode, gdsPrice, gdsStock, gdsDes,
       gdsImg, gdsThumbImg)
     values (tbl_goods_seq.nextval, #{gdsName}, #{cateCode}, #{gdsPrice}, #{gdsStock}, #{gdsDes},
        #{gdsImg}, #{gdsThumbImg})
</insert>

register.jsp의 폼(form)에 속성을 추가합니다.

enctype="multipart/form-data"

이제 이미지 첨부가 제대로되는지 확인하기 위해, 임의의 값을 넣어서 등록하겠습니다.

데이터 베이스에는 제대로 들어갔습니다.

실제 경로로 들어가보니 원본파일과 썸네일 모두 저장된걸 확인할 수 있습니다.

등록을 했으니 조회도 해야합니다. 매퍼에 있는 조회용 쿼리를 수정합니다.

<!-- 상품 조회 + 카테고리 조인-->
<select id="goodsView" resultType="com.kubg.domain.GoodsViewVO">
 select
     g.gdsNum, g.gdsName, g.cateCode, c.cateCodeRef, c.cateName, gdsPrice, gdsStock, gdsDes, gdsImg, gdsDate,
     g.gdsImg, g.gdsThumbImg
         from tbl_goods g
             inner join goods_category c
                 on g.cateCode = c.cateCode           
            where g.gdsNum = #{gdsNum}
</select>

상품 조회는 GoodsVO가 아니라, GoodsViewVO형태이므로 GoodsViewVO도 수정해야합니다.

GoodsViewVO에 썸네일을 추가하고, getter/setter를 추가합니다.

view.jsp에 코드를 추가하여 원본 이미지와 썸네일을 모두 넣어줍니다.

<div class="inputArea">
 <label for="gdsImg">이미지</label>
 <p>원본 이미지</p>
 <img src="${goods.gdsImg}" class="oriImg"/>
 
 <p>썸네일</p>
 <img src="${goods.gdsThumbImg}" class="thumbImg"/>
</div>

원본 이미지와 썸네일 모두 정상적으로 나오긴하는데, 원본 이미지가 너무 크네요.

클래스를 부여한 뒤

스타일을 적용하여 크기를 조절합니다.

이제 한눈에 볼 수 있네요.

지금보니까 view.jsp인데도 제목이 '상품 등록'으로 되어있습니다.

텍스트만 바꿔주면 끝.

이제 모두 정상적으로 보입니다.

게시물 수정
  1. 그대로 했는데 이미지를 데이터 베이스에 넣었을떄 none.png로만 뜨는데 어떻게 해야할까요??

    답글삭제
    답글
    1. 안녕하세요?

      이미지가 none.png로 저장된다면 조건문에서 걸러지는 경우입니다.
      조건문에서 걸러지는 경우는, 이미지를 첨부하지 않았거나
      어느 이유로 인해 view(JSP)에서 controller로 데이터가 전달되지 않은 경우입니다.

      파일을 첨부하는 인풋박스의 태그가 <input type="file" id="gdsImg" name="file" />인데, name의 속성이 file임을 주의하시고 controller의 첨부 파일을 받는 매개변수가 MultipartFile file로, 파일을 담당하는 명칭이 file로 동일한것을 알 수 있습니다.

      파일 첨부가 되었는지 안되었는지 구분이 잘 되지 않는다면, 상품 등록 메서드의 가장 첫번째 줄에 file을 출력해보는 코드를 추가해보신 뒤, 파일을 첨부했을 때 콘솔을 보면 알 수 있습니다.

      삭제
  2. ajax로 이 기능을 사용하려고 하는데,
    파일 첨부 후 글 등록시 null 값으로만 넘어오네요.
    혹 제가 잘못하고 있는건지 궁금하여 조언을 구하고자 댓글 남깁니다.

    답글삭제
    답글
    1. 안녕하세요?

      에이젝스(Ajax)로 구현한다면, 단순히 에이젝스 전송 코드만 작성하기 때문에 특별히 문제가 생길 일은 없을겁니다. 작업하신 부분의 코드를 알려주시면 확인해보겠습니다.

      일단 가장 먼저 확인해야할 부분은, 뷰(View)에서 컨트롤러(Controller)로 값이 제대로 전송되는지 로그를 찍어서 확인하시는겁니다. 말씀하신대로 파일만 null값이 나온다면 매개변수나 VO등이 제대로 설정되어있는 확인해보셔야 할 것 같습니다.

      삭제
    2. @RequestMapping(value="/insert")
      public @ResponseBody HashMap insertWarn(BoardVo boardVo, HttpSession session, @ModelAttribute("file") MultipartFile file) {

      HashMap result = new HashMap<>();

      try {

      String imgUploadPath = uploadPath + File.separator + "imgUpload";
      String ymdPath = UploadFileUtils.calcPath(imgUploadPath);
      String fileName = null;

      if(file.getOriginalFilename() != null && file.getOriginalFilename() != "") {
      fileName = UploadFileUtils.fileUpload(imgUploadPath, file.getOriginalFilename(), file.getBytes(), ymdPath);
      } else {
      fileName = uploadPath + File.separator + "images" + File.separator + "none.png";
      }

      boardVo.setPicture(File.separator + "imgUpload" + ymdPath + File.separator + fileName);
      boardVo.setThumbImg(File.separator + "imgUpload" + ymdPath + File.separator + "s" + File.separator + "s_" + fileName);

      warnService.insert(boardVo);
      result.put("status", true);
      result.put("message","정상 처리 되었습니다.");

      } catch (Exception e) {

      result.put("status", false);
      result.put("message", e.getMessage());
      }

      return result;


      }

      위는 컨트롤러 부분입니다. jsp 화면의 경우 댓글로 남길 수가 없네요.
      form 태그를 사용하여 method="post" enctype="multipart/form-data" 옵션을 사용했고
      컨트롤러로 데이터를 넘기는 부분은 테이블로 작성했습니다.

      삭제
    3. 게시물의 모든 값이 null로 넘어오는건가요?

      음.. jsp는 태그 때문에 댓글 작성이 안되고
      HTML 태그 특수문자 변환
      을 이용해서 특수 문자 변환을 하신 뒤 붙여넣기 해주시면 됩니다.

      컨트롤러 부분을 보면 특별히 문제 없어 보이는데 왜 안되는지 좀 더 확인해보고.. 저도 집에가면 한번 실행해봐야겠네요.

      삭제
    4. <%@ page language="java" contentType="text/html; charset=UTF-8"
      pageEncoding="UTF-8"%>

      <script>
      //alert($.fn.jquery);
      </script>

      <style>
      .select_img {
      width : 50px;
      height : auto;
      }
      </style>

      <!-- Modal -->
      <form id="frm" action="/warn/warnInsert" method="post" enctype="multipart/form-data" >
      <div class="modal fade" id="myModal" role="dialog">
      <div class="modal-dialog">

      <!-- Modal content-->
      <div class="modal-content">
      <div class="modal-header">
      <button type="button" class="close" data-dismiss="modal">×</button>
      <h4 id="modal-title" class="modal-title"></h4>
      </div>
      <div class="modal-body">
      <table class="table">
      <tr>
      <td>사용자명</td>
      <td><input class="form-control" id="warnWriter" name="warnWriter" type="text"></td>
      </tr>
      <tr>
      <td>제목</td>
      <td><input class="form-control" id="warnTitle" name="warnTitle" type="text"></td>
      </tr>
      <tr>
      <td>내용</td>
      <td><textarea class="form-control" id="warnContent" name="warnContent" rows="10"></textarea></td>
      </tr>
      <tr>
      <td>이미지</td>
      <td><input type="file" id="picture" name="file" /></td>
      </tr>
      <tr>
      <td colspan="2"><div class="select_img"><img src=""/></div></td>
      <%-- <td><%=request.getRealPath("/") %></td> --%>
      </tr>
      </table>

      <script>
      $("#picture").change(function(){
      if(this.files && this.files[0]) {
      var reader = new FileReader;
      reader.onload = function(data) {
      $(".select_img img").attr("src", data.target.result).width(500);
      }
      reader.readAsDataURL(this.files[0]);
      }
      });
      </script>

      </div>
      <div class="modal-footer">
      <!-- <button id="modalSubmit" type="button" class="btn btn-success">Submit</button> -->
      <button id="modalSubmit" type="submit" class="btn btn-success">Submit</button>
      <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
      </div>
      </div>
      </div>
      </div>
      </form>

      추가적으로 jsp 화면입니다. (_ _)

      삭제
    5. alert 창에서 null 값이 넘어옵니다.
      이미지가 없었을 때는 정상적으로 되던 코드인데, 혹 제가 무엇인가 잘못했나 해서요..

      (+) 컨트롤러 단에서 ModelAttribute 가 아닌 RequestParam 으로 받아오니, null값이 넘어오진 않는데
      500 에러가 발생하네요.

      삭제
    6. 간단하게 확인해봤는데, 반환자를 해시맵에서 스트링으로 변경한 뒤 해보니 사용자/제목/내용/파일 모두 뷰에서 컨트롤러로 넘어오는걸 확인했습니다. 다만 라이브러리가 이것저것 적용된 상태에서 해서;; 새로운 환경에서 한번 더 해봐야겠네요.

      게시물 작성용 화면으로 넘어가지 않고 게시물 목록 또는 게시물 조회 상태에서 모달창(Modal Window)을 띄워서 새로운 게시물을 작성하고, 작성이 완료되면 목록 부분만 새로 로딩하시려는것 같은데

      일반적으로 잘 알려진 에이젝스(Ajax) 게시판이나 에이젝스 댓글 작성...
      이 블로그에서는 상품 소감(댓글) 에이젝스(Ajax) 적용 처럼 구현하시면 될 것 같습니다.

      삭제
    7. 그렇군요 ㅠ.ㅠ.. 감사합니다. 위에 올려주신 링크로 한번 다시 구현해보겠습니다!

      삭제
    8. modal과 ajax 두가지를 사용하여, 구현하고 있었는데 잘못 구현한 부분이 없는 것 같은데 안돼서 속상하네요 (_ _)

      삭제
    9. $(document).ready(function() {
      // 새 글 쓰기 버튼 클릭
      $("#createBtn").click(function(){
      action='create';
      type = 'POST'
      $("#modal-title").text("새 글 작성");
      $("#myModal").modal();
      });

      $("#frm").on("submit", function(e) {
      e.preventDefault(); // 이벤트 전파 막기(무효화)

      $.ajax('/warn/warnInsert', {
      method : "POST",
      contentType: false,
      processData: false,
      dataType: 'json',
      data : $('#frm').serialize(),
      success: function(data, status, xhr) {
      console.log(data);
      alert(data.message);
      if(data.status) {
      location.href="/warn/warnList";
      }
      },
      error : function(jqXhr, textStatus, errorMessage) {
      console.log(jqXhr);
      console.log(textStatus);
      console.log(errorMessage);
      }
      });
      });
      });

      위 구문이 ajax 구문인데 혹 이 부분의 문제일까요?

      삭제
    10. 에이젝스의 URL은 /warn/warnInsert
      컨트롤러의 URL은 /insert 로 되어있네요.

      그리고 컨트롤러의 insert메서드의 반환값은 맵 타입일 필요는 없습니다. 에이젝스에서는 전송이 성공 유무만 알면 되니까요. 또한 매개면수는 VO가 아니라 @RequestBody 맵/리스트/HttpServletRequest로 받으셔서 처리하면 될것 같습니다.

      마지막으로 에이젝스 전송 성공시 새로고침을 하는것보단, 목록을 불러오는 자바스크립트 함수를 작성하여 호출하는 방식으로 사용해야 에이젝스를 사용하는 의미가 있을것 같습니다.

      확인을 안해보고 댓글을 작성하는거라 100% 신용하진 말아주세요ㅠㅠ

      삭제
    11. URL 관련은 제가 올릴때 조금 수정해서 올려드린거라 동일합니다!
      음.. 한번 확인해보겠습니다. 답변 감사드려요!

      삭제
    12. 기능구현 자체는 생각하시는것보다 간단히 구현이 가능하기 때문에, 차근차근하시면 문제없이 되실겁니다.

      삭제
    13. ajax로 구현시, data 를 formData 로 넘겨주면 되네요! 이것저것 해보다 해결했습니다. 정말 감사해요^0^

      블로그 보면서 학습 중인데, 정말 큰 도움이 되는 것 같습니다 ㅎㅎ

      좋은 하루 되세요~^^!!

      삭제
    14. 해결되셨다니 다행입니다. 저는 다른 부분을 긁고 있었네요-_-ㅋ

      삭제
    15. 저도 이것 때문에 몇일을 고생한건지 모르겠네요 :D 신경 써주셔서 감사합니다!

      삭제
  3. 안녕하세요. 자료보며 배우고 있습니다. :)

    문제가 생겨 하루종일 고민하다 이메일로 프로젝트 보내드립니다. ㅠㅠ

    이미지 업로드는 잘 되고 폴더에 날짜별로 이미지가 들어가는 것까지 확인했습니다.
    그런데 상품 상세보기와 수정페이지에서 업로드한 이미지 파일을 불러오지 못하네요..
    뭐가 문제인지 봐주시면 감사하겠습니다!

    답글삭제
    답글
    1. 안녕하세요?

      보내주신 코드를 봤는데 특별히 문제 없어보이는것 같네요.

      실제 폴더엔 이미지가 정상적으로 업로드(복사)가 되는데, 프로젝트를 실행해서 보면 안나오는거는 거지요?

      이때 이미지가 있는 부분을 크롬기준으로 우클릭 → 검사를 눌러서 src 속성이 어떤지 확인해보시고, DB에 입력된 경로와 일치하는지 확인해보셔야 할 것 같습니다.

      삭제
  4. 네. 실제 폴더엔 정상적으로 업로드 되는걸로 보아 경로값에 문제가 있는 것 같아 보입니다.

    안그래도 메일 보내드린 뒤에도 계속 고민하다 src 속성을 확인해보니
    img src="\imgUpload\2019\03\28\s\s_46f7a0eb-5aa9-4cc1-a42d-2dc77c2e75c1_이미지테스트용.jpg"
    로 나오더라구요.

    AdminController에 gdsThumbImg의 경로를
    goodsVO.setGdsThumbImg(File.separator + "imgUpload" + ymdPath + File.separator + "s" + File.separator + "s_" + imgUploadPath + File.separator + fileName);
    로 바꾸어도 보았으나 여전히 불러오지 못하고 있는 상황입니다. ㅠㅠ

    추가로 이미지 업로드 시 실제경로가 아닌 스프링 내의
    src/main/webapp/resource/imgUpload 폴더 안에는 이미지가 들어가지 않는데
    이 부분은 문제와 무관한 것인지도 알고싶습니다.

    번거롭게 해드려서 죄송합니다. ㅠㅠ

    답글삭제
    답글
    1. 먼저 프로젝트에 있는 imgUpload폴더와의 관계는 무관합니다.

      프로젝트에 있는 이 폴더는 실제로 서버에 업로드하여 구동될 때 필요한 폴더이며, 개발중에 업로드되는 폴더는 sevlet-context.xml에 있는 uploadPath입니다. 즉, D:\STS_workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\shoppingmall\resources에 업로드 됩니다.

      프로젝트 실행시, img태그의 src경로가 \imgUpload\2019\03\28\s\s_46f7a0eb-5aa9-4cc1-a42d-2dc77c2e75c1_이미지테스트용.jpg라면, 실제로 파일이 D:\STS_workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\shoppingmall\resources\imgUpload\2019\03\28\s\s_46f7a0eb-5aa9-4cc1-a42d-2dc77c2e75c1_이미지테스트용.jpg에 정상적으로 있는지 확인해보셔야합니다.

      만약 파일이 없다면 경로를 설정하는데에서 잘못한 부분이 있는것이고, 파일이 없다면 업로드하는 코드 또는 servlet-context.xml에 문제가 있을 가능성이 있습니다.

      하지만 특별히 문제가 안보였는데, 다시 한번 확인해봐야겠네요.

      삭제
    2. D:\STS_workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\shoppingmall\resources\imgUpload\2019\03\28\s\s_46f7a0eb-5aa9-4cc1-a42d-2dc77c2e75c1_이미지테스트용.jpg 에는 정상적으로 들어갑니다..

      업로드하는 코드는 같게 작성했는데, servlet-context에서 문제가 있을 것 같습니다. ㅠㅠ

      뭐가 문제인지 더 찾아봐야겠네요..

      삭제
    3. 크롬등의 브라우저에서 이미지 경로를 그대로 복사해서, 주소창에 넣고 엔터키를 눌렀을 때 이미지가 정상적으로 나온다면..
      경로에 이상이 있는것 같은데, 설정 부분을 다시 확인해보셔야겠네요.

      이미지가 안나오는 증상으로 검색해보니 ${pageContext.request.contextPath}를 앞에 붙이면...
      <img src="${pageContext.request.contextPath}/[이미지경로]" /> 이런식으로 하면 나온다던데, 한번 참고하시기 바랍니다.

      삭제
    4. 앗..! ${pageContext.request.contextPath}를 붙이니 너무 잘되네요.
      간단한 문제였네요. 귀찮게 해드려서 죄송하고 감사합니다! ㅠㅠ

      삭제
    5. 안녕하세요? 해결되셨다니 다행이네요.

      이제 예전에 안나오던 이미지의 경로와, 지금 잘나오는 이미지의 경로를 비교해보시고
      만약 경로가 다르다면 servlet-context.xml을 수정하시면 아마도 ${pageContext.request.contextPath}를 추가로 입력하지 않아도 될 것 같습니다.

      경로가 똑같은데 안나온거라면.. 다른 설정과 관련이 되어있는건지 찾아봐야겠지만요ㅠㅠ

      삭제
    6. 넵 참고하겠습니다 감사합니다~^^

      삭제
    7. 익명9/12/2021

      저도 같은 증상인데 ${pageContext.request.contextPath} 를 추가로 입력해도 안됩니다 ㅠㅠ 이미지 경로 복사해서 주소창에 넣고 확인했을 때도 이미지가 정상적으로 나오는데 어느 부분을 확인해봐야 할까요? ㅠㅠ

      삭제
    8. 안녕하세요? 방문해주셔서 감사합니다.

      해당 코드를 넣었을때 표시되는 경로와 실제로 이미지 파일이 저장되 경로를 비교해보셔야할 것 같습니다.

      삭제
  5. 안녕하세요. 감사히 보면서 공부하고 있습니다. 다름이 아니라 이미지를 저장할 때 데이터 베이스에 경로가 저장되는 것 까지는 확인을 했는데요. imgUpload폴더에 파일이 저장이 안됩니다. 그래서 ${}로 jsp에 표시해 줬을때도 경로는 나오지만 img src = ${} 이런식으로 작성하면 이미지 소스가 없는 걸로 나옵니다. 어떻게 해야할까요ㅜㅜ

    답글삭제
    답글
    1. 안녕하세요?

      기본적으로 스프링에서 이미지의 경로는
      1. servlet-context.xml에서 설정한 파일 경로(<%=request.getRealPath("/") %>)
      2. 매핑된 경로 (<resources mapping="/imgUpload/**" location="/resources/imgUpload/" />)
      3. 컨트롤러에서 날짜별로 생성하는 경로(UploadFileUtils)클래스
      이렇게 3가지정도로 구분됩니다.

      매핑이 걸리기 때문에 데이터베이스에 저장되는 값은 2번+3번 값이 되며 파일이 저장되는 경로는 1번+2번+3번이 됩니다.

      탐색기를 이용해 1번+2번+3번 경로를 찾아갔을 때 실제로 파일이 존재하지 않았다면, 가장 의심되는 부분은 3번일 가능성이 높고, 다음은 파일을 업로드하는 input 태그 자체가 제대로 설정되지 않았을 가능성이 있습니다.

      첫번째로는 실제 파일의 정보가 제대로 컨트롤러에 넘어오는지 확인해야하며
      두번째로는 UploadFileUtils이 제대로 작동하는지 확인해야합니다.

      UploadFileUtils가 제대로 작동되는지의 여부는 로그(또는 println)를 찍어보거나, 디버그 모드를 이용해 한줄씩 실행, 또는 날짜로 폴더를 생성하는 부분을 주석처리하고 곧바로 1번+2번 경로에 저장되는것을 확인해 본 뒤, 차근차근 날짜 폴더를 생성하여 저장해보는겁니다.

      제가 코드를 차근차근 작성해야했는데.. 죄송합니다ㅠ

      삭제
    2. 아닙니다. 정말 소중한 글 감사히 보고있어요! 문제도 해결되었습니다. 정말 감사합니다!!

      삭제
  6. 작성자가 댓글을 삭제했습니다.

    답글삭제
  7. 안녕하세요! 잘 설명해주셔서 보고 따라했는데 저도 윗분처럼 조회가 안돼서요 ㅠ ㅠ 이미지 검사해서 브라우저에 복붙하니까 이건 잘 뜹니다. servletcontext문제인거같고 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'empController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'uploadPath' available 이 오류가 뜨는데 페이지가 뜨긴해요 데이터들도 들어가고 그래서 경로도 바꿔보고 id=uploadPath 준거 name으로도 바꿔보고 폴더를 다른데다 만들어보고 했는데도 소용이 없어서 댓글 달아봅니다 혹시 다른 방법이 있을까요? ㅠ ㅠ

    답글삭제
    답글
    1. 안녕하세요? 늦게 확인해서 죄송합니다.
      에러 메시지에 있는 empController에 주입되는 uploadPath에 대한건데..

      설정하는 XML문서상에 에러가 없고, 빈 네임이 올바르게 입력된 경우 프로젝트와 메이븐을 전부 클린시켜준 뒤 스프링 또는 이클립스를 재시작해보시기 바랍니다.

      삭제
  8. 안녕하세요 : )
    kuzuro님의 자료를 활용하여 토이프로젝트를 재밌게 만들고 있습니다.
    이 점 정말 감사드립니다!!

    저는 이 프로젝트를 로컬에서 작성한 뒤 서버에 올리는 과정중에 있습니다.

    이미지를 첨부하는 과정에서
    servlet-context.xml 파일에서






    업로드 경로를 이렇게 작성했습니다.

    하지만 war파일을 redeploy할때마다 디렉토리가 삭제되는 문제가 생겼습니다.

    그래서 업로드 경로를 프로젝트 외부(webapps 외부)로 바꾸려고 하는데 바꾸면 계속해서 오류가 나네요 ㅜㅜ

    혹시 이런경우에는 어떻게 해야할까요??



    답글삭제
    답글
    1. 업로드패쓰입니다!! 특수문자 변환을 했어야 햇네용 ㅎㅎ
      value="/var/lib/tomcat8/webapps/whose/resources"

      삭제
  9. 안녕하세요 선생님 블로그를 보면서 코딩하고 있는데 첨부파일부분에서 이상한 현상이 발생해서 질문남깁니다.
    등록하는 부분에서 if(file != null)부분이 아무래도 null로 실행되는것 같습니다
    이미지 경로가 \imgUpload\2019\10\21\D:\workspace~~~~~~\none.png 라고 db에 저장됩니다 ㅜㅜ 어느부분을 수정해야 할지 조언 구합니다

    답글삭제
  10. 오늘은 10월21일인데 10월2일로 올라가네요

    답글삭제
    답글
    1. 그러게요 ㅋㅋ혹시 하는방법아시나요??ㅠㅠ

      삭제
    2. 안녕하세요? 방문해주셔서 감사합니다.

      오라클에서 select sydate() from dual;을 실행했을 때에도 오늘날짜가 나오지 않나요?
      만약 그렇다면 오라클의 시간을 변경해주셔야 합니다.

      삭제
  11. 안녕하세요? 궁금한 점이 있는데 이미지 파일을 저장할 때 실제경로를 <%=request.getRealPath("/")%> 이렇게 해주었잖아요. 이건 현재 프로젝트는 개발자의 컴퓨터에서 로컬로 실행되므로 저 경로로 찾아주는게 맞지만, "이 프로젝트가 완성되어 서버에 업로드하여 실행된다면 저 경로가 아닌 새로운 경로를 확인해야합니다." 라고 써주셨는데.. 혹시 그 새로운 경로는 어디를 가서 찾아봐야 하는걸까요?혹시 아시나욤 ㅠㅠ??

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      말씀하신대로 로컬의 경로만 알수 있고, 실제 서버에 업로드한 경우 서버에서 프로젝트의 경로를 지정해주어야합니다. 단, 이건 본문에 있는 코드의 방법입니다.

      무슨 얘기냐면, 프로젝트에서 css나 js 파일 경로를 입력할 때처럼 절대 경로를 사용하지 않고, 프로젝트 내부에 있는 resources 폴더에서 가져오실겁니다. 이미지를 저장하는 경로 또한 이렇게 지정해주면, 경로를 별도로 찾아낼 필요가 없습니다.

      예를들어

      public void imgUpload(HttpServletRequest request) {
      String imagePath = request.getServletContext().getRealPath("resources/imgUpload/");
      }


      이런식으로 사용할 수 있습니다.


      물론 본문에 있는 방식 그대로 사용할수도 있습니다. 본문에선 이미 현재 로컬의 경로 구조를 알고 있지요?
      D:\kubg\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\kubg\resources입니다.
      즉, [프로젝트가 업로드된 경로]\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\kubg\resources 가 되겠네요. (물론 실제론 조금씩 다를겁니다.)

      또는 프로젝트를 서버에 올리고 접속하면 경로가 그대로 나올테니, 그것 그대로 올려주시면 됩니다

      삭제
  12. 안녕하세요 개발자님... !
    이미지가 출력되지않는 문제를 알려주신 것처럼
    ${pageContext.request.contextPath} 를 이용해서 출력은 성공했는데요 !
    다음달 되니 다시 출력이 안되더라구요....
    그니까 당일에 업로드한 이미지만 출력되고 다음날 되면 다시 다 꺠지는 현상이 발생하네요
    그럼 또 새로 업로드하면 그날은 또 출력되는데 다음날은 다시 다 안보여요 ㅜㅜㅜ

    이건 무슨 상황일까요 ! ?
    혹시 해답을 알 수 있을까싶어 댓글남깁니다 !

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      말씀하신대로라면 '날짜별로 경로가 바뀐다'라는것 같은데.. 아마도 이미지를 불러오는 로직에서 현재 날짜를 이용하여 경로를 불러오는것 같습니다.

      삭제
  13. 익명2/23/2020

    안녕하세요 혹시 다중업로드도 가능한건가요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      다중업로드는 물론 가능하며, 파일을 업로드하는 input의 갯수를 늘리고 이름을 달리하여 본문처럼 사용하시면 가능합니다.

      삭제
    2. 익명2/25/2020

      선생님 그러면 3개의 파일을 추가로 넣고싶다면 input을 3개 늘려야 한다는 말씀이죠?

      삭제
  14. 안녕하세요,, 파일 업로드 잘 보았습니다ㅣ
    파일 저장까지는 잘 되는데 파일을 보는데 문제가 있습니다.. .
    <td><img src="${vo.gdsImg}"></td>
    <td><img src="${vo.gdsThumbImg}"></td>

    위처럼 해도안되고 ${pageContext.request.contextPath} 을 붙혀도 안되는데,,,

    /img/2020/03/02/28e2786e-6e46-4c61-96b0-c2f299976006_threeColorCat.jpeg
    데이터베이스에 들어간 경로는 이렇게 되었고,

    위처럼 (<td><img src="${vo.gdsImg}"></td>)하였을때 콘솔창에 뜨는 경로는
    <img src="/img/2020/03/02/28e2786e-6e46-4c61-96b0-c2f299976006_threeColorCat.jpeg">
    이렇게 뜨는데 사진이 안나옵니다 ㅜㅜㅜㅜㅜㅜ
    왜 그럴까요,,,

    servlet-context.xml 추가 한 것 입니다..
    참고로 맥 사용하고있습니다.

    <beans:bean class="java.lang.String" id="uploadPath">
    <beans:constructor-arg
    value="/Applications/apache-tomcat-9.0.30/wtpwebapps/Gasan/resources/" />
    </beans:bean>

    <!-- 일반 파일 업로드 경로 -->
    <resources mapping="/img/**" location="/resources/img/" />

    <beans:bean
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
    id="multipartResolver">
    <beans:property name="maxUploadSize" value="10485760" />
    </beans:bean>


    답글삭제
  15. 작성자가 댓글을 삭제했습니다.

    답글삭제
  16. 위에 글에 추가 해서 설명 적겠습니다.
    일단 경로는 이렇게 되어있습니다.
    servlet-context.xml에도 여기로 저장해놓았구요
    <%=request.getRealPath("/") %>로 나온 경로에 /resources/
    를 붙혀주었습니다.
    /Applications/apache-tomcat-9.0.30/wtpwebapps/Gasan/resources

    사진을 올렸을 때는 보이는데
    시간이 지나고 나서는 파일이 지워? 사라지더라구요,,
    왜 그렇고 어떻게 해야할까요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다. 답변은 메일로 보내겠습니다.

      삭제
    2. 안녕하세요! 저도 똑같이 파일이 그 당시에는 올라간 게 보이는데 시간이 지나면 사라지는데 혹시 답변해 주실 수 있으실까요? ㅠㅠ...

      삭제
    3. 안녕하세요? 방문해주셔서 감사합니다.

      파일을 저장 할 때 프로젝트 외부에 저장하도록 하셔야 됩니다

      삭제
  17. kuzuro님 안녕하세요 업로드 기능 중 문제가 생겨 이메일로 문제나는 사항 보내 드렸습니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      메일 확인하겠습니다~

      삭제
  18. 잘보고 있어요 ㅎㅎ혹시 다중업로드와 다운로드도 구현해주실수 있을까요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      지금 진행중인 프로젝트가 좀 여유로워지면..
      보편적으로 사용되는 기능들 위주로 차근차근 올릴 수 있을것 같습니다..ㅠㅠ

      삭제
  19. 안녕하세요
    궁금한게 있는데
    게시판 테이블 내에
    gdsThumbImg 컬럼과
    gdsImg 컬럼을 만들어 주셨잔아요

    저는 글을 작성하면서 글 안에 이미지를 넣고 싶은데
    그러면 gdsImg 를 빼고
    gdsDes 만 사용해도되나요?



    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      늦게 확인해서 죄송합니다. gdsImg와 gdsThumbImg는 '첨부된 이미지'를 저장하기 위한 컬럼입니다. 이 컬럼은 현재 상품의 대표 이미지 1개만을 저장하기 위한 컬럼입니다.

      게시물 내용 중간중간에 들어가는 이미지의 경우, 에디터를 이용하여 업로드하시면 됩니다.

      이 기능의 경우 이 게시물에서 확인하실 수 있습니다.

      삭제
  20. 안녕하세요~ 블로그 잘만들어주셔서 보고 따라하고 있습니다. 감사합니다! 저 파일업로드 하는 중 계속 에러가 나는데..
    java.nio.file.NoSuchFileException:(경로) 이렇게 나는데 ㅠ 원인을 모르겠네요 ㅠ 답글주시면 감사하겠습니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      파일 업로드간 java.nio.file.NoSuchFileException 에러가 발생한다면, 해당 파일 또는 경로상의 폴더가 없는 경우입니다.

      이럴 경우 디버그 모드나 콘솔 출력등을 이용하여, 현재 코드가 어느 경로를 확인하고 있는지 확인해야할 필요가 있습니다.

      삭제
  21. 익명5/18/2020

    @Resource(name = "uploadPath")
    private String uploadPath;
    이 부분때문에 404 오류가 나는데



    이걸 root-context에 넣으면 오류가 안납니다.

    해결법좀 알수있을까요?? ㅠㅠㅠㅠ

    답글삭제
  22. 안녕하세요 Kuzuro님 블로그 보고 잘 따라하고 있습니다 감사합니다 다름이 아니라 이미지 조회 부분에서 문제가 있는데, 이미지 파일이 디비에 잘 등록이 되고, 이미지가 저장되는 경로로 가면 이미지도 잘 저장이 됩니다 상품을 조회하면 원본 이미지는 잘 나오나 썸네일이 출력이 안됩니다, F12로 보면 img = src(unknown) 이라고 나오면서 찾지를 못하고 있습니다 해당 게시글을 다시 천천히 훓어보면서 해결법을 찾아보고 있으나 답을 찾이 못하여 죄송하지만 이렇게 댓글 남겨봅니다 추가적으로 프로젝트를 다시 실행하면 이전에 있던 이미지들이 사라집니다. 프로젝트를 재실행해도 이미지가 계속 저장되게 하고 싶은데 어떻게 해야 하나요? 감사합니다

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      img태그의 src 속성에 언노운(unkown)으로 들어가있는 경우는, db에서 null값을 받아왔을 가능성이 매우 높습니다. db에서 가져온건 없는데, 넣으라고 하니까 null이 들어가서 언노운이 나오는걸텐데..

      썸네일의 경로를 가져오는 부분을 확인해보셔야할 것 같습니다.

      삭제
    2. 아하 썸네일 경로를 다시 확인하니 잘 됩니다! 감사합니다!! ㅠㅠ 죄송하지만 한가지 더 질문 남겨봅니다, 해당 이미지와 썸네일이 저장된 경로에 이미지가 저장이 된 후 프로젝트를 재실행 하면 경로에 있던 이미지들이 사라집니다, 제가 하고 있는 프로젝트 특성상 이미지가 계속 남아있어야 하는데 어떻게 해야 하나요?

      삭제
    3. 안녕하세요? 방문해주셔서 감사합니다..

      파일 업로드 경로가 프로젝트 내부에 있는 경우, 서버를 재시작할 때 초기화되는걸로 알고 있습니다. (프로젝트 자체가 초기화됩니다)

      그렇기 때문에 이미지 업로드는 프로젝트 외부의 경로를 사용하는 방식으로 바꿔야하는걸로 알고 있습니다.

      삭제
  23. 익명6/30/2020

    먼저 멋진 강의 감사합니다. 현재는 강의를 바탕으로 프로젝트를 완성한 상황인데
    문제점이 있어서 질문 드립니다.
    사진 첨부기능에서 첨부할때는 이미지가 잘 보이고 설정해준 경로에 디렉토리도 생기며 거기에 이미지도 저장이 되는데
    물건을 클릭하거나 관리자 계정으로 물건 목록을 볼때
    사진이 출력되지 않습니다.

    Failed to load resource: the server responded with a status of 404 () 이런 오류만 발생합니다.

    그전에는 문제가 없었는데 제가 뭔가 설정을 잘못 만졌는지 계속 이럽니다..

    혹시 어떤 부분에서 문제가 생기는지 집히는게 있으시다면 알려주시면 감사하겠습니다.

    답글삭제
    답글
    1. 익명6/30/2020

      해결했습니다! 그냥 제가 실수로 경로에 오타가 들어갔네요..

      삭제
  24. 안녕하세요 제가 프로젝트를 하고있는데 아파치톰캣을 이용해서 war파일로 배포 했는데 재배포 할때 이미지들이 초기화가 돼서 이렇게 댓글달아요 "이미지 업로드는 프로젝트 외부의 경로를 사용하는 방식으로 바꿔야하는걸로 알고 있습니다." 위에서 이렇게 말씀하셨는데 외부경로는 어떻게 바꿔야 하나요? 알려주시면 감사하겠습니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      특별한 방법이 있는게 아니고, 말그대로 프로젝트 경로(war가 배포된 폴더)의 외부 경로로하시면 됩니다.
      더 간단하게 설명드리면, 프로젝트가 D:/tomcat/root에 있다고 가정할시 D:/image 폴더처럼 지정해주시면 됩니다.

      삭제
    2. 답변 감사합니다. servlet-context에 외부경로를 지정했는데
      <beans:bean class="java.lang.String" id="uploadPath">
      <beans:constructor-arg
      value="C:/install/image"/>
      </beans:bean>
      <!--이미지파일업로드/ 일반 파일 업로드 경로 -->
      <resources mapping="/imgUpload/**"
      location="/resources/imgUpload/" />

      이런식으로 지정을 했는데 C:\install\image\imgUpload\2020\09\21 여기에 이미지파일이랑 썸네일은 저장이 됐는데 등록하고나서 이미지를 불러오지 못합니다. 해결방법 있을까요??

      삭제
    3. 이미지와 썸네일이 저장되었다면, 파일복사는 성공적으로 되었다는 의미이고, 불러오기가 안된다면..
      db에 저장된 이미지의 경로가 잘못되었을걸로 예상됩니다.

      삭제
  25. 등록을 했는데 이미지 파일이 안들어갑니다 ㅠㅠ 머가 잘못된걸까요 님이 한데로 하긴 했는데

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      등록을 했는데 이미지 파일이 복사(업로드)가 되지 않은건지, 복사(업로드)는 되었으나 불러오기가 안되는건지에 따라서 봐야할 코드가 달라집니다.

      전자의 경우 FileCopyUtils 부분을, 후자의 경우 db에 저장된 경로를 확인하셔야합니다. 이 두개가 아니라 아예 에러가 발생하는 경우라면 에러 메시지를 보셔야겠지요.

      일단 이 3가지중 어떤건지(혹은 다른건지) 확인해보셔야할 것 같습니다.

      삭제
  26. 안녕하세요 선생님 ㅠㅠ 프로젝트 만들다 궁금한게 있어서 댓글남겨요
    혹시 썸네일용 이미지 크기를 항상 고정 시켜서 나오게끔하고싶은데 항상 300 300 이 숫자 바꿔도 비율에 맞게 이미지가 나오네요ㅠ 제가 정한 수치로만 나오게끔 할 수 있는 방법이 있나요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      이미지의 원래 크기와 상관없이, 항상 300x300크기로 나오게 하고싶다면...
      CSS를 이용하여 크기를 고정시켜주시면 될 것 같습니다.

      삭제
  27. 안녕하세요. 공부중인 학생입니다.
    이 게시글의 댓글을 살펴보다가 궁금증이 들어 질문드립니다.
    파일업로드시 ajax를 사용하는 이유에 대해 알려주실수 있나요??
    댓글같은 경우에는 새로고침 할 필요 없이 댓글쪽만 조회하면 되니까 ajax를 사용한다 하더라도 파일업로드에는 왜 ajax를 사용하는건지 모르겠습니다.

    답글삭제
  28. 안녕하세요, 경로 설정에 대해 질문드릴게 있습니다.

    servlet-context.xml에서


    이 문구의 의미가 클라이언트가 /imgUpload/ 하위로 요청했을 때

    /resources/imgUpload로 요청을 바꿔 보내준다는 것으로 알고 있습니다.

    그런데 AdminController에서

    String imgUploadPath = uploadPath + File.separator + "/imgUpload";

    로 resources 없이 그냥 imgUpload로 경로설정 되어 있는데

    혹시 다른 곳에서 경로에 resources가 붙게 되는 건지 궁금합니다.

    제 경우에는 resources가 경로에 포함되지 않아 디렉토리가 생성되지 않는 오류가 있었고,

    resources를 경로 포함시켜, 즉 String imgUploadPath = uploadPath + File.separator + "/resources/imgUpload";

    로 수정했을 때만 정상작동했습니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      게시물 본문에 있는 uploadPath는 servlet-context.xml에서 설정한 값이 어노테이션으로 인해 자동으로 입력됩니다.

      <!-- 업로드 패스 설정 -->
      <beans:bean class="java.lang.String" id="uploadPath">
      <beans:constructor-arg value="D:\kubg\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\kubg\resources" />
      </beans:bean>


      만약 에러가 발생했다면, 실제로 이미지를 저장하게 될때의 imgUploadPath의 경로가 어디인지 확인해봐야할 것 같습니다.

      삭제
  29. 안녕하세요 이미지 업로드 및 조회까지 응용해서 사용 잘했습니다!
    한가지 궁금한게 servlet-context에서 업로드 패스 준거 상대경로로 바꾸고 싶은데
    자문좀 구할 수 있을까요...

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      이미지는 프로젝트 외부 별도 저장소에 넣다보니 직접 테스트해보진 않았는데..

      D:\myproject\images이런식으로 되어있던걸 \resource\images 이런식으로, 앞에 \ 로 시작해주시면 현재 프로젝트 경로로 될..겁니다.

      이 방법으로 안된다면, 자바 코드상에서 프로젝트의 현재경로를 가져와서 넣는 방법도 있습니다.

      삭제
  30. 안녕하세요 글 잘 보고 있습니다!! 제가 딱 servlet-context전에 <%=request.getRealPath("/") %>여기까지는 잘 나옵니다..
    근데 servlet에 업로드 패스 설정을 하고나면 500번 에러가 납니다..
    java.lang.reflect.InaccessibleObjectException: Unable to make field public static final jdk.internal.PreviewFeature$Feature jdk.internal.PreviewFeature$Feature.TEXT_BLOCKS accessible: module java.base does not "exports jdk.internal" to unnamed module @730068da
    이러한 에러가 나는데 혹시 이유를 알 수 있을까요?ㅜㅜ

    답글삭제
    답글
    1. 그리고 @Request 이 어노테이션 저는 import안되는데 따로 pom.xml에 추가 해야하나요? ㅠㅠ

      삭제
    2. 해결했습니다!! 3일동안 삽질만 했네요 ㅠ

      삭제
    3. 아고.. 요즘 일이 밀려서..; 늦게 확인해서 죄송합니다.
      그래도 해결하셔서 다행입니다-_ㅠ..

      삭제
    4. 제가 같은에러가뜨는데 어떻게해결하셨나여

      삭제
    5. 저도 해결했어요
      이게 jdk14 버전의 스프링 오류? 같은거더라구여 버전을 1.8로 다 업그레이드 시켜주니 되었습니다

      삭제
  31. 안녕하세요 좋은 정보 감사합니다. 도움이 되었네요

    답글삭제
  32. 익명3/30/2021

    안녕하세요! 작성하신 글 보고 공부 열심히 하고 있습니다. servlet-context에 업로드패스를 설정하면 오류가 생깁니다,,

    ERROR: org.springframework.web.servlet.DispatcherServlet - Context initialization failed
    java.lang.ArrayIndexOutOfBoundsException: Index 10060 out of bounds for length 1043
    at org.springframework.asm.ClassReader.readClass(Unknown Source)
    at org.springframework.asm.ClassReader.accept(Unknown Source)
    at org.springframework.asm.ClassReader.accept(Unknown Source)
    at org.springframework.core.type.classreading.SimpleMetadataReader.(SimpleMetadataReader.java:54)
    at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:80)
    at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:101)
    at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:76)
    at org.springframework.context.annotation.ConfigurationClassUtils.checkConfigurationClassCandidate(ConfigurationClassUtils.java:70)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:233)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:203)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:617)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:446)
    at org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:631)
    at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:588)
    at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:645)
    at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:508)

    아무리 찾아봐도 무슨 오류인지 몰라서 댓글 남겨봅니다ㅠㅠㅠ register.jsp에서 <%=request.getRealPath("/") %>까지는 잘 됩니다!!

    답글삭제
    답글
    1. 익명4/01/2021

      해결 했습니다!! pom.xml에서 java버전과 springframework버전을 맞춰주지 못해서 생긴 오류였습니다. 너무 기본적인 설정을 안했었네요ㅠㅠㅠ

      삭제
    2. 혹시 어떻게 해결하셨나요ㅠㅠ?

      삭제
  33. 안녕하세요! 따라하다가 중간에 오류가 발생해서 댓글달았습니다 !
    DB에도 잘 담기고 파일에도 잘 담기는데 이미지가 안떠서요ㅜ
    img src="\imgUpload\2021\03\29\72abc0fe-8e15-47b6-8146-ba9c851c37c4_WIN_20200708_02_15_29_Pro.jpg" class="oriImg" alt="Img Err" 관리자 도구에선 이렇게 뜨는데 어디가 문제인지 알 수 있을까요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      첫번째로 해당 경로에 이미지가 실제로 있는지 확인해야하고
      두번째로 imgUpload로 제대로 매핑이 되어서 스프링에서 접근할 수 있는지 확인해보셔야합니다.

      삭제
  34. 안녕하세요 선생님

    이미지 업로드하자마자 목록을 보면 이미지가 안뜨다가
    5초 정도 지난후에 새로고침을 하면 이미지가 제대로 뜨는데
    원래 그런건가요??

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      이미지 로딩이 느리거나 안된 경우일텐데... 업로드 하자마자 접속한 상태에서 F12를 눌러 관리자도구를 불러왔을때, 404에러가 발생했거나 img 태그에 정상적인 경로가 입력되지 않았는지 확인해야할것 같습니다.

      삭제
  35. 안녕하세요
    No bean named 'uploadPath' is defined
    이렇게 에러뜨는데 어디를 수정해야 할까요??

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      해당 에러는 upoloadPath를 선언하지 않아서 생긴것으로 보입니다.
      선언을 하지 않았는지, 선언했다면 오타가 나지 않았는지 확인하셔야할 것 같습니다.

      삭제
  36. 익명9/11/2021

    <%=request.getRealPath("/") %> 에서 나오는 실제경로를 업로드 패스 설정하는 곳에 하는 것 아닌가요?
    DB에 업로드 되지도 않고 java.io.FileNotFoundException: E:\project\workspace\ris\target\m2e-wtp\web-resources\resources\imgUpload\2021\09\11\bd628a08-8a02-4ab0-b59b-7409acfcfa80_math.PNG (지정된 경로를 찾을 수 없습니다) 이렇게 에러가 떠서 여쭤봅니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      말씀하신대로 실제이 저장될 경로입니다.
      db에는 파일의 경로가 업로드 되는데, db에 파일 경로를 포함하여 데이터가 업로드 되지 않았을 경우, 해당 로직부터 보셔야할 것 같습니다.

      추가로 실제로 파일이 업로드(복사) 되었는지도 확인이 필요로 합니다.

      삭제
  37. HTTP 상태 500 – 내부 서버 오류


    타입 예외 보고

    메시지 서블릿 [appServlet]을(를) 위한 Servlet.init() 호출이 예외를 발생시켰습니다.

    설명 서버가, 해당 요청을 충족시키지 못하게 하는 예기치 않은 조건을 맞닥뜨렸습니다.

    예외
    javax.servlet.ServletException: 서블릿 [appServlet]을(를) 위한 Servlet.init() 호출이 예외를 발생시켰습니다.
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:544)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
    org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:364)
    org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:624)
    org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:831)
    org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1651)
    org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
    java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
    org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    java.base/java.lang.Thread.run(Thread.java:832)


    근본 원인 (root cause)
    java.lang.ArrayIndexOutOfBoundsException: Index 10060 out of bounds for length 1043
    org.springframework.asm.ClassReader.readClass(Unknown Source)
    org.springframework.asm.ClassReader.accept(Unknown Source)
    org.springframework.asm.ClassReader.accept(Unknown Source)
    org.springframework.core.type.classreading.SimpleMetadataReader.(SimpleMetadataReader.java:54)
    org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:80)
    org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:101)
    org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:76)
    org.springframework.context.annotation.ConfigurationClassUtils.checkConfigurationClassCandidate(ConfigurationClassUtils.java:70)
    org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:233)
    org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:203)
    org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:617)
    org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:446)
    org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:631)
    org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:588)
    org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:645)
    org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:508)
    org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:449)
    org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:133)
    javax.servlet.GenericServlet.init(GenericServlet.java:158)
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:544)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)



    이런 오류가 자꾸뜨는데 도저히 무슨 오류인지 모르겠습니다..
    register.jsp에서 <%=request.getRealPath("/") %> 여기까지는 잘 됩니다.
    왜 저런 오류가 나오는건가요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      올려주신 로그에는 베열에 맞지 않은 인덱스가 들어왔다는데... 이클립스의 로그를 보면 어느파일 몇번째줄에서 에러가 발생했는지 확인할 수 있는데, 그쪽을 봐야할 것 같습니다.

      삭제
    2. 감사합니다.
      servlet.context 에 업로드패스 관련 코드를 넣으면 오류가 납니다.
      다른건 오류안나는데 저거 넣었을때만 오류가 나는데 왜 그런지 아시나요?

      삭제
    3. 메일보냈습니다. 시간되시면 한번만 확인부탁드립니다.

      삭제
    4. 저도 같은 오류가 나는데 해결보셨는지 궁금합니다!!

      삭제
    5. 저도 아직 해결못했네요ㅠㅠ

      삭제
    6. 이게 자바 버전문제인거 같은데 1.6버전을 사용해서 그런건가요?

      삭제
    7. 본문에 사용된 jdk버전은 1.8입니다.

      음.. 만약 본문에 있는 코드 <%=request.getRealPath("/") %>가 안된다면
      <%=request.getSession().getServletContext().getRealPath("/") %> 이 코드를 사용해보시기 바랍니다.

      아아아주 예전 버전에서 쓰던건데..

      삭제
  38. 혹시 일반 파일 업로드 기능도 위와 같이 진행해도 괜찮을까요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      파일도 거의 동일하게 진행되나, 썸네일을 만들 필요 없으며 업로드시 이미지를 보여줄 필요가 없다는것만 주의해서 작성하시면 되겠습니다.

      삭제
    2. 감사합니다!!

      삭제
  39. 안녕하세요. 감사하며 배우고있습니다. 작업중에 오류로 인해 진척이 없어 댓글을 남겨봅니다.
    (이제 이미지 첨부가 제대로되는지 확인하기 위해, 임의의 값을 넣어서 등록하겠습니다.) - > 이부분에서 실행하고 확인하려는데 실행시 아래 처럼 오류가 뜹니다

    11월 18, 2021 6:27:27 오후 org.apache.catalina.core.StandardWrapperValve invoke
    SEVERE: 서블릿 [appServlet]을(를) 위해 할당하던 중 예외 발생
    java.lang.ArrayIndexOutOfBoundsException: Index 2565 out of bounds for length 1396

    오류로 추정되는 부분이 있다면 알려주심 감사하겠씁니다.

    컨트롤러
    package kr.co.controller;


    import java.io.File;

    import javax.annotation.Resource;
    import javax.inject.Inject;


    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.InitBinder;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.multipart.MultipartFile;

    import ko.co.utils.UploadFileUtiles;
    import kr.co.service.AdminService;

    import kr.co.vo.AdminVO;


    @Controller
    @RequestMapping("/admin/*")
    public class AdminController {


    private static final Logger logger = LoggerFactory.getLogger(AdminController.class);

    @Inject
    AdminService service;

    // 파일업로드
    @Resource(name="uploadPath")
    private String uploadPath;




    // 낚시용품 글 작성 화면
    @RequestMapping(value = "/admin/priteView", method = RequestMethod.GET)
    public void priteView() throws Exception{
    logger.info("priteView");

    }




    // 낚시용품 글 작성
    @RequestMapping(value = "/admin/prite", method = RequestMethod.POST)
    public String prite(AdminVO adminVO, MultipartFile file) throws Exception{


    String imgUploadPath = uploadPath + File.separator + "imgUpload";
    String ymdPath = UploadFileUtiles.calcPath(imgUploadPath);
    String fileName = null;

    if(file.getOriginalFilename() != null && file.getOriginalFilename() != "") {
    fileName = UploadFileUtiles.fileUpload(imgUploadPath, file.getOriginalFilename(), file.getBytes(), ymdPath);
    } else {
    fileName = uploadPath + File.separator + "images" + File.separator + "none.png";
    }

    adminVO.setImage(File.separator + "imgUpload" + ymdPath + File.separator + fileName);
    adminVO.setImage(File.separator + "imgUpload" + ymdPath + File.separator + "s" + File.separator + "s_" + fileName);

    service.prite(adminVO);

    return "redirect:/";
    }

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      해당 에러는 파일 업로드간에 제대로 전송되지 않았을때 생기는 에러로 추측됩니다.
      이미지 업로드시, 컨트롤러-서비스-DAO까지 해당 업로드하는 파일의 데이터 정보가 제대로 전달되는지 확인해보셔야 할 것 같습니다.

      삭제
  40. 안녕하세요, 폴더 경로를 바꾸고 싶어서 그러는데 특별히 <%=request.getRealPath("/") %>를 사용해야할 이유가 있을까요? 프로젝트를 서버에 올리는 실습을 연습하고 싶은데 그런 경우에는 servlet-context에 경로를 지정해주는 방법 말고 다른 방법이 있는지 궁금합니다

    답글삭제
    답글
    1. 아니면 <%=request.getRealPath("/") %>의 경로 말고 절대 경로를 이용하여 폴더 생성 및 저장을 하는 방법이 있을까요? 항상 좋은 글 감사히 보고있습니다!

      삭제
    2. 안녕하세요? 방문해주셔서 감사합니다.

      해당 코드는 프로젝트 내부를 가르키기 때문에 관리가 용이하여 사용하게 되는데
      프로젝트 외부의 별도 폴더를 주기 위해서는 server.xml에서 외부 폴더를 등록해줘야할 필요가 있습니다.

      삭제
  41. 안녕하세요
    그동안 블로그 코드 보면서 따라 작성중인 왕초보입니다.
    이미지를 업로드하면 realPath에 저장이 되는데 다음날 되면 폴더가 사라지는 현상과
    이미지를 포함해서 register 하면 resources/imgUpload에 저장아 되지 않습니다.
    어떻게 해야 하는건지 알고 싶습니다. 필요하면 파일시스템을 이메일로 보내려고 합니다.

    그리고
    올려주신 코드를 이용해서 전체적인 사이트 틀을 완성했습니다 ㅎㅎ
    스스로 문제해결능력을 키워보기 위해 회원등급 verify를 수정하는 기능을 추가중에 있습니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      톰캣 설정 중 unpack 관련 설정이 있는데 이걸 false로 설정해주시면 됩니다.
      true(기본값)인 경우, 톰캣을 실행할 때마다 실행할 프로젝트의 파일을 싹다 지우고(!) 실행됩니다.

      또는 이미지가 저장되는 경로를 프로젝트 밖의 별도의 경로로 설정해주셔도 됩니다


      메일로도 보내드리겠습니다.

      삭제
  42. 익명4/11/2022

    VO에서 gdsImg와 gdsThumbImg 자료형 타입을 MultipartFile로 안해도 괜찮은건가요?? jsp에서 submit으로 넘길때 typeMismatch error가 뜹니다, 다른부분을 잘못한걸까요?

    답글삭제
    답글
    1. 익명4/11/2022

      제가 jsp파일 input name을 controller에 MultipartFile 변수명과 동일하게 안해서 오류가 났었네요, 근데 둘의 이름을 동일하게 하는 이유가 vo를 사용안하고 넘어가도록 하기 위한건가요? 해당 부분을 모르겠어서 가능하시다면 간단하게라도 설명해주시면 감사하겠습니다 ㅠㅠ

      삭제
    2. 안녕하세요? 방문해주셔서 감사합니다.

      gdsImg와 gdsThumbImg는 단순히 이미지 파일의 경로 위한 변수이므로 문자열로 하는것이 맞습니다. 실제 파일은 서버 역할을 하는 컴퓨터(현재 작업중인 컴퓨터)에 저장되어있으며, 이것은 경로만 있으면 불러올 수 있기 때문입니다.

      프론트 요소의 name와 VO의 변수명을 동일하게하는 이유는, 그냥 편해서입니다(....?)
      이 둘이 다르다고하면, 서로 맞춰줘야하는 추가작업을 해야하는 고생(?)을 해야합니다.

      종종 보이던 @requestParam이 그런 역할을 해줍니다.

      삭제
  43. 익명9/05/2022

    감사합니다 저를 살리셨어요

    답글삭제
  44. 안녕하세요! 지정한 경로 폴더안에 원본과 썸네일은 잘 저장되는데 db에는 아무것도 안 들어오는데 왜 이런걸까요??ㅠㅠ

    답글삭제
  45. 안녕하세요 최대한 제 힘으로 해보려 했으나 도저히 되지 않아 도움을 구합니다.
    저는 맥북으로 해당 프로젝트를 구현하고, 일부는 수정하며 개발 중입니다. 이 과정에서 어떤 문제가 있었는지는 모르겠으나,
    파일 업로드 부분에서 문제가 발생했습니다. 파일이 실제 경로에 업로드 되는데 업로드 된 후에는 404로 경로를 찾지 못하는 이슈가 발생합니다. 의심되는 부분은 우선 파일명이 이클립스 콘솔창에는
    <img alt="" src="ckUpload/08317f3a-fdac-4b58-abf2-733886efe35b_KoreaHome.jpg" />
    이렇게 찍히는데요,
    크롬에서 검사를 통해 경로를 확인했을 때, Element 창에서 jsp 파일 내부에서 찍히는 경로는 이클립스 콘솔창과 같지만 크롬에 있는 콘솔창에는
    /imgUpload/2022/11/06/s/s_51e8772a-2ae4-4b12-8281-7dd2756f58e5_%E1%84%8F%E1%85%B5%E1%84%91%E1%85%A5%E1%84%8C%E1%85%A1%E1%86%BC%E1%84%80%E1%85%A1%E1%86%B81%20%E1%84%89%E1%85%A1%E1%86%BC%E1%84%89%E1%85%A6.jpg
    이런식으로 아스키모드? 로 찍힙니다.
    그러면서 GET 을 실패했다는 빨강 표시가 콘솔창에 찍힙니다.
    제가 jsp 파일 내부에 선언한 코드는
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
    <!doctype html>
    <html lang="ko">

    이렇게 선언했는데 이게 문제가 될 수 있나요?

    아, 그리고 최초에는 스프링 콘솔창에 빨강 글씨로
    org.apache.catalina.webresources.AbstractFileResourceSet logIgnoredSymlink
    ~~~일치하지 않아서, 로드되지 못했습니다. 심벌릭 링크 사용이 원인 중 하나일 수 있습니다.

    이렇게 나와서 심벌릭 링크에 대해서 알아봤고 이게 절대경로 상대경로 개념인 것도 이해해서 톰캣이 보안상 루트 폴더에 있는 것을 허용하지 않는다고 해서 톰캣 context.xml 파일에 해당 심벌릭 링크를 허용하는 코드를 집어넣어 빨강색 경고가 나오는 것을 해결했습니다만, 여전히 404 에러가 떨어져서 질문을 하기에 이르렀습니다. 바쁘시겠지만 시간을 내주셔서 부족한 제게 도움을 주시기를 부탁드립니다. 감사합니다.

    답글삭제
    답글
    1. 죄송합니다 이것 저것 해보다가 해결하긴 했는데 그 이유도 정확하게 모르겠네요...감사합니다 이 블로그의 게시물이 제게 큰 도움이 되었습니다 :)

      삭제