2022-03-28(월)
AJAX에서 하느냐 웹브라우저에서 하느냐
16.1 파일 업로드 : 자바스크립트 빌트인 객체 이용하기
http://localhost:8080/book/index.html
목록에 책 사진이 보인다
하나의 책을 선택하면 더 큰 그림으로 보인다
사진을 클릭하면 더 큰 사진이 나온다
도서록 테이블에 책 사진 파일 이름을 저장할 컬럼을 추가한다.
alter table ml_book
add column photo varchar(255);
도메인 클래스(값 객체; Value Object; VO)에 책 사진 파일 이름을 저장할 필드를 추가한다.
3단계 - SQL Mapper 파일을 변경한다.
resultMap 태그에 photo 컬럼 매핑 정보 추가
join 데이터는 생략하면 안 됨..
헷갈리니까 필드 이름이랑 컬럼 이름이 같아도 그냥 다 적기
findAll, findByNo, insert, update SQL문에 photo 컬럼 추가
http://localhost:8080/book/index.html
4단계 - 사진 파일 업로드 기능을 페이지 컨트롤러에 추가한다.
BookController 클래스 변경
서비스랑 Dao는 바꿀 필요 없음
@RequestMapping("/book/add")
public Object add(Book book, MultipartFile file) throws Exception {
// 파일이 업로드 되었다면 저장한다.
if (file != null && file.getSize() > 0) {
try {
String filename = UUID.randomUUID().toString(); // 임의의 파일명을 준비한다.
// 파일명의 확장자를 알아낸다.
int dotIndex = file.getOriginalFilename().lastIndexOf(".");
if (dotIndex != -1) {
filename += file.getOriginalFilename().substring(dotIndex);
}
// 파일을 지정된 폴더에 저장한다.
File photoFile = new File("/Users/nana/upload/book/" + filename);
file.transferTo(photoFile);
// 저장된 파일명을 도메인 객체에 설정한다.
book.setPhoto(filename);
} catch (Exception e) {
e.printStackTrace();
return "error!";
}
}
return bookService.add(book);
}
update도 똑같음. 근데 코드가 중복되니까
private String saveFile(MultipartFile file) throws Exception {
String filename = UUID.randomUUID().toString(); // 임의의 파일명을 준비한다.
// 파일명의 확장자를 알아낸다.
int dotIndex = file.getOriginalFilename().lastIndexOf(".");
if (dotIndex != -1) {
filename += file.getOriginalFilename().substring(dotIndex);
}
// 파일을 지정된 폴더에 저장한다.
File photoFile = new File("/Users/nana/upload/book/" + filename);
file.transferTo(photoFile);
return filename;
}
}
여기서 파일이 유효한지 검사하지 않는다
호출하는 쪽에서 예외가 발생했을 때 어떻게 할 건지
private String saveFile(MultipartFile file) throws Exception {
if (file != null && file.getSize() > 0) {
// 파일을 저장할 때 사용할 파일명을 준비한다.
String filename = UUID.randomUUID().toString(); // 임의의 파일명을 준비한다.
// 파일명의 확장자를 알아낸다.
int dotIndex = file.getOriginalFilename().lastIndexOf(".");
if (dotIndex != -1) {
filename += file.getOriginalFilename().substring(dotIndex);
}
// 파일을 지정된 폴더에 저장한다.
File photoFile = new File("/Users/nana/upload/book/" + filename);
file.transferTo(photoFile);
return filename;
} else {
return null;
}
}
}
@RequestMapping("/book/add")
public Object add(Book book, MultipartFile file) throws Exception {
// 파일이 업로드 되었다면 저장한다.
try {
book.setPhoto(saveFile(file));
return bookService.add(book);
} catch (Exception e) {
e.printStackTrace();
return "error!";
}
}
update도 똑같이 바꿔주기
프론트엔드
1단계 - 입력 화면에 사진 항목을 추가한다.
사진 파일을 업로드할 input 태그 추가
id -> name
서버에 보낼 파라미터 이름
필수입력 항목
값을 입력했는지 검사하기 위해 둔다
photo -> file
multipart로 넘어오는 데이터랑 이름 같으면 안 됨
http://localhost:8080/book/form.html
빈 문자열을 날짜 객체로 못 바꾼다
문자열을 날짜 객체로 바꿔서 저장하는데
빈 문자열은 날짜로 못 바꾼다
javascript formdata 검색
// 독서일을 지정하지 않았으면 서버에 보내지 않는다.
if (xReadDate.value == "") {
fd.delete("readDate");
}
독서일을 지정하지 않으면 서버에 보낼 때 readDate를 지워버린다
↑ readDate 없음
썸네일 이미지랑 상세 페이지 들어갔을 때 보이는 이미지랑 다른 파일임
출력 속도가 다름
이미지가 크면 속도가 더 느림
작은 이미지가 빠름
아마추어가 하는 방법
/static/book/index.html
photo/null
스프링 부트의 배치 경로
Spring Boot 실행
↓
Tomcat 서버 실행
↓
OS에서 제공하는 임시 폴더를 사용하여
Application을 배치한다
↓
Windows OS의 경우, 임시 폴더는
C:\Users\사용자홈\AppData\Local\Temp
실행할 때마다 폴더가 달라진다
스프링 부트는 실행할 때마다 위치가 결정된다
docbase
스프링 부트에서 파일 저장 경로
JVM을 실행하는 폴더
C:\Users\사용자홈\MyList\java
↑ JVM 실행 폴더
이클립스 IDE에서 App 클래스를 실행한다면 실행 폴더는 프로젝트 폴더이다.
예) .../mylist-boot/app
이클립스에서 실행하느냐 JVM에서 실행하느냐에 따라
스프링 부트에서 파일 읽기
파일을 저장한 폴더
URL 경로에서 지정한
파일 이름을 파라미터로
spring boot return binary file 검색
https://www.baeldung.com/spring-controller-return-image-file
https://stackoverflow.com/questions/35680932/download-a-file-from-spring-boot-rest-service
진짜 파일명과 오리지널 파일명을 함께 저장
다운로드할 때는 오리지널 파일명을 적어주면 됨
// header.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=오리지널파일명");
@RequestMapping("/book/photo")
public ResponseEntity<Resource> photo(String filename) {
try {
// 다운로드할 파일의 입력 스트림 자원을 준비한다.
File downloadFile = new File("./upload/book/" + filename); // 다운로드 상대 경로 준비
FileInputStream fileIn = new FileInputStream(downloadFile.getCanonicalPath()); // 다운로드 파일의 실제 경로를 지정하여 입력 스트림 준비
InputStreamResource resource = new InputStreamResource(fileIn); // 입력 스트림을 입력 자원으로 포장
// HTTP 응답 헤더를 준비한다.
HttpHeaders header = new HttpHeaders();
// 만약 다운로드 받는 쪽에서 사용할 파일명을 지정하고 싶다면 다음의 응답 헤더를 추가하라!
// header.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=오리지널파일명");
header.add("Cache-Control", "no-cache, no-store, must-revalidate");
header.add("Pragma", "no-cache");
header.add("Expires", "0");
// // HTTP 응답 생성기를 사용하여 다운로드 파일의 응답 데이터를 준비한다.
// BodyBuilder http응답생성기 = ResponseEntity.ok(); // 요청 처리에 성공했다는 응답 생성기를 준비한다.
// http응답생성기.headers(header); // HTTP 응답 헤더를 설정한다.
// http응답생성기.contentLength(0); // 응답 콘텐트의 파일 크기를 설정한다.
// http응답생성기.contentType(MediaType.APPLICATION_OCTET_STREAM); // 응답 데이터의 MIME 타입을 설정한다.
//
// // 응답 데이터를 포장한다.
// ResponseEntity<Resource> 응답데이터 = http응답생성기.body(resource);
//
// return 응답데이터; // 포장한 응답 데이터를 클라이언트로 리턴한다.
return ResponseEntity.ok()
.headers(header)
.contentLength(downloadFile.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
body의 파라미터 타입은 Resource만 가능하기 때문에 Resource로 포장한다
http://localhost:8080/book/photo?filename=9bd261b8-1f71-477b-8d85-d77af3bdb5f5.png
https://developer.mozilla.org/ko/docs/Web/CSS/vertical-align
이미지가 없는 책들도 있다
이렇게 기본 이미지가 뜨게 해야 한다
if (book.photo == null) {
book.photo = "default.png";
}
자바 썸네일 이미지 만들기 검색
라이브러리 다운 받아야 됨
https://github.com/coobird/thumbnailator
https://search.maven.org/artifact/net.coobird/thumbnailator/0.4.17/jar
// 썸네일 이미지 생성 라이브러리
implementation 'net.coobird:thumbnailator:0.4.17'
gradle eclipse
Refresh
// 썸네일 이미지 파일 생성
Thumbnails.of(photoFile)
.size(50, 50)
.outputFormat("jpg")
.toFile(new File("./upload/book/" + "50x50_" + filename));
썸네일 이미지로 만드니까 확실히 파일 크기가 작음
변경했더니 사진 날라감
사진 데이터를 변경하지 않으면 업데이트 항목에서 제외한다.
BookDao.xml
update sql문에 동적
https://mybatis.org/mybatis-3/dynamic-sql.html
<update id="update" parameterType="book">
update ml_book set
title=#{title},
author=#{author},
press=#{press},
feed=#{feed},
read_date=#{readDate},
page=#{page},
price=#{price}
<if test="photo != null">
,photo=#{photo}
</if>
where
book_no=#{no}
</update>
sql문을 만들 때 조건문, 반복문을 넣을 수 있다
• if
• choose (when, otherwise)
• trim (where, set)
• foreach
Lombok
Lombok 라이브러리를 프로젝트에 추가한다
https://projectlombok.org/setup/gradle
https://plugins.gradle.org/plugin/io.freefair.lombok
git/bitcamp-study/mylist-boot/app> gradle compileJava
소스파일을 다 컴파일한다
bin: 이클립스가 컴파일한 파일이 놓여지는 폴더
build: gradle 빌드 도구로 컴파일한 파일이 놓여지는 폴더
javap -cp bin/main com.eomcs.mylist.domain.Member
gradle compileJava
javap -cp build/classes/java/main com.eomcs.mylist.domain.Member
gradle compileJava
javap -cp build/classes/java/main com.eomcs.mylist.domain.Member
이클립스로 컴파일 할 때도 lombok이 적용되도록
https://projectlombok.org/download
승객 목록. 적하 목록.
jar 파일 안에 어떤 것들이 있는지
java -jar lombok.jar
IDE 체크되어 있는지 확인하고 Install/Update 클릭
도메인 클래스에 Lombok을 적용한다
Lombok 생성자
https://projectlombok.org/features/constructor
Author And Source
이 문제에 관하여(2022-03-28(월)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@banana/2022-03-28월저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)