[코드로 배우는 스프링 웹 프로젝트 개정판] 6장 정리

첨부파일 업로드는 사용자가 게시물을 등록하기 전에 업로드한 파일을 확인할 수 있게 해준다. 첨부파일 처리는 복잡하며, 업로드된 파일의 초기화, 업로드된 이미지 처리, 다운로드, 원본 이미지 보여주기, 첨부파일 삭제 등을 포함한다. 게시물 수정 시 첨부파일 처리는 기존 파일 삭제 및 새 파일 추가를 포함한다. 잘못 업로드된 파일 삭제는 주기적으로 실행되어야 하며, 이는 `Spring-Batch`나 `Quartz` 라이브러리를 통해 처리할 수 있다.
DriedPollack's avatar
Mar 26, 2024
[코드로 배우는 스프링 웹 프로젝트 개정판] 6장 정리
Contents
🌼게시물의 삭제와 첨부파일💡핵심 키워드🌼게시물의 수정과 첨부파일💡핵심 키워드🌼게시물의 조회와 첨부파일💡핵심 키워드💡핵심 키워드🌼프로젝트의 첨부파일-등록🌼게시물의 조회와 첨부파일💡핵심 키워드💡핵심 키워드🌼프로젝트의 첨부파일-등록🌼파일 업로드 방식💡핵심 키워드🌼파일 업로드 상세 처리💡핵심 키워드🌼브라우저에서 섬네일 처리💡핵심 키워드🌼첨부파일의 다운로드 혹은 원본 보여주기💡핵심 키워드🌼프로젝트의 첨부파일-등록💡핵심 키워드🌼게시물의 조회와 첨부파일💡핵심 키워드🌼게시물의 삭제와 첨부파일💡핵심 키워드🌼게시물의 수정과 첨부파일💡핵심 키워드🌼잘못 업로드된 파일 삭제💡핵심 키워드🏁결론

🌼게시물의 삭제와 첨부파일

💡핵심 키워드

  • 게시물을 삭제할 때는 게시물이 포함된 첨부파일 역시 삭제해야 한다.
  • 단순히 데이터베이스 상에서 삭제만 이루어지는 게 아니라 실제 폴더 내의 파일도 같이 삭제할 필요가 있기 때문에 작업의 순서 역시 신경 써야만 한다.
    • 폴더에서의 파일 삭제는 위험한 작업이기 때문에 가능하면 뒤쪽으로 미루고 데이터베이스의 삭제 작업을 처리한 후 실제 파일을 삭제한다.

첨부파일 삭제 처리

  • 작업의 순서는 다음과 같다.
    • 해당 게시물의 첨부파일 정보를 미리 준비
    • 데이터베이스 상에서 해당 게시물과 첨부파일 데이터 삭제
    • 첨부파일 목록을 이용해서 해당 폴더에서 섬네일 이미지(이미지 파일의 경우)와 일반 파일을 삭제
  • UriComponentsBuilder 를 통해서 브라우저에서 GET 방식 등의 파라미터 전송에 사용되는 문자열(query string)을 손쉽게 처리할 수 있다.
 

🌼게시물의 수정과 첨부파일

💡핵심 키워드

  • 게시물 수정에서 첨부파일을 수정이라는 개념보다는 삭제 후 다시 추가한다는 개념으로 접근해야 한다.

화면에서 첨부파일 수정

  • 게시물의 수정은 게시물의 조회화면과 유사하지만 다음과 같은 점이 다르다.
    • 원본 이미지 확대나 다운로드 기능이 필요하지 않다.
    • 게시물 조회와 달리 삭제 버튼이 있어야 한다.
  • 첨부파일 처리에서 가장 신경 쓰이는 부분은 사용자가 이미 있던 첨부파일 중에 일부를 삭제한 상태에서 게시물을 수정하지 않고 빠져나가는 상황이다.
    • 만일 사용자가 특정 첨부파일을 삭제했을 때 Ajax를 통해서 업로드된 파일을 삭제하게 되면 나중에 게시글을 수정하지 않고 빠져나갔을 때 파일은 삭제된 상태가 되는 문제가 생긴다.
    • 이를 방지하려면 사용자가 특정 첨부파일을 삭제했을 때 화면에서만 삭제하고, 최종적으로 게시물을 수정했을 때 이를 반영하는 방식을 이용해야 한다.
  • 실제 파일 삭제는 게시물의 수정 버튼을 누르고 처리되는 과정에서 이루어지도록 한다.
  • 실제 게시물의 첨부파일을 수정하는 모든 작업은 서버에서 처리되도록 하므로, 게시물을 수정할 때는 게시물 등록 작업과 가이 모든 첨부파일 정보를 같이 전송해야 한다.

