✨ 학원 다닐 때 파일 업로드, 다운로드를 1도 이해 못 한 상태로 그냥 코드 따라 치기 했었는데 업무중 다중 파일 업로드, 파일 다운로드할 일이 생겨서 며칠을 삽질했다 ^.^...
내가 보려고 기록해두는 다중파일업로드..! 얼른 파일 관리쯤은 유틸로 만들어두고 5초 만에 휙 가져다 쓰는 머쨍이 개발자가 됐으면 좋겠다 🔥🔥
1. pom.xml 설정 추가하기
<!-- 파일업로드 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
2. context.xml에 MultipartResolver 등록
- 다중파일 업로드를 위해서는 MultipartResolver를 등록해야 한다.
- 서치한 레퍼런스들은 보통 dispatcher-context.xml에 빈을 등록했었는데, 본 프로젝트의 경우 기존에 있던 context-common.xml에 추가해주었음!
- bean의 id를 multipartResolver로 등록해야 한다.
<bean id="spring.RegularCommonsMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000000" />
<property name="maxInMemorySize" value="100000000" />
<property name="defaultEncoding" value="utf-8"/>
</bean>
<alias name="spring.RegularCommonsMultipartResolver" alias="multipartResolver" />
- maxUploadSize: 최대 업로드 가능한 바이트 크기
- maxInMemorySize: 임시 파일을 생성하기 전에 메모리에 보관할 수 있는 최대 바이트 크기
- defaultEncoding: 요청을 파싱 할 때 사용할 인코딩
3. DB 테이블 생성
- https://addio3305.tistory.com/83?category=772645 참고하여 테이블을 생성했다.
CREATE TABLE QNA_FILE
(
FILE_SEQ NUMBER, --첨부파일 Sequence(PK)
QNA_NO NUMBER NOT NULL, --첨부파일 원본글 번호
ORIGINAL_FILE_NAME VARCHAR2(260 BYTE) NOT NULL, --원본 파일명
STORED_FILE_NAME VARCHAR2(36 BYTE) NOT NULL, --저장 파일명(중복파일명 방지)
FILE_SIZE NUMBER, --파일 사이즈
CREA_DTM DATE DEFAULT SYSDATE NOT NULL, --저장 날짜
CREA_ID VARCHAR2(30 BYTE) NOT NULL, --글 작성자
DEL_GB VARCHAR2(1 BYTE) DEFAULT 'N' NOT NULL, --삭제여부
PRIMARY KEY (FILE_SEQ)
);
CREATE SEQUENCE QNA_FILE_SEQ
START WITH 1
INCREMENT BY 1
NOMAXVALUE
NOCACHE;
- QNA 게시판의 첨부파일이었기 때문에 알맞게 수정해주고, DEL_GB는 삭제 여부를 기록하는 컬럼인데 본 프로젝트에서는 아직 적용을 못했당.. (현재는 파일 삭제가 안 되는 상황..! 추후 수정해야 함)
4. jsp 수정
- 기존 jsp 페이지에 파일 첨부 버튼 추가해서 사용했음
- 직접 작성한 css가 아니기 때문에 css가 포함되는 class명은 명시하지 않았음 (다음에 쓸 때 화면 구성에 맞춰서 class명 추가하기..!)
- 파일 업로드하면서 DB에 저장하고, 파일 저장까지 한 뒤에 첨부한 파일명들을 화면에 노출하는 부분에서도 시간 왕창 잡아먹음...😞
<form:form commandName="qnaVO" id="qnaForm" name="qnaForm" mothod="post" enctype="multipart/form-data">
<td> 파일첨부 </td>
<td>
<div>
<input type="file"
name="file"
id="file-upload"
accept="video/*, image/*, .hwp"
multiple="multiple"/>
<div class="spanBox"></div>
<label for="file-upload">Upload</label>
</div>
</td>
</form:form>
<button type="button" class="main__table-btn main__table-btn--blue" onclick="saveQnA();">
작성
</button>
- input 태그의 accept의 경우 허용하는 확장자를 명시
- multuple="multiple"은 다중 파일을 다룰 때 추가해줘야 한다.
- div spanBox영역은 첨부한 파일명들을 추가해 줄 공간
- label의 경우 for로 input의 id를 지정해주면 해당 input 태그를 제어하여 상태를 변경할 수 있다 (new!)
4-1. javascript
<script>
function saveQnA() {
frm = document.qnaForm;
frm.acrion = "<c:url value="/question"/>";
frm.submit();
}
</script>
4-2. javascript로 jsp에 첨부한 파일명 띄워주기
var sel_files = [];
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$("#file-upload").on("change", handleFileSelect);
});
function handelFileSelect(e) {
select_files = [];
$(".spanBox").empty();
var files = $(this)[0].files;
for(var i = 0; i < files.length; i++) {
var html = "<span class=''>"+files[i].name+"</span>";
$(".spanBox").append(html);
};
}
</script>
5. SQL문 작성 (xml파일) - ORACLE
- DB가 다르면 foreach부분이 달라진다 (mysql에 적용되는 코드 가지고 삽질했음 ㅎ.ㅎ)
<insert id="registerQnAFile" parameterType="java.util.List">
INSERT INTO OPN.QNA_FILE (
FILE_SEQ,
QNA_NO,
ORIGINAL_FILE_NAME,
STORED_FILE_NAME,
FILE_SIZE,
CREA_DTM,
CREA_ID,
DEL_GB
)
SELECT QNA_FILE_SEQ.NEXTVAL, A.* from(
<foreach item="item" collection="list" separator="UNION ALL " >
SELECT #{item.qna_no} as qna_no,
#{item.original_file_name} as original_file_name,
#{item.stored_file_name} as stored_file_name,
#{item.file_size} as file_size,
SYSDATE as crea_dtm,
#{item.crea_id} as crea_id,
'N' as del_gb
FROM dual
</foreach>
) A
</insert>
6. 환경에 따라서 VO, DAO, Service 등 작성
- 본 프로젝트는 VO / Mapper(DAO) > Service > ServiceImpl로 구성되어 있었다
//QnAFileVO
import java.time.LocalDateTime;
@Data
public class QnAFileVO{
private int file_seq;
private int qna_no;
private String original_file_name;
private String stored_file_name;
private long file_size;
private LocalDateTime crea_dtm;
private String crea_id;
private String del_gb;
}
// QnAMapper
@Mapper("qnaMapper")
public interface QnAMapper {
int registerQnAFile(ArrayList<QnAFileVO> qnaFileVO) throws Exception;
}
// QnAService
public interface QnAService {
int registerQnAFile(ArrayList<QnAFileVO> qnaFileVO) throws Exception;
}
// QnAServiceImpl
@Service("qnaService")
public class QnAServiceImpl implements QnAService {
@Resource(name = "qnaMapper")
private QnAMapper qnaDAO;
@Override
public int registerQnAFile(ArrayList<QnAFileVO> qnaFileVO) throws Exception {
return qnaDAO.registerQnAFile(qnaFileVO);
}
}
7. 컨트롤러에 파일 업로드 부분 추가
- 포스팅용으로 따로 작성해서 수정해야겠다..
- 각 주석으로 설명 대체 (블로그 코드 블록에서 들여 쓰기가 엉망진창으로 인식된다..)
@Controller
public class QnAController {
private static final Logger LOGGER = LoggerFactory.getLogger(QnAController.class);
@Resource(name = "qnaService")
private QnAService qnaService;
//파일을 저장할 경로 (properties 파일로 따로 빼기)
public String qnaUploadPath = "D:/project/java/workspace/lx_user/src/main/webapp/WEB-INF/uploads/QnAFile/";
@PostMapping("/question")
public String createQuestion(@ModelAttribute("qnaVO") QnAVO qnaVO,
BindingResult bindingResult,
Model model, HttpSession session,
HttpServletRequest request,
MultipartHttpServletRequest mtfRequest) throws Exception {
//로그인한 유저 정보 받아서 작성자 등록하기 위해 Session 가져옴
MemberVO member = HttpSessionUtils.getMemberFromSession(session);
//요청 들어온 file 목록을 MultipartFile List로 받아준다
List<MultipartFile> fileList = mtfRequest.getFiles("file");
ArrayList<QnAFileVO> uploadList = new ArrayList<>();
if(!fileList.isEmpty()) {
int index = 0;
//요청들어온 파일 개수만큼 반복
for(MultipartFile mf : fileList) {
if(mf.isEmpty())
continue;
String originFileName = mf.getOriginalFilename();
//업로드할 파일 정보 세팅
QnAFileVO upload = new QnAFileVO();
upload.setOriginal_file_name(originFileName);
upload.setCrea_id(member.getUserName());
upload.setQna_no(qnaVO.getSeqNo());
upload.setFile_size(mf.getSize());
//대체파일명에 날짜 저장하기 위해 사용
SimpleDateFormat formatter = new SimpleDateFormat("YYYYMMdd_HHMMSS_"+index);
Calendar now = Calendar.getInstance();
index++;
//대체파일명: 날짜 + 인덱스 + originFileName
String stored_file_name = formatter.format(now.getTime()) + "_" + originFileName;
String uploadPath = qnaUploadPath + stored_file_name;
try {
//파일 저장하는 부분
mf.transferTo(new File(uploadPath));
//DB에 등록하기 위해서 업로드한 파일목록을 리스트에 추가
uploadList.add(upload);
} catch (Exception e) {
e.printStackTrace();
}
//대체파일명 세팅
upload.setStored_file_name(stored_file_name);
}
if(uploadList.size() > 0) {
//DB등록
qnaService.registerQnAFile(uploadList);
}
}
LOGGER.debug("seq no : {}", qnaVO.getSeqNo());
qnaVO.setOriginNo(qnaVO.getSeqNo());
qnaService.updateOriginNo(qnaVO);
return "redirect:/qnaList";
}
길어져서 일단 파일 업로드 부분에서 마무리!
블로그용으로 따로 예제 만들어서 수정해야 할 것 같다 ㅠ_ㅠ
'Back > Java' 카테고리의 다른 글
RESTApi / XML로 데이터 응답하기 (0) | 2021.09.17 |
---|---|
JDBC 기초 (0) | 2021.07.21 |
서블릿 기초 (Servlet) (0) | 2021.07.20 |
클래스의 개념 (Java) (0) | 2021.07.17 |