오늘은 게시판 만들기 복습 과제를 진행하면서 새롭게 알게된 내용들을 정리해본다.
게시물 작성에서 입력글자수 제한에 대한 요구사항이 있었다.
제목은 500자, 내용은 5000자 제한이었다.
처음에는 RequestDto에 max =500
으로 작성하면 끝일 줄 알았다.
@Getter
public class PostRequestDto {
@Size(max = 500, message = "제목 500자 이하")
private String title;
@Size(max = 5000, message = "작성 내용 5000자 이하")
private String content;
}
하지만 제목에 500자 이상을 작성했을 때 무려 status 500이라는 서버 에러를 만들어냈다.
글자수 제한을 넘었으면 status 400이어야하는게 아닌가!? 라고 생각했다.
그리고 잘 작성될 것이라 생각하며 400글자도 작성해봤다. 하지만 포스트맨은 예상과 달리 똑같이 500 서버에러를 띄웠다.
400자에 대한 에러코드를 확인해봤을 때는 다음과 같았다.
쿼리문은 내가 작성한 코드와 같이,
로 잘 나타났다.
하지만 DB에는 저장이 되지 않는 문제였다. 그렇다면 DB와의 뭔가 문제가 있다는 얘기인데, 그 밑의 오류 내용을 확인해보았다.
SqlExceptionHelper : SQL Error: 1406, SQLState: 22001
SqlExceptionHelper : Data truncation: Data too long for column 'title' at row 1
SqlExceptionHelper가 발생한 입력한 title이 너무 길다는 메세지..!
500자 이하로 넣어줬는데 왜 여기서 너무 크다는걸까? 라고 생각하며 SQL Error: 1406, SQLState: 22001
를 검색해봤다.
그랬더니 "해당 컬럼에 정의된 최대 길이를 초과"에 대한 내용이 나왔다.
DB의 컬럼에는 생성 될 때 크기가 정해져있고, 명시하지 않을 시 default값인 255로 고정되어 있었던 것이다! 평소 알아차릴 수 없어 내가 간과하고 있던 사실이었다.
따라서 Post 엔터티의 속성에 다음과 같이 크기에 대한 옵션을 넣어주니 오류가 해결되었다
@Column(length = 500)
private String title;
@Column(length = 5000)
private String content;
이번 오류를 해결하면서 느낀 것은 '이래서 테스트코드가 꼭 필요하겠구나..!' 였다.
내가 간과한 부분을 테스트를 통해 잡아내자!
요구사항에 이미지 파일 업로드가 있었다.
파일..? 그동안은 Json형태로 데이터를 주고받았기에 파일 업로드는 생소했다.
이미지 파일은 어떻게 전송해야하는 것이지?, 이미지는 어떤 형태로 저장되는 것이지? 등 알고 있던 것이 아무것도 없었기 때문에 폭풍 검색을 진행하며 해결했다.
우선 이미지 전송에는 multipart/form-data
라는 방법이 주로 사용된다고 했다.
이것은 웹 폼에서 파일 업로드를 지원하기 위한 데이터 전송 방식 중 하나로, HTML 폼 요소에서 사용자가 파일을 선택하고 제출할 때 주로 사용된다.
특징으로는 폼 데이터를 여러 부분으로 분할하여 전송하며, 각 부분은 다른 종류의 데이터를 포함 할 수 있다는 것이다.
이 각 부분이란 의미는 폼에서 여러 필드와 파일을 업로드할 때 (헤더-데이터로 이루어진) 각 필드 또는 파일은 개별적인 부분으로 표현된다는 것이다.
파일이 담기는 것을 볼 수 있다!
그럼 이 요청을 받아올 때는 어떻게 해야하지?
Java에서 파일 업로드를 처리하기 위한 인터페이스로 MultipartFile이 있으며, @RequestParam("file") MultipartFile file
의 형태로 받아올 수 있다고 한다.
근데 @RequestParam이라니 굉장히 익숙한데!? url에 담겨있는 정보를 가져올 때 사용하던 것이 아닌가!
하지만 포스트맨의 url을 확인해봤을 때 'Params'에 작성하는 것과는 달리 'Body'에 있는 'form-data' 라는 곳에 작성했다. 이게 어떻게 된 것 일까?
@RequestParam("file") MultipartFile file
을 사용하는 경우에는 파일 데이터가 URL에 직접적으로 포함되지 않습니다.
대신, 클라이언트에서 multipart/form-data를 사용하여 HTTP 요청을 보낼 때, 파일 데이터는 요청 본문(body)에 담겨서 전송됩니다.
이렇게 하면 파일이 URL에 직접적으로 노출되지 않고, 요청 본문에 포함되어 서버로 전송됩니다.
url에 직접 전송되지 않지만 @RequestParam으로 받는 방법도 있었다니! 새롭다.
이 부분을 채택하고, form-data은 '각 부분' 이라고 했으므로 이전처럼 Dto 형태로 Json데이터를 보내는게 불가능하기 때문에 다음과 같이 title과 content를 따로 명시해서 받아줬다.
@PostMapping("/image")
public ResponseEntity<CommonResponseDto> createPostWithImage(
@RequestParam("image")MultipartFile imageFile,
@RequestParam("title") String title,
@RequestParam("content") String content,
@CookieValue(JwtUtil.AUTHORIZATION_HEADER) String value) {
postService.createPostWithImage(imageFile,title, content, value);
return ResponseEntity.ok().body(
new CommonResponseDto("게시되었습니다.", HttpStatus.CREATED.value())
);
}
그렇다면 받아온 image file은 어떻게 처리해야할까?
이미지는 이미지는 이진 데이터로 구성되어 있다.
'byte[]' 는 이진 데이터를 표현하기에 적합한 자료형이다.
따라서 받아온 image를 byte[]로 변환하는 메서드를 작성하고, 변환된 byte[] 형태의 이미지 데이터를 저장했다.
private byte[] imageToByteArray(MultipartFile imageFile) {
try {
return imageFile.getBytes();
} catch (IOException e) {
throw new RuntimeException("이미지 변환 실패.");
}
}
...
byte[] imageByte = imageToByteArray(imageFile);
Post post = new Post(title, content,imageByte,user);
postRepository.save(post);
...
검색하면서 알게 된 내용인데,
@Lob은 Large Object를 나타내는 JPA 어노테이션이다.
Large Object란 데이터베이스에 큰 크기의 데이터를 저장하기 위한 데이터 타입을 의미하며, 주로 문자열이나 이진 데이터를 나타내는 필드에 사용된다.
그리고 더불어 @Column
에 columnDefinition = "LONGBLOB"
를 사용했다.
LONGBLOB 옵션은 MySQL 데이터베이스에서 사용되는 데이터 타입으로, 이진 데이터를 저장하는 데 사용된다. 이 데이터 타입은 BLOB (Binary Large Object) 중에서도 매우 큰 크기의 이진 데이터를 저장할 때 사용.
이 둘을 적용하여 아래와 같이 엔터티의 컬럼을 작성해줬다.
...
@Lob
@Column(name = "image", columnDefinition = "LONGBLOB")
private byte[] image;
...
결과는 성공적이었다!!
DB에는 이런식으로 저장이 되는구나~
다만 주의할 점은, 대부분의 경우 이미지 파일은 파일 시스템에 저장하고 데이터베이스에는 파일의 경로나 URL을 저장하는 것이 효율적일 수 있는 것이다.
LOB 형태로 저장하는 경우 성능에 영향을 줄 수 있기 때문이다.
이번 요구사항에는 그러한 내용이 없었기에 따로 파일 시스템을 두진 않았으나, 다음에는 도전해봐야겠다.