[231013] 13장

MJ·2023년 10월 23일

수업 TIL🐣💚

목록 보기
62/68
post-thumbnail

1교시

0. 13장에서 추가된 설정

  • 디펜던시 추가: Apache Commons IO 2.8, Apache Commons FileUpload 1.5

1. Multipart

  @Bean //AppConfig.java
  public MultipartResolver multipartResolver() {
    CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
    commonsMultipartResolver.setDefaultEncoding("UTF-8");
    commonsMultipartResolver.setMaxUploadSize(1024 * 1024 * 100);       // 전체 첨부 파일의 최대 크기 100MB
    commonsMultipartResolver.setMaxUploadSizePerFile(1024 * 1024 * 10); // 개별 첨부 파일의 최대 크기 10MB
    return commonsMultipartResolver;
  }
  • Multipart라는 단어가 보이면 전부 file을 뜻하는 것.
  • MultipartResolver : 파일 업로드 시 기본 세팅
  • MultipartResolver와 CommonsMultipartResolver의 관계는 인터페이스-인터페이스 구현체, ex) service-serviceImpl의 관계와 같다.
  • 항상 bean을 만들어서 타입을 넘길때는 인터페이스가 있다면 인터페이스 타입으로 넘겨야한다. 위 메소드도 반환값은 commonsMultipartResolver지만 반환타입은 MultipartResolver이다. (스프링에서는 인터페이스 기준으로 타입을 찾는다)

2. 파일첨부

MyFileUtil.java (완성본)

  • 여기에 메소드 여러개 만들어놓고 꺼내 쓸 예정 -> 메소드 꺼내쓰려면 객체로 만들어야하니까 클래스 어노테이션으로 @Component 필요 (@Autowired로 불러쓰기 위해 스프링 컨테이너 bean으로 잡아두는 작업)

