Spring 실습

Spring 실습 11일차(파일 업로드)

choco2706 2024. 5. 7. 11:33

파일을 업로드 했을 때 이미지의 경우 미리보기를 진행해보자

파일 미리보기(이미지)

book폴더의 create를 열어 아래와 같이 작성해준다.

<form id="frm" action="/create" method="post" enctype="multipart/form-data">
	<p>제목 : <input type="text" name="title" required placeholder="타이틀"></p>
	<p>카테고리 : <input type="text" name="category" required placeholder="카테고리"></p>
	<p>가격 : <input type="text" name="price" required placeholder="가격"></p>
	<p id="pImg"></p>
	<p>도서 이미지 : 
		<input type="file" name="pictures" id="uploadFile">
	</p>
	<p>
		<input id="create" type="button" value="저장">
		<input id="list" type="button" value="목록">
	</p>
</form>

 

$(function(){
    // 이미지 미리보기 시작 //////
    $('#uploadFile').on('change',handleImg)
    // 이미지 미리보기 시작 //////
}

//e : onchange 이벤트
function handleImg(e){
   //<p id="pImg"></p> 영역에 이미지 미리보기를 해보자
   //이벤트가 발생 된 타겟 안에 들어있는 이미지 파일들을 가져와보자
   let files = e.target.files;
   //이미지가 여러개가 있을 수 있으므로 이미지들을 각각 분리해서 배열로 만듦
   let fileArr = Array.prototype.slice.call(files);
   //파일 타입의 배열 반복. f : 배열 안에 들어있는 각각의 이미지 파일 객체
   /*
   let arr = ["피자","떡볶이","탕수육"];
   //*******
   arr.forEach(function(str){
      console.log("str : " + str);
   });
   
   $.each(arr,function(idx,str){
      console.log("str[" + idx + "] : " + str);
   });
   */
   fileArr.forEach(function(f){
      //이미지 파일이 아닌 경우 이미지 미리보기 실패 처리(MIME타입)
      if(!f.type.match("image.*")){
         alert("이미지 확장자만 가능합니다.");
         //함수 종료
         return;
      }
      //이미지 객체를 읽을 자바스크립트의 reader 객체 생성
      let reader = new FileReader();
      
      $("#pImg").html("");
      
      //e : reader가 이미지 객체를 읽는 이벤트
      reader.onload = function(e){
         //e.target : f(이미지 객체)
         //e.target.result : reader가 이미지를 다 읽은 결과
         let img_html = "<img src=\"" + e.target.result + "\" style='width:50%;' />";
         //p 사이에 이미지가 렌더링되어 화면에 보임
         //객체.append : 누적, .html : 새로고침, .innerHTML : J/S
         $("#pImg").append(img_html);
      }
      //f : 이미지 파일 객체를 읽은 후 다음 이미지 파일(f)을 위해 초기화 함
      reader.readAsDataURL(f);
   });//end forEach
}

 

이미지가 선택될 때(change 될 때, 파일이 선택됐을 때) 파일이 여러개일 수 있으니 각각 분리해 fileArr에 담아둔다. for문을 통해 하나씩 가져와서 파일의 확장자가 이미지 확장자가 아닐 경우 함수가 종료되고, 이미지 객체일 경우

img_html이란 변수명에 <img>태그를 새로 만든 뒤 src의 값을 읽어온 파일의 데이터를 넣어준다.

 

그 뒤 #("pImg")(출력 영역)의 앞에 .append를 이용해 추가해준다.

 

이제 이전에 했던 파일 업로드 처리를 해주자.

 

@Override
	public int createPost(BookVO bookVO) {
		
		int result = this.bookDao.createPost(bookVO);
		
		// 파일업로드 및 ATTACH 테이블에 insert
		// 연월일 폴더 처리 ///////
		// C:\\ ... \\uploadFolder + "\\" + 2024\\05\\07 
		File uploadPath = new File(uploadFolder + "\\" + UploadConroller.getFolder());
		if(uploadPath.exists() == false) {
			uploadPath.mkdirs();
		}
		String uploadFileName = "";
        long size = 0;
        String contentType = "";
        String fileName = "";
        int seq = 1;
		
		// 스프링 파일 객체 타입의 배열로부터 파일객체를 하나씩 꺼내보자
		MultipartFile[] pictures = bookVO.getPictures();
		for (MultipartFile multipartFile : pictures) {
			log.info("--------------------------------");
			log.info("파일명 : " + multipartFile.getOriginalFilename());
			log.info("파일크기 : " + multipartFile.getSize());
			log.info("MINE : " + multipartFile.getContentType());
			
			// UUID
			UUID uuid = UUID.randomUUID();
			uploadFileName = uuid.toString() + "_" + multipartFile.getOriginalFilename();
			
			size = multipartFile.getSize();
			contentType = multipartFile.getContentType();
			// 2024\\05\\07 => /2024/05/07/asdfdsa_개똥이.jpg
			fileName = "/" + UploadConroller.getFolder().replace("\\", "/") + "/" + uploadFileName;
			
			// 파일 복사 계획
			// File 객체 설계(복사할 대상 경로 , 파일명)
			// File saveFile = new File(uploadFolder, uploadFileName);
            // uploadPath : D:\\A_TeachingMaterial\\06_spring\\springProj\\src\\
            //               main\\webapp\\resources\\upload\\2024\\05\\07
			File saveFile = new File(uploadPath, uploadFileName);
			
			try {
				multipartFile.transferTo(saveFile);
			} catch (IllegalStateException | IOException e) {
				log.error(e.getMessage());
			}
			
			AttachVO attachVO = new AttachVO();
			
			// 중복되지 않는 값
	        attachVO.setGlobalCode(bookVO.getBookId() + "");
	        // 위 GlobalCode와 함께 복합키를 이루는 값
	        attachVO.setSeq(seq++); // 들어간 뒤 증가
	        attachVO.setFileName(fileName);
	        attachVO.setFileSize(size);
	        attachVO.setContentType(contentType);
	        attachVO.setRegDate(null);
	         
	        log.info("attachVO : " + attachVO);
	        
	        result += this.attachDao.insertAttach(attachVO);
		}
		
		
		return result;
	}

 

본래 있던 createPost 메소드를 수정하여 만들었다. 

 

kr.or.ddit.utils 패키지 안에 UploadController를 만들고. getFolder메소드를 만들어 준 뒤 Impl에서 불러와줬다.

계속 같은 메소드를 Impl에 일일이 만들어주는것보다 공통으로 쓸 수 있도록 만들어 준 뒤 불러오도록 만들어줬다

package kr.or.ddit.utils;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.stereotype.Controller;

@Controller
public class UploadConroller {
	// 연/월/일 폴더 생성
	public static String getFolder() {
		// 2024-05-03 형식(format) 지정
		// 간단한 날짜 형식
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		// 날짜 객체 생성(java.util 패키지)
		Date date = new Date();
		// 2024-05-03
		String str = sdf.format(date);
		// 2024-05-03 -> 2024\\05\\03
		return str.replace("-", File.separator);
	}
}

 

 

첨부파일 1개 업로드 처리 (모듈화)

첨부파일 1개를 업도르 처리하는 코드를 모듈화 해보도록 한다.

p><input type="text" name="empNo" required placeholder="사원번호(ex. A001)"></p>
	<p><input type="text" name="empName" required placeholder="사원명"></p>
	<p><input type="text" name="empAddress" required placeholder="주소"></p>
	<p><input type="text" name="empTelno" required placeholder="연락처"></p>
	<p><input type="number" name="empSalary" required placeholder="월급"></p>
	<p id="pImg"></p>
	<!-- multiple 안씀 = EMPLOYEE : 증명사진 = 1 : 1 -->
	<p>
		<input type="file" name="uploadFile" id="uploadFile" placeholder="증명사진">
		<label for="uploadFile" class="btn btn-info btn-sm col-sm-1">증명사진 선택</label>
	</p>

 

multiple을 넣지 않아야 1개만 선택되고, label의 for를 input의 name값과 동일하게 맞춰놓으면 label을 클릭해도 input이 실행된다

private MultipartFile uploadFile;

 

VO에 추가해주고, ServiceImpl의 createPost에 아래 코드를 추가해준다.

 

// 스프링 파일 객체 타입의 배열로부터 파일객체를 하나씩 꺼내보자
		MultipartFile uploadFile = empVO.getUploadFile();
		
		result += UploadConroller.uploadOne(uploadFile, empVO.getEmpNo());
		
		return result;

 

이제 지난번 만들어놓은 곳에 uploadOne의 메소드를 만들어 준 뒤 파일 등록 코드들을 옮겨적으면 된다.

package kr.or.ddit.utils;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.multipart.MultipartFile;

import kr.or.ddit.service.dao.AttachDao;
import kr.or.ddit.vo.AttachVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class UploadConroller {
	
	@Autowired
	String uploadFolder;
	
	@Autowired
	AttachDao attachDao;
	
	// 연/월/일 폴더 생성
	public static String getFolder() {
		// 2024-05-03 형식(format) 지정
		// 간단한 날짜 형식
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		// 날짜 객체 생성(java.util 패키지)
		Date date = new Date();
		// 2024-05-03
		String str = sdf.format(date);
		// 2024-05-03 -> 2024\05\03
		return str.replace("-", File.separator);
	}

	// 첨부파일 1개 업로드
	public static int uploadOne(MultipartFile uploadFile, String globalCode) {
		int result = 0;
		
		File uploadPath = new File(uploadFolder, getFolder());
		if(uploadPath.exists() == false) {
			uploadPath.mkdirs();
		}
		
		log.info("------------------");
		log.info("파일명 : " + uploadFile.getOriginalFilename());
		log.info("파일크기 : " + uploadFile.getSize());
		log.info("MINE : " + uploadFile.getContentType());
		
		String uploadFileName = uploadFile.getOriginalFilename();
		
		UUID uuid = UUID.randomUUID();
		uploadFileName = uuid + "_" + uploadFileName;
		
		File saveFile = new File(uploadPath, uploadFileName);
		
		try {
			uploadFile.transferTo(saveFile);
			
			// 웹 경로
			String fileName = "/" + getFolder().replace("\\", "/") + "/" + uploadFileName;
			
			AttachVO attachVO = new AttachVO();
			attachVO.setGlobalCode(globalCode);
			attachVO.setSeq(1);
			attachVO.setFileName(fileName);
			attachVO.setFileSize(uploadFile.getSize());
			attachVO.setContentType(uploadFile.getContentType());
			attachVO.setRegDate(null);
			
			result = attachDao.insertAttach(attachVO);
			
		} catch (IllegalStateException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return result;
	}
}

 

나중에 다른 Impl들에서도 호출해서 필요한 값만 넣어주면 업로드가 되도록 모듈화 한 것이다.

 

그럼 이제 filename이 계속 null값으로 들어가고있을테니 filename값을 줘보자

 

아래코드를 ServiceImpl의 createPost에 추가해준다.

//4) employeeVO.filename=null
// 글로벌 코드를 조건으로 하여 첫번째 첨부파일 객체를 가져옴
AttachVO attachVO = this.attachDao.getFileName(empVO.getEmpNo());//ATTACH.GLOBAL_CODE
log.info("Impl -> attachVO : " + attachVO);

// 리턴 받은 fileName을 update해준다.
result += this.employeeDao.updateFileName(attachVO);

 

이후 AttachDao에 아래 코드를 추가해주고

public AttachVO getFileName(String globalCode) {
    return this.sqlSessionTemplate.selectOne("attach.getFileName",globalCode);
}

 

SQL 쿼리문을 작성해준다.

<select id="getFileName" parameterType="String" resultType="attachVO">
    <!-- 상관(CORRED)관계 (RELATIVE) 서브쿼리 -->
    SELECT A.GLOBAL_CODE, A.SEQ, A.FILE_NAME, A.FILE_SIZE, A.CONTENT_TYPE, A.REG_DATE 
    FROM ATTACH A
    WHERE GLOBAL_CODE = #{globalCode}
    AND A.SEQ = (
        SELECT MIN(B.SEQ)
        FROM ATTACH B
        WHERE B.GLOBAL_CODE = A.GLOBAL_CODE
    )
</select>

 

fileName이 리턴되어 attachVO에 담겼을테니 SQL에 UPDATE를 해주자.

 

EmployeeDao에 아래 코드를 추가해주고

public int updateFileName(AttachVO attachVO) {
    return this.sqlSessionTemplate.update("employee.updateFileName", attachVO);
}

 

SQL문을 작성해준다.

<update id="updateFileName" parameterType="attachVO">
    update employee set 
    file_name = #{fileName}
    where emp_no = #{globalCode}
</update>

 

emp_no의 값은 globalCode로써 attachVO에 담겨져있기 때문에 empNo로 불러오는게 아니다.

 

그리고 id = createPost의 SQL문도 수정을 해주자

<insert id="createPost" parameterType="employeeVO">
    INSERT INTO EMPLOYEE(EMP_NO, EMP_NAME, EMP_ADDRESS, EMP_TELNO, EMP_SALARY
    <if test="filename != null and filename != ''">
    , FILENAME
    </if>
    )
        VALUES(
            #{empNo},
            #{empName},
            #{empAddress},
            #{empTelno},
            #{empSalary}
            <if test="filename != null and filename != ''">
            , #{filename}
            </if>
        )
</insert>

 

filename이 null값이거나 없을때는 , #{filename}이 들어가지 않도록 만들어놨다.