* JAVA POI 엑셀파일 생성시, "이 통합 문서의 내용을 복구하시겠습니까?" 에러 해결보자
최근 java POI를 사용하여 엑셀업로드 개발을 작업하고 있는데, 한가지 문제를 만났다.
엑셀 파일을 생성 후 아래 코드처럼 구현하였는데,
response.setHeader("Set-Cookie", "fileDownload=true; path=/");
response.setHeader("Content-Disposition", String.format("attachment; filename=" + excelName));
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
wb.write(response.getOutputStream());
이때 다운로드받은 파일을 열면 "파일 손상"이라는 에러가 발생하였다.
+ 어떤 에러?
[파일명]에 읽을 수 없는 내용이 있습니다. 이 통합 문서의 내용을 복구하시겠습니까? 이 통합 문서의 원본을 신뢰할 수 있는 경우 [예]를 클릭하십시오.
이 상태로 [예]를 누르게 되면 파일이 열리면서 아래 메세지가 적힌 팝업이 뜹니다
[파일명.xlsx](으)로 복구 읽을 수 없는 내용을 복구하거나 제거하여 파일을 열 수 있습니다. |
[복구] 버튼을 누르면 엑셀파일은 알맞게 생성되었지만 열때마다 뜨는 경고창을 해결해야했다.
ByteArrayOutputStream bout = new ByteArrayOutputStream();
wb.write(bout);
bout.close();
response.setHeader("Set-Cookie", "fileDownload=true; path=/");
response.setHeader("Content-Disposition", "attachment; filename=" + excelName);
response.setContentLength(bout.size());
ServletOutputStream out = response.getOutputStream();
out.write(bout.toByteArray());
out.flush();
out.close();
위 코드처럼 변경한 결과, 문제는 바로 해결되었다.
하지만 stream에 무지했기 때문에 위 코드를 보면서도 어떤 점 때문에 해결되었는지 알 수가 없었다.
하나하나, 모르는걸 알아가면서 원인을 찾아내보자.
(1) ByteArrayOutputStream
=> 내부적으로 저장 공간이 있어 출력되는 모든 내용들이 내부적인 저장 공간에 쌓이게 된다.
=> 메모리의 특정 객체에 byte 배열을 쓴다.
(2) response.setContentLength(bout.size());
=> 파일의 크기
(3) ServletOutputStream
=> 파일을 브라우저에 출력하기 위해 사용한다.
=> 추상클래스로서, 인스턴스를 생성할 수 없다.
(4) out.write(bout.toByteArray());
=> 브라우저에 출력할 파일
.
.
저 코드를 분석해본 이후, 나는 내가 Stream에 대한 JAVA 기초가 부족하다고 느꼈다.
response.setHeader("Set-Cookie", "fileDownload=true; path=/");
response.setHeader("Content-Disposition", String.format("attachment; filename=" + excelName));
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
wb.write(response.getOutputStream());
내가 처음 사용했던 위 코드에서 단지 OutputStream을 close해주지 않았기 때문이였다.
response.getOutputStream().close() 한줄 때문에 엑셀 파일에서 내용을 손실했다는 에러가 발생한 것이였다.
내가 위 코드를 아래 코드로 수정하였는데, 아래코드도 분석한 결과 몇가지 문제가 있었다.
ByteArrayOutputStream bout = new ByteArrayOutputStream();
wb.write(bout);
bout.close();
response.setHeader("Set-Cookie", "fileDownload=true; path=/");
response.setHeader("Content-Disposition", "attachment; filename=" + excelName);
response.setContentLength(bout.size());
ServletOutputStream out = response.getOutputStream();
out.write(bout.toByteArray());
out.flush();
out.close();
위 코드로 실행했을때 물론 내게 발생되었던 엑셀파일 파일손상 에러는 해결되었다.
위 코드를 다시 보자.
ByteArrayOutputStream 을 생성하고, wb.write(bout)로 해당 스트림에 wb객체 (엑셀파일)을 write한다.
(여기서, WorkBook 객체 wb의 write 메소드는 wb 객체에 쓰는게 아닌, bout 안에 자신을 쓴다는 뜻이다. = write out)
그리고 다시한번 ServletOutputStream 을 생성하여 ByteArrayOutputStream 스트림 안의 내용을 wirte 한다.
위 코드는 Stream을 굳이 2번 생성한 것이다.
1. ByteArrayOutputStream을 선언하고,
2. ByteArrayOutputStream스트림에 WorkBook 객체 wb를 쓰고,
3. 또다시 ServletOutputStream을 선언하고, --> Stream 2번 생성
4. 해당 스트림에 ByteArrayOutputStream 의 객체를 toByteArray 메소드를 사용하하여 write 한것이다.
결론적으로 맞는 코드는,
아래 코드처럼, response.getOutputStream()으로 해당 스트림에 WorkBook 객체 wb를 쓰고,
response.setHeader("Set-Cookie", "fileDownload=true; path=/");
response.setHeader("Content-Disposition", String.format("attachment; filename=" + excelName));
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
wb.write(response.getOutputStream());
마지막에 꼭 response.getOutputStream().close()로 해당 스트림을 close 해주는 것이다.
여기서 한가지 더,
아마 flush()를 선언해주고 close()를 선언하는 개발자가 있을것이다.
위 코드를 분석하면서 한가지 알아낸 사실은, close() 하나만 선언해도 선언되지않은 flush() 메소드도 실행된다는 것이다.
'JAVA' 카테고리의 다른 글
[JAVA] HTTPS 요청 시 SSL 인증서 오류 무시하기 (2) | 2020.08.06 |
---|---|
[JAVA] 이미지 업로드하기 (0) | 2020.06.12 |
[Java] 객체지향 생활 체조 (0) | 2020.02.25 |
[Java] Rest API (0) | 2020.02.21 |
[Java] DAO,DTO,Entitiy Class (0) | 2020.02.16 |