현재 MultiPartFile을 이용한 게시물 수정 시 이미지 변경 로직을 짜고 있다
S3에 업로드하는 메소드에서 'MultipartFile -> File convert fail' 이라는 것이 떴다
이 부분에서 에러가 난다
요청은 아래 사진처럼 했다
수정하는 이미지의 위치를 맞춰주기 위해 imageUrl과 image에서 아무것도 입력 안한 것이 있다
게시물 작성 때 사진을 2개 올리고, 두번째 위치에 있는 사진을 수정하려고 한다
그래서 두번째 위치에 있던 기존 이미지 URL은 빈칸으로 두고,
image의 두번째 위치에 새로 올린 이미지파일을 첨부해줬다(춘식3.png)
첫번째 사진은 수정을 안할거기 때문에 첫번째 imageUrl에 기존 이미지 링크를 적어뒀고,
첫번째 image는 빈칸으로 두었다
디버그를 찍어보니 빈칸으로 request한 것들은 null이 아니었다
imageUrl은 null이 아닌 "",
image는 null이 아니고, filename에서 "" 로 들어가있었다
imageUrl이나 image가 null일 때 로직을 실행하도록 만들어놨기 때문에 위와 같은 에러가 났던 것이다
슬랙에 질문을 남김
- 현재 하려는것: 업로드한 이미지들을 수정하기 위해, 먼저 기존 RECIPE_IMAGE테이블에서 특정 Recipe에 해당하는 이미지들을 모두 삭제를 하고 새로운 사진을 INSERT하고자 합니다.
- 구체적인 문제상황:
- RECIPE_IMAGE테이블에 RECIPE_ID가 1에 해당하는 row들이 5개가 있음(총 5장의 사진이 등록된 상황).
- RECIPE_ID가 1인 게시물에 새 사진 2장으로 수정하려고 함
- 실행해보면 기존 사진이 삭제가 되지 않고(delete쿼리가 날라가지 않음), insert만 되어서 사진이 총 7장이 쌓임.
- 이미지수정메서드 코드
@Transactional
public Recipe updateRecipe(Long recipeId,PostRecipeRequestDto requestDto, UserDetailsImpl userDetails) throws IOException {
//게시글 존재여부확인
Recipe recipe = recipeRepository.findById(recipeId).orElseThrow(()->new CustomErrorException("해당 게시물을 찾을 수 없습니다"));
//S3에 있는 사진 삭제
for(int i=0; i<recipe.getRecipeImagesList().size();i++){
RecipeImage recipeImage = recipe.getRecipeImagesList().get(i);
if(recipeImage!= null) deleteS3(recipeImage.getImage());
}
//S3에 이미지 업로드
List<String> imageUrlList= uploadManyImagesToS3(requestDto, "recipeImage");
//DB의 recipe_image 기존 row들 삭제(그냥 update하면 더 작은 개수로 image업뎃할때 outOfInedex에러남)
recipeImageRepository.deleteAllByRecipe(recipe);
List<RecipeImage> recipeImageList = new ArrayList<>();
imageUrlList.forEach((image)->recipeImageList.add(new RecipeImage(image,recipe)));
recipeImageRepository.saveAll(recipeImageList); return recipe;
}
- 해결:
- 기존 Recipe엔티티:
- 케스케이드 옵션을 가지고 있음. (참조무결성오류 방지)
- 기존 Recipe엔티티:
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
@JsonBackReference
private List<RecipeImage> recipeImagesList;
- 기존 RecipeImage엔티티:
@Getter
@NoArgsConstructor
@Entity
public class RecipeImage extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "image_id")
private Long id;
private String image;
@ManyToOne(fetch = FetchType.LAZY)
@JsonManagedReference
@JoinColumn(name = "recipe_id")
private Recipe recipe;
- 수정한 엔티티:
- 다음처럼(cascade = CascadeType.REMOVE) 수정하니 해결이 되었습니다.(사실은 cascade옵션 아무것도 안 써도 해당문제는 해결은 되나, 참조무결성때문에 넣었음)
@OneToMany(mappedBy = "recipe", cascade = CascadeType.REMOVE)
@JsonBackReference
private List<RecipeImage> recipeImagesList;
- 궁금한것:
- 블로그들을 짜집해서 이해한결과,, CascadeType.ALL로 설정하면 트랜잭션 끝날때(flush하는거 맞죠?) recipeImageList에 있는 recipeImage에 대해서 persist연산을 수행하기 때문에 delete쿼리가 작동하지 않는 것 같긴 한데요, 이해가 잘 되지 않습니다....
일단 CasacadeType에 대한 정확한 이해가 없이는 사용하는 것을 저는 비추천 합니다!! (사이드 이펙트가 많이 발생할거에요!!) 해당 문제도 마찬가지이구요!!
일단 저의 뇌피셜은 하나의 transaction에서 remove와 insert가 같이 진행되는 것으로 보입니다!! 그러면 영속성 컨텍스트에서 이미지들이 지워지지 않았기 때문에 여전히 남아있습니다!! 그리고 다시 신규 이미지를 두 개 넣어버리면 7개가 들어갑니다!!!
즉, 하나의 트랜잭션에서 관리가 2가지 작업이 일어나고 있기 때문에 이러한 현상이 발생한 것인데요!! 영속성 컨텍스트에서 이미지들을 없애야 이런 장애가 발생하지 않을 것입니다. 그래서 remove를 하거나 아무것도 적용하지 않았을 때 정상작동하는 것이구요!!
그래서 저는 이럴 때는 orphanremoval를 사용하는 것을 보다 추천드립니다!! + 양방향 관계를 하지 않아도 이런 문제가 없을 것으로 보입니다!!
아래의 글을 읽어보시는 것을 추천드립니다!!
나도 위의 내용에 해당된다
어차피 질문한 사람이 같은 팀원이기도 하고, 나와 저 팀원분은 결국 같은 게시판 기능을 만들고 있기 때문이다
사용자가 게시물 작성 시 여러 장의 이미지를 올리고, 게시물 수정 시 여러장 이미지 중 하나만 삭제하고 수정 완료를 한다면, DB에서 한 행의 데이터를 삭제해야 한다
그런데, 나도 위의 내용처럼 CascadeType.ALL이 걸려있다
@Getter
@NoArgsConstructor
@Entity
public class BoardImage extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "image_id")
private Long id;
private int location;
private String image;
@ManyToOne(fetch = FetchType.LAZY)
@JsonManagedReference
@JoinColumn(name = "board_id")
private Board board;
//Board.java
@OneToMany(mappedBy = "board", cascade = CascadeType.ALL)
@JsonBackReference
private List<BoardImage> boardImageList;
현재 CascadeType.ALL 때문에 한 행이 삭제가 되지 않은 채 "수정완료"라는 response를 받았다
추가로 질문이 있는데요..! 그럼 서로 참조를 해야만 양방향 관계를 설정해야 하는가에 대한 질문입니다.
서로 참조를 하지 않기에 단방향으로만 했을 경우, 예를들어 레시피-댓글 관계가 있다 치면요..!
레시피-댓글 관계에서 단방향으로만 관계를 설정한 경우엔 1번 레시피를 삭제 한경우 1번 레시피에 대한 댓글들도 함께 삭제되지 않으면 DataIntegrityViolationException 이 발생하는데요..!
이런 경우를 위해서라도 양방향으로 설정하고 cascadetype 또는 orphanRemoval = true 를 설정해줘야 하지 않을까요?? 만약 그렇지 않으면 해당 댓글들을 삭제해주는 쿼리를 또 작성해줘야 할 것 같아서요..! 어떤 방법을 더 선호하시나요??
서로 참조를 해야만 양방향 관계를 설정해야하나?? 라는 질문에는 정답이 없습니다!! 어떤 방식을 사용하던 잘 사용하는 것이 중요하다고 생각합니다!!!그리고 해당 문제는 다음과 같은 옵션들이 있다고 생각합니다!!!
- 정책적으로 댓글이 달린 레시피는 사용자 마음대로 삭제할 수 없다 라는 정책을 가진다.
- soft delete를 한다.
- orphanRemoval를 사용한다.(고아 객체 제거)
이중에서 가장 많이 사용되는 방식이 soft delete를 하는 방법을 가장 많이 사용하는 것으로 알고 있습니다!!
(Soft Delete는 delete 쿼리를 날리지 않고 flag 값으로 처리하는 것을 의미)
댓글들 같은 경우 보통 여러가지 법적 이유도 있고해서 DB에서 완전히 삭제를 하지 않습니다.
(일정시간이 지난후 삭제)
* 고아객제(orphan): 연관관계가 끊어진 자식 엔티티
MultipartFile을 Spring에서 다룰 때
request로 들어온 파일의 이름을 알아내고 싶었음 : getName()
파일의 크기 : getSize()
[Page<Entity> -> Page<Dto>로 변환하는 방법]
페이지를 유지하면서 엔티티를 DTO로 변환하기!
Page<Member> page = memberRepository.findByAge(10,pageRequest);
Page<MemberDto> dtoPage = page.map(m -> new MemberDto(m.getId(), m.getUsername(), null));
위의 코드를 보고 나는 이렇게 사용했다
Page<Board> boardList = boardRepository.findAll(pageable);
Page<GetBoardResponseDto> responseDtoList = boardList.map(board -> new GetBoardResponseDto(
board.getId(), board.getUser().getNickname(), board.getTitle(), board.getContent(),
board.getBoardImageList().get(0).getImage(), board.getRegDate(), board.getBoardCommentList().size(),
board.getBoardLikesList().size(), board.getBoardLikesList().contains(currentLoginUser)
));
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class GetBoardResponseDto {
private Long boardId;
private String nickname;
private String title;
private String content;
private String image;
private LocalDateTime regDate;
private int commentCount;
private int likeCount;
private boolean likeStatus;
}
response가 내가 원하는대로 왔다..!(작성한 날짜 최신순 정렬 && 한 페이지에 10개씩 출력 && 1 페이지)
'항해99 3기' 카테고리의 다른 글
[TIL] 2021.11.09 최종 프로젝트 진행중 (0) | 2021.11.09 |
---|---|
[TIL] 2021.11.06 최종 프로젝트 진행중 (0) | 2021.11.06 |
[TIL] 2021.11.03 최종 프로젝트 진행중 (0) | 2021.11.03 |
[TIL] 2021.11.02 최종 프로젝트 진행중 (0) | 2021.11.03 |
[TIL] 2021.11.01 파이널 프로젝트 진행중 (0) | 2021.11.01 |