1. 이슈사항

  1. DefaultHandlerExceptionResolver 발생
  2. JSON으로 파싱할때 타입이 안맞아서 생기는 에러로 보임(VALUE_STRING token)
WARN 10900 --- [nio-8989-exec-7] .w.s.m.s.DefaultHandlerExceptionResolver 
: Resolved [org.springframework.http.converter.HttpMessageNotReadableException
: JSON parse error
: Cannot deserialize instance of `[Lorg.springframework.web.multipart.MultipartFile;` out of VALUE_STRING token;

nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException
: Cannot deserialize instance of `[Lorg.springframework.web.multipart.MultipartFile;` out of VALUE_STRING token
 at [Source: (PushbackInputStream); line: 1, column: 106] (through reference chain: edu.bit.ex.joinvo.BoardImageUploadVO["uploadfiles"])]

 

2. 해결사항

2-1. VIew 페이지에서 AJAX 처리시 Type 파라미터를 FormData로 변경(JSON.stringify => FormData)

// 기존
$.ajax({
    dataType: 'json',   
    type : "POST", 
    url : $(this).attr("action"), 
    cache : false, 
    contentType:'application/json; charset=utf-8', 
    processData: false, 
    data: JSON.stringify({form, formData}), 
    success: function (result) {       
        $(location).attr('href', '${pageContext.request.contextPath}/board/magazine')             
    },
    error: function (e) {
        console.log(e);
        alert('업로드를 할 수 없습니다.');
        location.reload();
    }
});            

// 변경사항
$.ajax({
    type : "POST",
    url : $(this).attr("action"),
    cache : false,
    contentType:'application/json; charset=utf-8', 
    processData: false, 
    contentType: false, 
    data: formData, 
    success: function (result) {       
        $(location).attr('href', '${pageContext.request.contextPath}/board/magazine');
    },
    error: function (e) {
        console.log(e);
        alert('업로드 실패');
        location.reload();
    }
});

 

2-2. JSON.stringify()로 받지 않는다는건 말그대로 문자 형태의 데이터를 넘기지 않는다는 의미이므로 Controller단에 있는 파라미터의 @RequestBody 어노테이션을 달아줄 필요가 없다.

// 매거진 작성
@Transactional
@PostMapping("/magazine/write")
public ResponseEntity<String> magazineWrite(BoardPrdctImageVO bPrdctImageVO) {
	ResponseEntity<String> entity = null;
    log.info("magazineWrite..");
    MultipartFile[] uploadfiles = bPrdctImageVO.getUploadfiles();
    try {
    	boardService.setMagazineWrite(bPrdctImageVO); // 텍스트 등록(1)
        for (MultipartFile f : uploadfiles) {
        	boardService.setMagazineImage(f); // 이미지 등록(N)
        }
        entity = new ResponseEntity<String>("SUCCESS", HttpStatus.OK);
    } catch (Exception e) {
    	e.printStackTrace();
        entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
    } return entity;
}

 

3. JSP파일

<!-- 작성 폼 스크립트 -->
<script>
    $(document).ready(function () {
        $("#writeForm").submit(function(event){
            event.preventDefault();

            var formData = new FormData();

            // 텍스트 입력 영역
            var mbr_id = $("#mbr_id").val();
            var board_name = $("#board_name").val();
            var board_content = $("#board_content").val();

            console.log(mbr_id);
            console.log(board_name);
            console.log(board_content);    
            console.log($(this).attr("action"));   

            formData.append("mbr_id", mbr_id);
            formData.append("board_name", board_name);
            formData.append("board_content", board_content);

            // 파일저장 영역
            var inputFile = $("#uploadfiles");
            var files = inputFile[0].files;  

            for (var i = 0; i < files.length; i++) {
                console.log(files[i]);
                formData.append("uploadfiles", files[i]);
            }     

            // 파일 넣을때 JSON.stringify()는 적용이 안된다...
            $.ajax({
                type : "POST",
                url : $(this).attr("action"),
                cache : false,
                contentType:'application/json; charset=utf-8', 
                processData: false, 
                contentType: false, 
                data: formData, 
                success: function (result) {       
                    console.log("UPLOAD SUCCESS!")  
                    $(location).attr('href', '${pageContext.request.contextPath}/board/magazine');                                        
                },
                error: function (e) {
                    console.log(e);
                    alert('업로드 실패');
                    location.reload(); // 실패시 새로고침하기
                }
            }) 
        });
    })
</script>

