Spring 실습 11일차(파일 업로드)
파일을 업로드 했을 때 이미지의 경우 미리보기를 진행해보자
파일 미리보기(이미지)
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}이 들어가지 않도록 만들어놨다.