-
Notifications
You must be signed in to change notification settings - Fork 2
Trouble shooting
코드를 짜며 고민했던 것의 기록들입니다.
아래에서, dto를 활용하게 되면 널포인트 익셉션에러가 뜬다.
@Getter
@AllArgsConstructor
public class RecipeListResponseDto {
private Long recipeId;
private String nickname;
private String title;
private String content;
private List<String> image ;
private LocalDateTime regdate;
private int commentCount;
private int likeCount;
private boolean likeStatus;
public RecipeListResponseDto(Recipe recipe, UserDetailsImpl userDetails, RecipeLikesRepository recipeLikesRepository){
this.recipeId = recipe.getId();
this.nickname = recipe.getUser().getNickname();
this.title = recipe.getTitle();
this.content = recipe.getContent();
this.regdate = recipe.getRegDate();
this.commentCount = recipe.getRecipeCommentList().size();
recipe.getRecipeImagesList().forEach((RecipeImage)->this.image.add(RecipeImage.getImage()));
this.likeCount = recipe.getRecipeLikesList().size();
Optional<RecipeLikes> foundRecipeLike = recipeLikesRepository.findByRecipeIdAndUserId(recipe.getId(),userDetails.getUser().getId());
this.likeStatus = foundRecipeLike.isPresent();
}
}해결: 멤버변수 선언시 초기화를 해주니까 해결이 된다. 원인은 images.add를 하는데 객체가 생성이 되어 있지 않았기 때문이라 추측된다.
@Getter
@AllArgsConstructor
public class RecipeListResponseDto {
private Long recipeId;
private String nickname;
private String title;
private String content;
private List<String> images = new ArrayList<>();
private LocalDateTime regdate;
private int commentCount;
private int likeCount;
private boolean likeStatus;- 업로드한 이미지들을 수정하기 위해, 먼저 기존 RECIPE_IMAGE테이블에서 특정 Recipe에 해당하는 이미지들을 모두 삭제를 하고 새로운 사진을 INSERT하는 방법을 선택하였다.
- 그러나 다음 쿼리가 날라가지 않음.
recipeImageRepository.deleteAllByRecipe(recipe);- 구체적인 문제상황:
1번 레시피글에 사진이 5장 등록되어있음. 새로운 2장으로 사진을 수정하고자 함. 그러나, 기존 사진이 삭제가 되지 않았음.
-
디버깅 시 쿼리가 delete쿼리 날라가는가? 아니? 트랜잭션이 끝날 때 아무 쿼리도 날라가지 않음.
-
원인 추측:
- jpa쿼리를 잘못 썼나? 아무리 봐도 아니다.
- 혹시 영속성 관련 문제인가? 정답
- Recipe엔티티:
아래처럼 케스케이드 옵션을 가지고 있다. 부모역할을 한다.
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
@JsonBackReference
private List<RecipeImage> recipeImagesList;- RecipeImage엔티티:
단순히 이미지와 recipe를 연결해주고있는 엔티티이다.
@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.ALL 을 사용한 이유
부모(Recipe)를 삭제할 때 자식(recipeImageList)도 삭제하여참조무결성 오류를 방지하기 위해.
해결책:
- Recipe엔티티:
케스케이드옵션을 REMOVE로 하거나, orphanRemoval = true를 설정한다.
@OneToMany(mappedBy = "recipe", cascade = CascadeType.REMOVE)
@JsonBackReference
private List<RecipeImage> recipeImagesList; @OneToMany(mappedBy = "recipe", orphanRemoval = true)
@JsonBackReference
private List<RecipeImage> recipeImagesList;특정기간 동안에 발생한 row만 뽑아내는 쿼리를 날리려 하는데, 서버시간과 DB시간이 맞지 않았다.
해결방법:
- 스프링부트의 시간을 서울시간으로 설정.
@PostConstruct
void started() {
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
}2.디비(mysql)의 시간도 서울로 설정한다.
select now() //현재시간 확인
set time_zone='Asia/Seoul';
이제 쿼리날릴 때 아래처럼 LocalDateTime으로 보낼 때, 현재시간으로 보내면 스프링부트와 디비시간 모두 한국시간으로 잘 나온다.
@Query("select r.id as recipeId, r.title as title, r.content as content , r.price as price " +
"from Recipe r join r.recipeLikesList l " +
"where l.regDate between :startDate and :endDate " +
"group by r.id order by count(l.recipe) desc ")
List<PopularRecipeInterface> findPopularRecipe(LocalDateTime startDate, LocalDateTime endDate);
- 주의사항: 쿼리를 날릴때 p6spy로 쿼리내용을 보면 2021-10-31T01:45:20.253+0900 처럼 찍힘을 알 수 있다. 처음에 +0900을 보고, 현재시간에 9시간을 더해준다고 오해를 해서, 현재도 한국시간인데 9시간을 또 더하다니?라고 생각을 해서 조금 해맸다. 여기서 +0900은 utc에 비해 9시간을 더해줘서 현재시간이 됐다는 "결과"를 설명해주는 것이다.
다음처럼 인터페이스를 하나 만들어야 한다. alias로 설정한 값들에 대한 getter함수를 설정하는 것이다.
@Query(value = "select r.id as recipeId, r.title as title, r.content as content , r.price as price " +
"from Recipe r join r.recipeLikesList l " +
"where l.regDate between :startDate and :endDate " +
"group by r.id order by count(l.recipe) desc ")
List<PopularRecipeInterface> findPopularRecipe(LocalDateTime startDate, LocalDateTime endDate);public interface PopularRecipeInterface {
Long getRecipeId();
String getTitle();
String getContent();
int getPrice();
}- 시도 1 다른 테이블을 join을 하고, join한 테이블의 칼럼을 group by를 하여야 한다. 따라서 데이터 jpa로는 한계가 있다고 판단하여 jpql쿼리를 짰다.
@Query("select r.id " +
"from Recipe r join r.recipeLikesList l " +
"where l.regDate between :startDate and :endDate " +
"group by r.id order by count(l.recipe) ")
List<Long> findPopularRecipeId(LocalDateTime startDate, LocalDateTime endDate);- 시도 2 그러나 top3를 하려면 limit키워드를 쓰면 된다 생각했는데, jpql은 limit를 지원하지 않았다...충격... 따라서 native sql로 쿼리를 작성하였다.
@Query(value="SELECT r.recipe_id " +
"FROM recipe r JOIN recipe_likes l ON r.recipe_id = l.recipe_id " +
"WHERE l.regdate BETWEEN :startDate AND :endDate " +
"GROUP BY r.recipe_id order by count(l.recipe_id) desc limit 3",
nativeQuery = true)
List<Long> findPopularRecipeId2(LocalDateTime startDate, LocalDateTime endDate);- 시도 3 비즈니스 로직을 짜다보니, top3의 레시피의 id뿐만 아니라, 그 레시피가 가지고 있는 다른 정보들이 필요했다. 가져온 id로 또 레시피정보를 가져오는 쿼리를 보내는 것은 비효율적이라 판단하여, 위 시도2번에서 id를 가져오는 것만이 아니라 레시피의 모든 정보를 다 가져오도록 시도햐였다.
@Query(value = "select re from Recipe re where re.id in (" +
"select r.id as recipeId " +
"from Recipe r join r.recipeLikesList l " +
"where l.regDate between :startDate and :endDate " +
"group by r.id order by count(l.recipe) desc) " +
"order by field(re.id, " +
"select r.id as recipeId " +
"from Recipe r join r.recipeLikesList l " +
"where l.regDate between :startDate and :endDate " +
"group by r.id order by count(l.recipe) desc) ")
List<Recipe> findPopularRecipe2(LocalDateTime startDate, LocalDateTime endDate);그러나, 문제가 발생..Recipe 전체를 가져오기는 하나, top3의 순서가 지켜지지 않았다...order by field라는 게 있다길래 시도했으나 jpql은 order by field를 지원하지 않았다. 따라서 sql에서 시도를 하고자 하였다.
- 시도 4 위 시도 3에서 top3의 순서를 지키지 않으며 Recipe를 반환한 것을 보완하고자, order by field를 적용하였다.
@Query(value = "SELECT * from recipe r2 " +
"where r2.recipe_id " +
"in " +
"(SELECT r.recipe_id " +
"FROM recipe r JOIN recipe_likes l ON r.recipe_id = l.recipe_id " +
"WHERE l.regdate BETWEEN '2021-11-01' AND '2021-11-08' " +
"GROUP BY r.recipe_id order by count(l.recipe_id) desc) " +
"ORDER BY FIELD(r2.recipe_id,(SELECT r.recipe_id " +
" FROM recipe r JOIN recipe_likes l ON r.recipe_id = l.recipe_id " +
" WHERE l.regdate BETWEEN :startDate AND :endDate " +
" GROUP BY r.recipe_id order by count(l.recipe_id) desc))"
,nativeQuery = true)
List<Recipe> findPopularRecipe3(LocalDateTime startDate, LocalDateTime endDdate);그러나, 이역시 문제가 발생....order by field는 다음처럼 활용해야 했다. order by field(id, 1,3,4,5) 즉, select문을 넣으니까 오류가 발생한 것이다. 분명히 select문을 넣는 경우가 있을텐데, 방법을 못 찾았을 거라 생각하기에 다음 리팩토링때 보완하고자 한다.
결론: 효율을 위해 많은 시도를 하였지만, 일단 구현을 먼저 하기위해 시도 2번 방법을 선택하였음.