파일 경로 반환 메소드

  // 파일이 저장될 경로 반환하기
  public String getPath() {
    
    /*  /storage/yyyy/MM/dd  */
    LocalDate today = LocalDate.now();
    return "/storage/" + today.getYear() + "/" + String.format("%02d", today.getMonthValue()) + "/" + String.format("%02d", today.getDayOfMonth());
 // return "/storage/" + DateTimeFormatter.ofPattern("yyyy/MM/dd").format(today);
    
  }
  • 파일이 한 폴더에 전부 모여있으면 양이 너무 커지기 때문에 저장 위치가 하루 한번씩 바뀌도록 최종 경로 dd(날짜)에 저장
  • String.format("%02d, today.getMonthValue( )): 2자리 정수인데 모자르면 0으로 채워라. / getMonth아니고 getMonthValue인 거 주의
  • String.format("%02d", today.getDayOfMonth()): 마찬가지로 2자리 정수, 모자르면 0 채우기 / getDayOfMonth()로 불러와야 1~31로 값이 나온다.
  • 마지막에 주석처리해놓은 것처럼 패턴으로 반환해도 된다.

파일 이름 반환 메소드

  // 파일이 저장될 이름 반환하기
  public String getFilesystemName(String originalName) {
    /*  UUID.확장자  */
    String extName = null;
    if(originalName.endsWith("tar.gz")) {  // 확장자에 마침표가 포함되는 예외 경우를 처리한다.
      extName = "tar.gz";
    } else {
      String[] arr = originalName.split("\\.");  // [.] 또는 \\.
      extName = arr[arr.length - 1];
    }
    
    return UUID.randomUUID().toString().replace("-", "") + "." + extName;
  }
  • filesystemName : 파일이 저장될 때 이름
  • originalName : 원래 이름 (업로드할 때 이름)ㅍㅊ
  • 올릴 때 이름과 저장될 때 이름은 다를 수 밖에 없다. a.txt을 3번 업로드하면 올릴 때 이름은 전부 a.txt지만 저장될 때는 같은 이름을 여러개 보관할 수 없으므로 전부 다른 이름으로 저장된다.
  • originalName이 a.txt면 filesystemName은 erognaerofjdvb3r2rf.txt (확장자만 같고 막 친거)이 될 수 있다. 이렇게 막 저장되어도 db에 저장시켜놨기 때문에 매치 가능...
  • 정리: originalName에서 확장자만 꺼내고(extName) 이름은 UUID를 사용해서 filesystemName으로 저장하면 된다. (UUID : 중복 없는 값을 임의로 만들어주는 자바 클래스)
  • originalName.endsWith: endsWith 뭘로 끝나는지 판단
  • if문: 확장자 마지막 마침표를 찾아가지고 잘라내면 확장자라고 판단 가능한데 tar.gz는 잘라내도 .gz가 나오니까 확장자를 찾을 수 없다? 그래서 tar.gz는 확장자에 마침표가 포함되는 예외상황이라 처리를 해주고 있음...
  • else문: tar.gz가 아니면 정상적인 확장자라고 가정하고 마침표로 파일 이름 구분해서 배열에 집어넣은 후 마지막 배열 요소 꺼내오는 코드
  • split()에서 괄호 안은 정규식으로 입력해야한다. 정규식에서 "."(마침표)는 모든 문자라는 뜻이니까 그렇게 적으면 틀림. 글자 마침표를 나타내고 싶다면 [.] 또는 \\. (원래는 \.인데 자바에서 역슬래시 인식하려면 두번 적어줘야함)
    -replace("-", ""): uuid 형태가 '어쩌구-저쩌구-어쩌구'의 형태라서 없애주는 것. 근데 굳이 안해줘도 되긴 함.

2교시

2. 파일첨부

index.jsp

  <div>
    <h3>MVC 파일첨부</h3>
    <form method="post" action="${contextPath}/upload.do" enctype="multipart/form-data">
      <div>
        <input type="file" name="files" class="files" multiple>
      </div>
      <div>
        <button type="submit">업로드</button>
      </div>
    </form>
  </div>
  • 파일 업로드 시 method="post", enctype="multipart/form-data" 필수
  • multiple : 다중 첨부가 가능한 파일

FileController.java

@RequiredArgsConstructor
@Controller
public class FileController {

  private final FileService fileService;
  
  @RequestMapping(value="/upload.do", method=RequestMethod.POST)
  public String upload(MultipartHttpServletRequest multipartRequest, RedirectAttributes redirectAttributes) {
    int addResult = fileService.upload(multipartRequest);
    redirectAttributes.addFlashAttribute("addResult", addResult);
    return "redirect:/main.do";
  }
  }
  • 업로드할 데이터가 파일첨부된 경우(input의 type이 file이고 enctype이 multipart/form-data로 잡혀있는 경우)에는 HttpServletRequest가 아닌 MultipartHttpServletRequest를 쓴다.
  • 주입해야하니까 autowired 위해서 @RequiredArgsConstructor
  • FileService upload로 multipartRequest 전달하고 반환은 리다리엑트로
  • db작업을 안해서 impl로 받아온 return 값 0을 controller가 쓰지는 않지만 이 메소드는 insert를 타고올 리다이렉트 메소드이므로 리다이렉트 해준다.
  • 리다이렉트 결과는 RedirectAttributes에 저장. (addResult를 받아서 RedirectAttributes에 저장)
  • (그 밖에 main.do가 index.jsp와 연결되도록 MvcController 작업해줬음. 생략)

FileService 인터페이스

public int upload(MultipartHttpServletRequest multipartRequest);

FileServiceImpl

@RequiredArgsConstructor
@Service
public class FileServiceImpl implements FileService {

  private final MyFileUtil myFileUtil;
  
  @Override
  public int upload(MultipartHttpServletRequest multipartRequest) {

    // 첨부된 파일들의 목록
    List<MultipartFile> files = multipartRequest.getFiles("files");
    
    // 순회
    for(MultipartFile multipartFile : files) {
      
      // 첨부 여부 확인
      if(multipartFile != null && !multipartFile.isEmpty()) {
        
        try {
          
          // 첨부 파일이 저장될 경로 가져오기
          String path = myFileUtil.getPath();
          
          // 저장될 경로의 디렉터리 만들기
          File dir = new File(path);
          if(!dir.exists()) {
            dir.mkdirs();
          }
          
          // 첨부 파일의 원래 이름 알아내기
          String originalName = multipartFile.getOriginalFilename();
          
          // 첨부 파일이 저장될 이름 가져오기
          String filesystemName = myFileUtil.getFilesystemName(originalName);
          
          // 첨부 파일의 File 객체
          File file = new File(dir, filesystemName);
          
          // 첨부 파일 저장하기
          multipartFile.transferTo(file);
          
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
    return 0;
  }
}  
  • @Service 애노테이션 붙여서 autowired 준비
  • multiple 설정으로 다중첨부가 가능하도록 해줬으므로 List로 작업한다.
  • MultipartFile : 스프링에서 첨부된 파일을 뜻함 (import 필요)
  • getFiles()로 꺼내주는 건 index.jsp의 input 태그 name 속성에 적어줬던 files (name으로 지정해줬던 것을 꺼내야함)
  • 기본적으로 파일 첨부는 예외가 필요하다 -> try-catch문 필수
  • @Component로 bean으로 만들어둔 MyFileUtil을 autowired하기 위해 MyFileUtil myFileUtil 필드 생성하고 클래스 레벨에 @RequiredArgsConstructor 추가
  • java에서 파일시스템 배울 때 저장방식은 첨부파일을 읽어서 저장할 장소? 스트림 뚫어서 파일 복사형식으로 보냈었는데 스프링에서는 transferTo라는 파일 저장 코드로 쉽게 해결된다.
  • transferTo(인수) : 인수로 보냄, 여기서는 인수로 file이 쓰였고 file은 new File(dir, filesystemName)으로 정의되어 있기 때문에 서버 경로와 서버이름으로 들어가게 되는 것 (=저장)
  • 진짜 데이터를 dao 사용해서 이용하는거면 return 값이 0이 아니라 addResult 같은거여야한다고 설명해주셨는데 생략해서 적어야지. 아무튼 여기선 0으로 끝냄


파일 여러개 선택해서 업로드해준 결과 날짜 폴더에 잘 저장됐다. 이름도 랜덤으로 잘 설정됨.


3교시

3교시부터 정리하면 댐

0개의 댓글