아인띠
Hello, Ain!
아인띠
  • 분류 전체보기 (23)
    • 사담🌹 (1)
    • Basic (7)
      • 알고리즘 (4)
    • Front (1)
      • React (1)
    • Back (9)
      • Java (5)
      • DATABASE (3)
      • JSP (1)
      • Python (0)
    • ETC (5)
      • Unity(C#) (2)

인기 글

티스토리

hELLO · Designed By 정상우.
아인띠
Back/Java

[Spring] 파일업로드, 파일다운로드 (1)

Back/Java

[Spring] 파일업로드, 파일다운로드 (1)

2022. 3. 25. 16:48

✨ 학원 다닐 때 파일 업로드, 다운로드를 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
    'Back/Java' 카테고리의 다른 글
    • RESTApi / XML로 데이터 응답하기
    • JDBC 기초
    • 서블릿 기초 (Servlet)
    • 클래스의 개념 (Java)
    아인띠
    아인띠
    https://github.com/aine-jeong

    티스토리툴바

    단축키

    내 블로그

    내 블로그 - 관리자 홈 전환
    Q
    Q
    새 글 쓰기
    W
    W

    블로그 게시글

    글 수정 (권한 있는 경우)
    E
    E
    댓글 영역으로 이동
    C
    C

    모든 영역

    이 페이지의 URL 복사
    S
    S
    맨 위로 이동
    T
    T
    티스토리 홈 이동
    H
    H
    단축키 안내
    Shift + /
    ⇧ + /

    * 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.