서버 측 게시물 수정과 첨부파일

  • 게시물을 수정랄 때 첨부파일의 처리는 생각보다 복잡한데, 가장 큰 이유는 첨부파일 중에 어떤 파일을 수정했고, 어떤 파일이 삭제되었는지 알아야 하기 때문이다.
    • 이에 대한 처리는 간단한 방법으로 게시물의 모든 첨부파일 목록을 삭제하고, 다시 첨부파일 목록을 추가하는 형태로 처리를 하는것이다.
    • 이 경우 데이터베이스 상에는 문제가 없는데, 실제로 파일이 업로드된 폴더에는 파일이 남아있는 문제가 생긴다. 이에 대한 처리는 주기적으로 파일과 데이터베이스를 비교하는 등의 방법을 활용해서 처리할 수 있다.
    •  


🌼게시물의 조회와 첨부파일

💡핵심 키워드

  • 게시물의 정보는 게시글 테이블에 기록되어 있고, 첨부파일의 정보는 첨부파일 테이블에 기록되어 있디 떄문에 화면에서 두 테이블에 있는 정보를 사용하기 위해서는 다음과 같은 ㅂ아식을 고려할 수 있다.
    • 게시글 VO 객체를 가져올 때 join을 처리해서 한꺼번에 게시글과 첨부파일의 정보를 같이 처리하는 방식
      • 이 경우 데이터베이스를 한 번만 호출하게 되므로 효율적이지만 MyBatis 쪽에서 처리해야 하는 일이 많아진다.
    • JSP에서 첨부파일의 정보를 Ajax를 이용해서 처리하는 방식
      • 다시 쿼리를 처리해야 하는 불편함이 있지만 난이도가 낮고, 화면에서 처리는 JavaScript가 복잡해진다.

BoardService와 BoardController 수정

  • 게시물을 조회할 때 Ajax로 처리하기로 했다면 우선적으로 서버 측에서 JSON 데이터를 만들어서 화면에 올바르게 전송하는 작업을 먼저 처리해야 한다.

BoardController의 변경과 화면 처리

  • Controller가 @RestController로 작성되지 않았다면 직접 @ResponseBody를 적용해서 JSON 데이터를 반환하도록 처리한다.
 

 
  • 게시물을 등록할 때 첨부파일 테이블 역시 같이 insert 작업이 진행되어야 하므로 트랜잭션 처리가 필요하다.
  • 첨부파일 테이블은 UUID가 포함된 이름을 PK로 하는 칼럼, 실제 파일이 업로드 된 경로를 의미하는 칼럼, 파일 이름을 의미하는 칼럼, 이미지 파일 여부를 판단할 수 있는 칼럼, 해당 게시물 번호를 저장하는 칼럼을 이용한다.

등록을 위한 화면 처리

  • 첨부파일 자체의 처리는 Ajax를 통해서 이루어지므로, 게시물의 등록 시점에는 현재 서버에 업로드된 파일들에 정보를 등록하려는 게시물의 정보와 같이 전송해서 처리한다.
  • 파일 업로드는 별도의 업로드 버튼을 두지 않고, <input=type='file'>의 내용이 변경되는 것을 감지해서 처리하도록 한다.
  • 업로드된 파일을 삭제하기 위해서는 파일의 경로와 UUID가 포함된 파일 이름이 필요하므로 <button> 태그에 data-filedata-type 정보를 추가한다.
  • 게시물의 등록 과정에서는 첨부파일의 상세 조회는 의미가 없고, 단순히 새로운 첨부파일을 추가하거나 삭제해서 자신이 원하는 파일을 게시물 등록할 때 같이 포함하도록 한다.
    • 시물이 등록될 때 첨부파일과 관련된 자료를 같이 전송하고, 이를 데이터베이스에 등록한다.
    • 게시물의 등록은 <form> 태그를 통해서 이루어지므로, 이미 업로드된 첨부파일의 정보는 별도의 <input type=’hidden’> 태그를 생성해서 처리한다.
  • 게시물의 등록 작업은 게시글 테이블과 첨부파일 테이블 약쪽 모두 insert가 진행되어야 하기 때문에 트랜잭션 처리가 필요하다.
    • 일반적인 경우하면 오라클의 시퀀스를 이용해서 nextval과 currval을 이용해서 처리하겠지만, MyBatis의 selectkey를 이용할 수도 있다.

첨부파일 정보를 위한 준비

💡핵심 키워드