<!-- 업로드 폼(간소화) -->
<form id="writeForm" method="post" action="${pageContext.request.contextPath}/board/magazine/write" enctype="multipart/form-data">
    <!-- 회원 id 가져오기 -->
    <input type="hidden" id="mbr_id" value="${magazine_write.mbr_id}">
    <!-- 글제목 -->
    <input class="form-control" type="text" id="board_name" name="board_name" placeholder="글제목을 입력하세요">
    <!-- 글내용 -->
    <textarea class="form-control" cols="3" id="board_content" name="board_content" placeholder="글내용을 입력하세요"></textarea>
    <!-- 첨부파일 -->
    <input type="file" id="uploadfiles" name="uploadfiles" placeholder="첨부 사진" multiple/>
    <button type="submit" class="btn btn-primary">작성하기</button>
</form>

 

4 . Controller

// 매거진 작성
@Transactional
@PostMapping("/magazine/write")
public ResponseEntity<String> magazineWrite(BoardPrdctImageVO bPrdctImageVO) {
    ResponseEntity<String> entity = null;
    log.info("magazineWrite..");

    MultipartFile[] uploadfiles = bPrdctImageVO.getUploadfiles();

    try {
        boardService.setMagazineWrite(bPrdctImageVO); // 텍스트 등록(1). 우선 텍스트 부분을 선행으로 한다.

        for (MultipartFile f : uploadfiles) {
            boardService.setMagazineImage(f); // 이미지 등록(N). 텍스트 부분이 선행되면 이미지를 올린다.
        }

        entity = new ResponseEntity<String>("SUCCESS", HttpStatus.OK);
    } catch (Exception e) {
        e.printStackTrace();
        entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
    }

    return entity;
}

 

5. Service

// 매거진 작성
public void setMagazineWrite(BoardVO boardVO);

// 매거진 첨부사진 작성
public void setMagazineImage(MultipartFile file);
// 매거진 작성
@Override
public void setMagazineWrite(BoardVO boardVO) {
    log.info("setMagazineWrite");
    boardMapper.setMagazineWrite(boardVO);
}

// 매거진 첨부사진 작성
@Override
public void setMagazineImage(MultipartFile file) {
    // 파일 이름 변경(중복방지)
    UUID uuid = UUID.randomUUID();
    String saveName = uuid + "_" + file.getOriginalFilename();
    log.info("image_name: ", saveName);

    // 저장할 File 객체를 생성(껍데기 파일)
    // 저장할 폴더 이름, 저장할 파일 이름
    File saveFile = new File(UPLOAD_PATH, saveName);

    try {
        // 업로드 파일에 saveFile이라는 껍데기 입힘
        file.transferTo(saveFile);
    } catch (IOException e) {
        e.printStackTrace();
    }

    boardMapper.setMagazineImage(saveName);
}

 

6. Mapper

// 매거진 작성
public void setMagazineWrite(BoardVO boardVO);

// 매거진 첨부사진 작성
public void setMagazineImage(String saveName);
<!-- 매거진 작성 -->
<insert id="setMagazineWrite" >
<![CDATA[
    INSERT INTO board (board_id, board_name, board_content, mbr_id, board_type_number, board_like, board_hit) 
    VALUES (board_seq.nextval, #{board_name}, #{board_content}, #{mbr_id}, 2, 0, 0) 
]]>
</insert> 

<!-- 매거진 첨부사진 작성 -->
<!-- 매거진 작성을 거친뒤 첨부사진을 올리는 상황이므로 업로드하는 board_id의 현재 번호를 가리키는 board_seq.currval을 insert 한다.  -->
<insert id="setMagazineImage" >
<![CDATA[
    INSERT INTO prdct_image (image_number, image_name, image_path, board_id) 
    VALUES (prdct_image_seq.nextval, #{uploadfiles}, 'D:/Others/Programming/Project Space/branches/branches_project/src/main/resources/static/prdct_img/', board_seq.currval)
]]>
</insert>

 

7. VO

@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
// 게시판 이미지 VO
public class BoardPrdctImageVO { 
    private String board_name; // 게시글제목
    private String board_content; // 게시글내용
    private String mbr_id; // 회원id
    private int board_type_number; // 게시판유형번호
    private int inquiry_number; // 문의유형번호
    private Date board_date; // 게시글작성일자
    private String prdct_id; // 상품id
    private int order_number; // 주문번호
    private int board_like; // 게시글추천수
    private int board_hit; // 게시글조회수
    private int board_starrate; // 게시글별점수

    private int board_id; // 게시글번호 부모pk, 자식fk

    private int image_number; // 상품이미지번호
    private String image_name; // 상품이미지명
    private String image_path; // 상품이미지경로

    private MultipartFile[] uploadfiles; // 게시판이미지업로드
}