[코드로 배우는 스프링 웹 프로젝트 개정판] 6장 정리
첨부파일 업로드는 사용자가 게시물을 등록하기 전에 업로드한 파일을 확인할 수 있게 해준다. 첨부파일 처리는 복잡하며, 업로드된 파일의 초기화, 업로드된 이미지 처리, 다운로드, 원본 이미지 보여주기, 첨부파일 삭제 등을 포함한다. 게시물 수정 시 첨부파일 처리는 기존 파일 삭제 및 새 파일 추가를 포함한다. 잘못 업로드된 파일 삭제는 주기적으로 실행되어야 하며, 이는 `Spring-Batch`나 `Quartz` 라이브러리를 통해 처리할 수 있다.
Mar 26, 2024
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-file
과data-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-file
과data-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-batch
나Quartz
라이브러리를 이용해서 처리한다.
🌼프로젝트의 첨부파일-등록
💡핵심 키워드
첨부파일의 다운로드
- 첨부파일 테이블은 UUID가 포함된 이름을 PK로 하는 칼럼, 실제 파일이 업로드된 경로를 의미하는 칼럼, 파일 이름을 의미하는 칼럼, 이미지 파일 여부를 판단할 수 있는 칼럼, 해당 게시글 번호를 저장하는 칼럼이 필요하다.
등록을 위한 화면 처리
- 첨부파일 자체의 처리는 Ajax를 이용해서 처리하므로, 게시물의 등록 시점에는 현재 서버에 업로드된 파일들에 정보를 등록하려는 게시물의 정보와 같이 전송해서 처리한다.
- 이 작업은 게시물의 등록 버튼을 클릭했을 때 현재 서버에 업로드된 파일의 정보를
<input type=’hidden’
으로 만들어서 한 번에 전송하는 방식을 사용한다.
- 첨부파일의 변경은 사실상 업로드의 삭제와 같다.
- 삭제를 위해서는 업로드된 파일의 경로와 UUID가 포함된 파일 이름이 필요하므로
<button>
태그에data-file
과data-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-Batch
나Quartz
라이브러리를 이용한다.
Quartz 라이브러리 설정
- 서버를 운영하기 위해서는 간혹 주기적으로 매일, 매주, 매월 등 특정한 프로그램을 실행할 필요가 있다.
- 이 작업은 스프링과 Quartz 라이브러리를 이용하면 간단히 처리할 수 있다.
- Quartz에 대한 설정은 XML과 어노테이션을 활용할 수 있다.
@Scheduled
어노테이션 내에는cron
이라는 속성을 부여해서 주기를 제어할 수 있다.
cron 설정과 삭제 처리
- cron 속성은 첫째 자리부터 초, 분, 시, 일, 월, 주, 년(생략 가능)의 값을 지정할 수 있다.
- 파일 목록 처리 작업의 순서는 다음과 같다.
- Mapper를 이용해서 어제 날짜로 보관되는 모든 첨부파일의 목록을 가져온다.
- 데이터베이스에서 가져온 파일 목록은 VO 타입의 객체이므로, 비교를 위해서
java.nio.Paths
의 목록으로 변환한다. - 이 떄 이미지 파일의 경우에는 섬네일 파일도 목록에 필요하기 때문에 별도로 처리해서 해당 날짜의 예상 파일 목록을 완성한다.
- 해당 폴더의 파일 목록에서 데이터베이스에 없는 파일을 찾아낸다.
- 실제 폴더에 있는 파일들의 목록에서 데이터베이스에는 없는 파일들을 찾아서 목록으로 만든다.
- 이후 데이터베이스에 없는 파일들을 삭제한다.
🏁결론
해당 내용을 정리하면서 첨부파일을 별도로 업로드해서 사용자가 최종적으로 게시물을 등록하기 전에 자신이 어떤 파일들을 업로드하는지, 알 수 있는 방식을 연습할 수 있었다.
또한 첨부파일에 대한 처리는 업로드된 이루에 처리가 복잡하다는 것을 알게 되었다.
Share article