🌼프로젝트의 첨부파일-등록


🌼게시물의 조회와 첨부파일

💡핵심 키워드

  • 화면에서 게시물과 첨부파일 두 테이블에 있는 정보를 사용하기 위해서는 다음과 같은 방식이 있다.
    • 게시글 객체를 가져올 때 join을 처리해서 한꺼번에 게시물과 첨부파일의 정보를 같이 처리하는 방식
      • 데이터베이스를 한 번만 호출학[ 되므로 효율적이지만 MyBatis 쪽에서 처리해야 하는 일이 많아진다.
    • JSP에서 첨부파일의 정보를 Ajax를 이용해서 처리하는 방식
      • 다시 쿼리를 처리해야 하는 불편함이 있지만 난이도가 낮고, 화면에서 처리는 JavaScript 처리가 복잡하다.

BoardService와 BoardController 수정

  • 게시물을 조회할 때 첨부파일은 Ajax로 처리하기로 했다면 우선적으로 서버 측에서 JSON 데이터를 만들어서 화면에 올바르게 전송하는 작업을 먼저 처리해야 한다.

BoardController의 변경과 화면 처리

  • Controller가 @RestController로 작성되지 않았다면 직접 @ResponseBody를 적용해서 JSON 데이터를 반환하도록 처리한다.
    •  

BoardController, BoardService의 처리

  • 게시물의 등록 작업은 게시글 테이블과 첨부파일 테이블 모두 insert가 진행되어야 하기 때문에 트랜잭션 처리가 필요하다.
  • 일반적인 경우라면 오라클의 시퀀스를 이용해서 nextval과 currval을 이용해서 처리하지만, MyBatis의 selectkey를 이용해서 처리할 수도 있다.
    •  
  • 첨부파일 테이블은 UUID가 포함된 이름을 PK로 하는 칼럼, 실제 파일이 업로드된 경로를 의미하는 칼럼, 파일 이름을 의미하는 칼럼, 이미지 파일 여부를 판단할 수 있는 칼럼, 해당 게시글 번호를 저장하는 칼럼이 필요하다.

등록을 위한 화면 처리

  • 첨부파일 자체의 처리는 Ajax를 이용해서 처리하므로, 게시물의 등록 시점에는 현재 서버에 업로드된 파일들에 정보를 등록하려는 게시물의 정보와 같이 전송해서 처리한다.
    • 이 작업은 게시물의 등록 버튼을 클릭했을 때 현재 서버에 업로드된 파일의 정보를 <input type=’hidden’으로 만들어서 한 번에 전송하는 방식을 사용한다.
  • 첨부파일의 변경은 사실상 업로드의 삭제와 같다.
    • 삭제를 위해서는 업로드된 파일의 경로와 UUID가 포함된 파일 이름이 필요하므로 <button> 태그에 data-filedata-type 정보를 추가한다.
  • 게시물의 등록 과정에서는 단순히 새로운 첨부파일을 추가하거나 삭제해서 자신이 원하는 파일을 게시물 등록할 때 같이 포함하도록 한다.
    • 이미 업로드된 첨부파일의 정보는 별도의 <input type=’hidden’> 태그를 생성해서 처리한다.

💡핵심 키워드

첨부파일의 다운로드

🌼프로젝트의 첨부파일-등록

🌼파일 업로드 방식

💡핵심 키워드

  • 첨부파일을 서버에 전송하는 방식은 크게 두가지가 있다.
    • <form> 태그를 이용하는 방식: 브라우저의 제한이 없어야 하는 경우에 사용한다.
      • 일반적으로 페이지 이동과 동시에 첨부파일을 업로드하는 방식
      • <iframe>을 이용해서 화면의 이동 없이 첨부파일을 이동하는 방식
    • Ajax를 이용하는 방식: 첨부파일을 별도로 처리하는 방식
      • <input type=’file’>을 이용하고 Ajax로 처리하는 방식
      • HTML5의 Drag And Drop 기능이나 jQuery 라이브러리를 이용해서 처리하는 방식
  • 일반적으로는 commons-fileupload API를 이용해서 서버에서 첨부파일을 처리할 수 있고, 서블릿 3.0 이상부터는 자체적인 파일 업로드 처리가 API 상에서 지원된다.

스프링의 첨부파일을 위한 설정

  • web.xml의 <servlet> 태그 내의 <multipart-config>의 설정은 특정 사이즈의 메모리 사용(file-size-threshold), 업로드되는 파일을 임시로 저장할 공간(location), 업로드되는 파일의 최대 크기(max-file-size), 한 번에 올릴 수 있는 최대 크기(max-request-size)를 지정할 수 있다.
  • 이후 스프링의 업로드 처리는 MultipartResolver 타입의 객체를 빈으로 등록해야 가능하다. 이는 Web과 관련된 설정이므로 servlet-context.xml을 이용해서 설정한다.

<form> 방식의 업로드

  • Controller에서 GET 방식으로 첨부파일을 업로드할 수 있는 화면을 처리하는 메서드와, POST 방식으로 첨부파일 업로드를 처리하는 메서드를 작성한다.
  • 스프링 MVC에서는 MultipartFile 타입을 제공해서 업로드되는 파일 데이터를 처리할 수 있다.
    • String getName()
      파라미터의 이름 <input> 태그의 이름
      String getOriginalFileName()
      업로드되는 파일의 이름
      boolean isEmpty()
      파일이 존재하지 않는 경우 true
      long getSize()
      업로드되는 파일의 크기
      byte[] getBytes()
      byte[]로 파일 데이터 반환
      InputStream getInputStream()
      파일데이터와 연결된 InputStream을 반환
      transferTo(File file)
      파일의 저장

Ajax를 이용하는 파일 업로드

  • Ajax를 이용하는 첨부파일 처리는 FormData라는 객체를 이용하는데 IE의 경우 10 이후의 버전부터 지원되므로 브라우저에 제약이 있을 수 있다.
  • Controller에 GET 방식으로 첨부파일을 업로드하는 페이지를 제작한다.
  • jsp에서 순수한 JavaScript를 이용해서 처리할 수도 있지만, jQuery를 이용해서 처리하는 것이 편리하다. 이를 위해서 jQuery cdn을 <script>를 이용해서 라이브러리의 경로를 추가한후 첨부파일을 처리한다.
    • 이 경우 파일 업로드는 FormData라는 객체를 이용한다. 가상의 <form> 태그를 이용해서 필요한 파라미터를 담아서 전송하는 방식이다.
  • 첨부파일 데이터는 fileData를 formData에 추가한 뒤에 Ajax를 통해서 formData 자체를 전송한다. 이 때 processData와 contentType은 반드시 false로 지정해야만 전송된다.

파일 업로드에서 고려해야 하는 점들

  • 동일한 이름으로 파일이 업로드 되었을 때 기존 파일이 사라지는 문제
  • 이미지 파일의 경우에는 원본 파일의 용량이 큰 경우 섬네일 이미지를 생성해야 하는 문제
  • 이미지 파일과 일반 파일을 구분해서 다운로드 혹은 페이지에서 조회하도록 처리하는 문제
  • 첨부파일 공격에 대비하기 위한 업로드 파일의 확장자 제한
 

🌼파일 업로드 상세 처리

💡핵심 키워드

스프링의 첨부파일을 위한 설정

  • 포털에서도 특정한 확장자를 제외한 파일들의 업로드를 제한하는 경우가 많다. 이는 첨부파일을 이용하는 웹 공격을 막기 위해서 행해지는 조치다. 또한 JavaScript를 이용해서 특정 크기 이상의 파일은 업로드할 수 없도록 제한할 수 있다.
  • 첨부파일을 저장할 때 신경 쓰이는 것은 크게 두 가지다.
    • 중복된 이름의 파일 처리의 경우 현재 시간을 밀리세컨드 까지 구분해서 파일 이름을 생성하거나 UUID를 이용해서 중복이 발생할 가능성이 거의 없는 문자열을 생성해서 처리한다.
      • java.util.UUID의 값을 이용해서 동일한 이름의 업로드를 막아 기존 파일을 지우지 않을 수 있다.
    • 한 폴더 내에 너무 많은 파일의 생성 문제의 경우 년/월/일 단위의 폴더를 생성해서 파일을 저장한다.
      • java.io.File에 존재하는 mkdirs()를 이용하면 필요한 상위 폴더까지 한 번에 생성할 수 있다.

섬네일 이미지 생성

  • 용량이 큰 파일을 섬네일 처리하지 않는다면 모바일과 같은 환경에서 많은 데이터를 소비해야만 하므로 이미지의 경우는 특별한 경우가 아니라면 섬네일을 제작해야만 한다.
  • 섬네일을 제작하는 방법은 여러 가지 방식이 있다.
    • JDK1.4부터는 ImageIO를 제공해서 원본 이미지의 크기를 줄일 수 있고, ImgScalr와같은 별도의 라이브러리를 이용하는 방식도 있다.
    • Thumbnailator 라이브러리를 이용하면 이미지를 축소했을 때의 크기나 해상도를 직접 조절하는 작업을 줄일 수 있다.
  • Controller에서는 다음과 같은 단계를 이용해서 섬네일을 생성한다.
    • 업로드된 파일이 이미지 종류의 파일인지 확인
    • 이미지 파일의 경우에는 섬네일 이미지 생성 및 저장
  • Ajax로 사용하는 호출은 반드시 브라우저만을 통해서 들어오는 것이 아니므로 서버에 업로드된 파일은 조금 시간이 걸리더라도 파일 자체가 이미지인지를 정확히 체크한 뒤에 저장하는 것이 좋다.
  • Thunbnailator는 InputStream과 java.io.File 객체를 이용해서 파일을 생성할 수 있고, 뒤에 사이즈에 대한 부분을 파라미터로 width와 height를 지정할 수 있다.

업로드된 파일의 데이터 반환

  • 서버에서 Ajax의 결과로 브라우저에 전송해야 하는 데이터는 다음과 같은 정보를 포함하도록 설계해야 한다.
    • 업로드된 파일의 이름과 원본 파일의 이름
    • 파일이 저장된 경로
    • 업로드된 파일이 이미지인지 아닌지에 대한 정보
  • 이에 대한 모든 정보를 처리하는 방법은 두 가지가 있다.
    • 업로드된 경로가 포함 된 파일 이름을 반환하는 방식은 브라우저에서 해야 하는 일이 많다.
    • 별도의 객체를 생성해서 처리하는 방법을 고려하는 것이 편하다.
  • Ajax를 호출했을 때의 결과 타입(dataType)은 json으로 변경한다.
 

🌼브라우저에서 섬네일 처리

💡핵심 키워드

  • 브라우저에서 첨부파일의 업로드 결과가 JSON 객체로 반환되었다면, 남은 작업은 다음과 같다.
    • 업로드 후에 업로드 부분을 초기화 시키는 작업
    • 결과 데이터를 이용해서 화면에 섬네일이나 파일 이미지를 보여주는 작업

<input type=’file’>의 초기화

  • <input type=’file’>은 다른 DOM 요소들과 다르게 readonly라 안쪽의 내용을 수정할 수 없기 때문에 별도의 방법으로 초기화 시켜서 또 다른 첨부파일을 추가할 수 있도록 만들어야 한다.
  • 첨부파일을 업로드라기 전에 아무 내용이 없는 <input type=’file’>객체가 포함된 <div>를 복사(clone)한다. 첨부파일을 업로드한 뒤에는 복사된 객체를 <div>내에 다시 추가해서 첨부파일 부분을 초기화한다.

업로드된 이미지 처리

  • 서버에서 섬네일은 GET 방식을 통해서 가져올 수 있도록 처리한다. 특정한 URI 뒤에 파일 이름을 추가하면 이미지 파일 데이터를 가져와서 <img> 태그를 작성하는 과정을 통해서 처리한다.
  • 이 때 경로나 파일 이름에 한글 혹은 공백 등의 문자가 들어가면 문제가 발생할 수 있으므로 JavaScript의 encodeURIComponent() 함수를 이용해서 URI에 문제가 없는 문자열을 생성해서 처리한다.
 

🌼첨부파일의 다운로드 혹은 원본 보여주기

💡핵심 키워드

  • 브라우저에서 보이는 첨부파일은 크게 이미지 종류와 일반 파일로 구분되므로 사용자의 첨부파일과 관련된 행위도 종류에 따라 다르게 처리되어야 한다.
  • 첨부파일이 이미지인 경우에는 섬네일 이미지를 클릭했을 때 화면에 크게 원본 파일을 보여주는 형태로 처리되어야 한다.
    • 이 경우는 브라우저에서 새로운 <div> 등을 생성해서 처리하는 방식을 이용하는데 흔히 light-box라고 한다. light-box는 jQuery를 이용하는 많은 플러그인들이 있으므로, 이를 활용하거나 직접 구현할 수 있다.
  • 첨부파일이 이미지가 아닌 경우에는 사용자가 파일을 선택하면 다운로드가 실행되면서 해당 파일의 이름으로 다운로드가 가능해야 한다.

첨부파일의 다운로드

  • 첨부파일의 다운로드는 서버에서 MINE 타입을 다운로드 타입으로 지정하고, 적절한 헤더 메시지를 통해서 다운로드 이름을 지정하게 처리한다.
  • MINE 타입은 다운로드를 할 수 있는 application/octet-stream으로 지정하고, 다운로드 시 저장되는 이름은 Content-Disposition을 이용해서 지정한다.
  • IE의 경우 HttpServletRequest에 포함된 헤더 정보들을 이용해서 요청이 발생한 브라우저가 IE 계열인지 확인하고, Content-Disposition의 인코딩 방식을 다르게 처리한다.

원본 이미지 보여주기

  • 원본 이미지를 화면에서 보기 위해서는 <div>를 생성하고, 해당 <div>에 이미지 태그를 작성해서 넣어주는 작업과 이를 화면상에서 절대 위치를 이용해서 보여줄 필요가 있다.
  • CSS의 flex 기능을 이용하면 화면의 정중앙에 요소를 배치할 수 있다.
  • JavaScript의 애로우 펑션(⇒)은 크롬에서는 정상 작동하지만, IE에서는 제대로 동작하지 않으므로 필요하다면 코드의 변경을 고려한다.

첨부파일 삭제

  • 첨부파일 삭제는 다음과 같은 문제점들을 고민해야 한다.
    • 이미지 파일의 경우에는 섬네일까지 같이 삭제되어야 하는 점
    • 파일을 삭제한 후에는 브라우저에서도 섬네일이나 파일 아이콘이 삭제되도록 처리하는 점
    • 비정상적으로 브라우저의 종료 시 업로드된 파일의 처리
  • 업로드된 첨부파일의 삭제는 Ajax를 이용하거나 <form> 태그를 이용하는 방식 모두를 적용할 수 있다.
    • 첨부파일의 삭제는 <span> 태그를 이용해서 처리하지만, 첨부파일의 업로드 후에 생성되기 때문에 이벤트 위임 방식으로 처리해야 한다.
    • Ajax를 이용해서 첨부파일의 경로와 이름, 파일의 종류를 전송할 수 있다.
  • 이미 업로드된 첨부파일의 삭제는 일반 파일의 경우에는 업로드된 파일만을 삭제하면 되지만, 이미지의 경우에는 생성된 섬네일 파일과 원본 파일을 같이 삭제해야 한다.
  • 첨부파일을 삭제하는 작업의 최대 고민은 사용자가 비정상적으로 브라우저를 종료하고 나가는 행위다.
    • 이에 대한 가장 좋은 해결책은 실제 최종적인 결과와 서버에 업로드된 파일의 목록을 비교해서 처리하는 것이다. 보통 spring-batchQuartz 라이브러리를 이용해서 처리한다.
 

🌼프로젝트의 첨부파일-등록

💡핵심 키워드

첨부파일의 다운로드

  • 첨부파일 테이블은 UUID가 포함된 이름을 PK로 하는 칼럼, 실제 파일이 업로드된 경로를 의미하는 칼럼, 파일 이름을 의미하는 칼럼, 이미지 파일 여부를 판단할 수 있는 칼럼, 해당 게시글 번호를 저장하는 칼럼이 필요하다.

등록을 위한 화면 처리

  • 첨부파일 자체의 처리는 Ajax를 이용해서 처리하므로, 게시물의 등록 시점에는 현재 서버에 업로드된 파일들에 정보를 등록하려는 게시물의 정보와 같이 전송해서 처리한다.
    • 이 작업은 게시물의 등록 버튼을 클릭했을 때 현재 서버에 업로드된 파일의 정보를 <input type=’hidden’으로 만들어서 한 번에 전송하는 방식을 사용한다.
  • 첨부파일의 변경은 사실상 업로드의 삭제와 같다.
    • 삭제를 위해서는 업로드된 파일의 경로와 UUID가 포함된 파일 이름이 필요하므로 <button> 태그에 data-filedata-type 정보를 추가한다.
  • 게시물의 등록 과정에서는 단순히 새로운 첨부파일을 추가하거나 삭제해서 자신이 원하는 파일을 게시물 등록할 때 같이 포함하도록 한다.
    • 이미 업로드된 첨부파일의 정보는 별도의 <input type=’hidden’> 태그를 생성해서 처리한다.

BoardController, BoardService의 처리

  • 게시물의 등록 작업은 게시글 테이블과 첨부파일 테이블 모두 insert가 진행되어야 하기 때문에 트랜잭션 처리가 필요하다.
  • 일반적인 경우라면 오라클의 시퀀스를 이용해서 nextval과 currval을 이용해서 처리하지만, MyBatis의 selectkey를 이용해서 처리할 수도 있다.
    •  

🌼게시물의 조회와 첨부파일

💡핵심 키워드

  • 화면에서 게시물과 첨부파일 두 테이블에 있는 정보를 사용하기 위해서는 다음과 같은 방식이 있다.
    • 게시글 객체를 가져올 때 join을 처리해서 한꺼번에 게시물과 첨부파일의 정보를 같이 처리하는 방식
      • 데이터베이스를 한 번만 호출학[ 되므로 효율적이지만 MyBatis 쪽에서 처리해야 하는 일이 많아진다.
    • JSP에서 첨부파일의 정보를 Ajax를 이용해서 처리하는 방식
      • 다시 쿼리를 처리해야 하는 불편함이 있지만 난이도가 낮고, 화면에서 처리는 JavaScript 처리가 복잡하다.

BoardService와 BoardController 수정

  • 게시물을 조회할 때 첨부파일은 Ajax로 처리하기로 했다면 우선적으로 서버 측에서 JSON 데이터를 만들어서 화면에 올바르게 전송하는 작업을 먼저 처리해야 한다.

BoardController의 변경과 화면 처리

  • Controller가 @RestController로 작성되지 않았다면 직접 @ResponseBody를 적용해서 JSON 데이터를 반환하도록 처리한다.
    •  

🌼게시물의 삭제와 첨부파일

💡핵심 키워드

  • 게시물을 삭제할 때는 게시물에 포함된 첨부파일 역시 삭제할 필요가 있다.
  • 단순히 데이터베이스 상에서 삭제만 이뤄지는 게 아니라 실제 폴더 내의 파일도 같이 삭제할 필요가 있기 때문에 작업의 순서 역시 신경 써야 한다.
    • 폴더에서의 파일 삭제는 위험한 작업이기 때문에 가능하면 뒤쪽으로 미루고 먼저 데이터베이스의 삭제 작업을 처리한 후 실제 파일을 삭제한다.

첨부파일 삭제 처리

  • 첨부파일의 삭제와 실제 게시물의 삭제가 같이 처리되도록 Service에서 @Transactional을 선언해야 한다.
  • 게시물과 첨부파일 삭제 작업의 순서는 다음과 같다.
    • 해당 게시물의 첨부파일 정보를 미리 준비
    • 데이터베이스 상에서 해당 게시물과 첨부파일 데이터 삭제
    • 첨부파일 목록을 이용해서 해당 폴더에서 섬네일 이미지(이미지 파일의 경우)와 일반 파일을 삭제
  • UriComponentsBuilder는 브라우저에서 GET 방식 등의 파라미터 전송에 사용되는 문자열(query string)을 손쉽게 처리할 수 있는 클래스다.
    •  

🌼게시물의 수정과 첨부파일

💡핵심 키워드

  • 게시물을 수정할 때 첨부파일과 관련된 작업은 사실상 게시물 등록 작업과 상당히 유사하다. 첨부파일이라는 개념 자체가 수정이 아닌 기존 파일을 삭제하고, 새로운 파일을 추가하기 때문이다.
  • 게시물의 수정에는 기존의 게시물 테이블을 수정하는 작업과 변경(새롭게 추가된)된 첨부파일을 등록하는 작업으로 이루어진다.

첨부파일 데이터 보여주기

  • 게시물의 수정은 게시물의 조회 화면과 유사하지만 다음과 같은 차이점이 있다.
    • 원본 이미지 확대나 다운로드 기능이 필요하지 않다
    • 게시물 조회와 달리 삭제 버튼이 있어야 한다.
  • 첨부파일 처리에서 가장 신경 쓰이는 부분은 사용자가 이미 있던 첨부파일 중에 일부를 삭제한 상태에서 게시물을 수정하지 않고 빠져나가는 상황이다.
    • 만약 사용자가 특정 첨부파일을 삭제했을 때 Ajax를 통해서 업로드된 파일을 삭제하게 되면 나중에 게시물을 수정하지 않고 빠져나갔을 때 파일은 삭제된 상태가 되는 문제가 생긴다.
    • 이를 방지하려면 사용자가 특정 첨부파일을 삭제했을 때 화면에서만 삭제하고, 최종적으로 게시물을 수정했을 때 이를 반영하는 방식을 이용해야 한다.
  • 실제 파일 삭제는 게시물의 수정 버튼을 누르고 처리되는 과정에서 이루어지도록 한다.
  • 실제 게시물의 첨부파일을 수정하는 모든 작업은 서버에서 처리되도록 하므로, 게시물을 수정할 때는 게시물 등록 작업과 같이 모든 첨부파일 정보를 같이 전송해야 한다.

서버 측 게시물 수정과 첨부파일

  • 게시물을 수정할 때 첨부파일의 처리는 생각보다 복잡하다. 가장 큰 이유는 기존의 첨부파일 중에 어떤 파일을 수정했고, 어떤 파일이 삭제되었는지 알아야 하기 때문이다.
  • 이에 대한 처리는 우선 간단한 방법으로 게시물의 모든 첨부파일 목록을 삭제하고, 다시 첨부파일 목록을 추가하는 형태로 처리를 하는 것이다.
    • 이 경우 데이터베이스 상에는 문제가 없는데, 실제로 파일이 업로드된 폴더에는 삭제된 파일이 남아있는 문제가 생긴다.
    • 이에 대한 처리는 추기적으로 파일과 데이터베이스를 비교하는 등의 방법을 활용해서 처리할 수 있다.
 

🌼잘못 업로드된 파일 삭제

💡핵심 키워드

  • Ajax를 이용해서 첨부파일을 사용하면 사용자가 게시물을 등록하거나 수정하기 전에 미리 업로드시킨 파일들을 볼 수 있다는 장점이 있지만, 다음과 같은 문제가 생긴다
    • 첨부파일만을 등록하고 게시물을 등록하지 않았을 때의 문제
      • 파일은 이미 서버에 업로드되었지만, 게시물을 등록하지 않았으므로 의미 없이 파일들만 서버에 업로드된 상황
    • 게시물을 수정할 때 파일을 삭제했지만 실제로 폴더에서 기존 파일은 삭제되지 않은 문제
      • 데이터베이스에는 기존 파일이 삭제되었지만, 실제 폴더에는 남는 문제
  • 이 문제를 해결하는 핵심은 정상적으로 사용자의 게시물에 첨부된 파일인지 아닌지를 파악하는 것이 핵심이다.

잘못 업로드된 파일의 정리

  • 게시물에 필요한 모든 파일에 대한 정보는 최종적으로는 데이터베이스레 기록되어 있다.
  • 만일 정상적으로 해당 첨부파일이 게시물에 사용되었다면 데이터베이스의 첨부파일 테이블에 기록되어 있을 것이므로 데이터베이스와 비교하는 작업을 거쳐 업로드된 파일의 목록을 찾아야 한다.
  • 파일의 목록을 찾을 때는 반드시 오늘 날짜가 아닌 파일들을 대상으로 해야만 한다.
    • 만일 오늘 날짜를 대상으로 하는 경우 지금 현재 게시물을 작성하거나 수정하기 위해서 업로드하고 있는 파일들을 삭제할 가능성이 있기 때문이다.
  • 이 작업은 주기적으로 동작해야 하므로 스케줄링을 할 수 있는 Spring-BatchQuartz 라이브러리를 이용한다.

Quartz 라이브러리 설정

  • 서버를 운영하기 위해서는 간혹 주기적으로 매일, 매주, 매월 등 특정한 프로그램을 실행할 필요가 있다.
    • 이 작업은 스프링과 Quartz 라이브러리를 이용하면 간단히 처리할 수 있다.
  • Quartz에 대한 설정은 XML과 어노테이션을 활용할 수 있다.
    • @Scheduled 어노테이션 내에는 cron 이라는 속성을 부여해서 주기를 제어할 수 있다.

cron 설정과 삭제 처리

  • cron 속성은 첫째 자리부터 초, 분, 시, 일, 월, 주, 년(생략 가능)의 값을 지정할 수 있다.
  • 파일 목록 처리 작업의 순서는 다음과 같다.
    • Mapper를 이용해서 어제 날짜로 보관되는 모든 첨부파일의 목록을 가져온다.
      • 데이터베이스에서 가져온 파일 목록은 VO 타입의 객체이므로, 비교를 위해서 java.nio.Paths의 목록으로 변환한다.
      • 이 떄 이미지 파일의 경우에는 섬네일 파일도 목록에 필요하기 때문에 별도로 처리해서 해당 날짜의 예상 파일 목록을 완성한다.
    • 해당 폴더의 파일 목록에서 데이터베이스에 없는 파일을 찾아낸다.
      • 실제 폴더에 있는 파일들의 목록에서 데이터베이스에는 없는 파일들을 찾아서 목록으로 만든다.
    • 이후 데이터베이스에 없는 파일들을 삭제한다.
 

🏁결론

해당 내용을 정리하면서 첨부파일을 별도로 업로드해서 사용자가 최종적으로 게시물을 등록하기 전에 자신이 어떤 파일들을 업로드하는지, 알 수 있는 방식을 연습할 수 있었다.
또한 첨부파일에 대한 처리는 업로드된 이루에 처리가 복잡하다는 것을 알게 되었다.
Share article

More articles

See more posts
RSSPowered by inblog