Search
Duplicate

240111

문제 발생 : 재귀가 끝나지 않음

@Override public DetailedPostResponse getPost(Long postId, User user) { Post post = getPostById(postId); List<UserPost> userPosts = getUserPostsByPost(post); List<List<Menu>> allMenus = getMenus(userPosts,post); return DetailedPostResponse.builder() .author(getAuthor(userPosts)) .address(post.getAddress()) .store(post.getStore()) .minPrice(post.getMinPrice()) .deliveryCost(post.getDeliveryCost()) .participants(getParticipants(userPosts)) .myMenus(getMyMenus(user, allMenus)) .menus(allMenus) .sumPrice(getSumPrice(getUserPostsByPost(post),post)) .deadline(getDeadline(post)) .build(); }
Swift
복사
{ "status": 200, "message": "글 상세페이지 조회에 성공했습니다.", "data": { "author": "가나다라", "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "myMenus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ "deliveryCost": 2000, "category": "KOREAN", ... 이하 반복 후 에러메시지가 출력됨 "menus": [ { "status": 400, "message": "에러가 발생했습니다", "data": "Could not write JSON: Infinite recursion (StackOverflowError)" }
Java
복사
myMenus가 문제임
private List<Menu> getMyMenus(User user, List<List<Menu>> allMenus){ List<Menu> myMenus = null; for(List<Menu> menus : allMenus){ if(menus.size()>0){ if(menus.get(0).getUser().getId().equals(user.getId())){ myMenus = menus; break; } } } return myMenus; }
Dart
복사
저기에서 뭐가 오류가 나는지 모르겠네?
break가 안 되는건가 싶어서 break대신 return을 해봄
private List<Menu> getMyMenus(User user, List<List<Menu>> allMenus){ for(List<Menu> menus : allMenus){ if(menus.size()>0){ if(menus.get(0).getUser().getId().equals(user.getId())){ return menus; } } } return null; }
CSS
복사
상황이 똑같음
mymenus가 아니라 allmenus가 문제인가 싶어서 순서를 한번 바꿔봄
@Builder public record DetailedPostResponse ( String author, String address, String store, Integer minPrice, Integer deliveryCost, List<List<Menu>> menus, List<Menu> myMenus, List<String> participants, Integer sumPrice, LocalDateTime deadline ){ }
Mathematica
복사
return DetailedPostResponse.builder() .author(getAuthor(userPosts)) .address(post.getAddress()) .store(post.getStore()) .minPrice(post.getMinPrice()) .deliveryCost(post.getDeliveryCost()) .participants(getParticipants(userPosts)) .menus(allMenus) .myMenus(getMyMenus(user, allMenus)) .sumPrice(getSumPrice(getUserPostsByPost(post),post)) .deadline(getDeadline(post)) .build();
Less
복사
음 바꾸니까 allMenus가 문제였다는 걸 알게됨
{ "status": 200, "message": "글 상세페이지 조회에 성공했습니다.", "data": { "author": "가나다라", "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "menus": [ [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [
JSON
복사
지금 allMenus가 어떻게 되고 있냐면
List<List<Menu>> allMenus = getMenus(userPosts,post);
Mathematica
복사
private List<List<Menu>> getMenus(List<UserPost> userposts, Post post){ List<List<Menu>> menus = new ArrayList<>(); for(UserPost userpost : userposts){ List<Menu> myMenus = getUserMenus(userpost.getUser(),post); menus.add(myMenus); } return menus; } private List<Menu> getUserMenus(User user, Post post){ return menuRepository.findAllByUserAndPost(user,post); }
Swift
복사
음… 왜 무한반복이 되는거지…
List<List<Menu>> menus = new ArrayList<>(); -> 미부분을 이렇게 쓰면 알아서 안 되는건가? List<List<Menu>> menus = new ArrayList<List<Menu>>(); 이렇게 바꿔봄
Mathematica
복사
아무 상관 없었음~~
ArrayList 에 add하는게 맞나 싶어서 찾아봄
C#
복사
private List<List<Menu>> getAllMenus(List<UserPost> userposts, Post post){ List<List<Menu>> menus = new ArrayList<>(); for(UserPost userpost : userposts){ List<Menu> userMenus = getUserMenus(userpost.getUser(),post); menus.add(userMenus); } return menus; }
Swift
복사
private List<String> getParticipants(List<UserPost> userPosts){ return userPosts.stream() .filter((UserPost userPost)->userPost.getRole().equals(UserPostRole.PARTICIPANT)) .map((UserPost userPost)->userPost.getUser().getNickname()).toList(); }
LiveScript
복사
private List<List<Menu>> getAllMenus(List<UserPost> userposts, Post post){ List<List<Menu>> menus = new ArrayList<>(); for(UserPost userpost : userposts){ List<Menu> userMenus = getUserMenus(userpost.getUser(),post); menus.add(userMenus); } return menus; }
Swift
복사
다시봐도 뭐가 잘못되었는지 모르겠고 일단 스트림으로 해봄
stream으로 바꾸다보니 List<List<Menu>>에다가 List<ArrayList<Menu>> 넣지 말라고 해서 그 부분 다 수정함
@Builder public record DetailedPostResponse ( String author, String address, String store, Integer minPrice, Integer deliveryCost, List<String> participants, List<ArrayList<Menu>> menus, List<Menu> myMenus, Integer sumPrice, LocalDateTime deadline ){ }
Mathematica
복사
private List<Menu> getMyMenus(User user, List<ArrayList<Menu>> allMenus){ for(List<Menu> menus : allMenus){ if(menus.size()>0){ if(menus.get(0).getUser().getId().equals(user.getId())){ return menus; } } } return null; }
CSS
복사
private List<ArrayList<Menu>> getAllMenus(List<UserPost> userposts, Post post){ List<ArrayList<Menu>> menus = userposts.stream().map((UserPost userpost)->new ArrayList<>(getUserMenus(userpost.getUser(),userpost.getPost()))).toList(); return menus; }
LiveScript
복사
{ "status": 200, "message": "글 상세페이지 조회에 성공했습니다.", "data": { "author": "가나다라", "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "participants": [], "menus": [ [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ {
JSON
복사
오 ~~ 여러가지 시도 다 해봐도 menus는 스트림으로 하나마나 무한반복임
private List<List<Menu>> getAllMenus(List<UserPost> userposts, Post post){ List<List<Menu>> menus = userposts.stream().map((UserPost userpost)->getUserMenus(userpost.getUser(),userpost.getPost())).toList(); return menus; }
LiveScript
복사
이렇게 해봄
상황 똑같음
for문으로 다시 돌아가봄
private List<List<Menu>> getAllMenus(List<UserPost> userposts, Post post){ List<List<Menu>> menus = new ArrayList<>(); for(UserPost userpost : userposts){ List<Menu> userMenus = getUserMenus(userpost.getUser(),post); menus.add(userMenus); } return menus; }
Swift
복사
"menus": [ [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [
YAML
복사
참조에 문제가 있다고 함
menu를 가져올때 post를 가져오고 다시 menu를 가져오고 무한반복의 루프에 빠짐
Post entity에 JSON Ignore를 붙였더니 끝나긴 끝났다 아직 상호참조를 하고있지만…
@OneToMany(mappedBy = "post") @JsonIgnore//상호참조문제? private List<Menu> menus;
Less
복사
{ "status": 200, "message": "글 상세페이지 조회에 성공했습니다.", "data": { "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "participants": [ "가나다라" ], "menus": [ [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "sumPrice": null, "deadline": "2024-01-09T23:03:49.701351" }, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 }, { "id": 2, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "sumPrice": null, "deadline": "2024-01-09T23:03:49.701351" }, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 }, { "id": 3, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "sumPrice": null, "deadline": "2024-01-09T23:03:49.701351" }, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 } ] ], "myMenus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "sumPrice": null, "deadline": "2024-01-09T23:03:49.701351" }, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 }, { "id": 2, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "sumPrice": null, "deadline": "2024-01-09T23:03:49.701351" }, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 }, { "id": 3, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "sumPrice": null, "deadline": "2024-01-09T23:03:49.701351" }, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 } ], "sumPrice": 15000, "deadline": "2024-01-09T23:03:49" } }
JSON
복사
Post entity에서 menus를 그냥 참조해올수 있는데 menu repository에서 해서 문제가 됐던걸까!
private List<List<Menu>> getAllMenus(List<UserPost> userposts, Post post){ List<List<Menu>> menus = new ArrayList<>(); for(UserPost userpost : userposts){ List<Menu> userMenus = getUserMenus(userpost.getUser(),post); menus.add(userMenus); } return menus; } private List<Menu> getUserMenus(User user, Post post){ return menuRepository.findAllByUserAndPost(user,post); }
Swift
복사
private List<List<Menu>> getAllMenus(List<UserPost> userposts, Post post){ List<List<Menu>> menus = new ArrayList<>(); for(UserPost userpost : userposts){ List<Menu> userMenus = userpost.getPost().getMenus(); menus.add(userMenus); } return menus; }
Swift
복사
이렇게 바꿔봄
{ "status": 200, "message": "글 상세페이지 조회에 성공했습니다.", "data": { "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "participants": [ "가나다라" ], "menus": [ [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ { "id": 1, "post": { "id": 1, "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "category": "KOREAN", "menus": [ {
JSON
복사
음 똑같은 문제가 일어난다

Spring Data JPA, @OneToMany 무한 반복 오류

@OneToMany Entity을 RestAPI로 리턴할때 부한 반복 오류
양방향 관계의 Entity에서 @OneToMany를 사용하면, 엔티티를 리턴할 때 순환 참조(Circular Reference) 문제가 발생할 수 있다고 함!
JSON 직렬화 시 무한 루프 -> StackOverflowError가 발생함 ( 나도 저거 발생했음! )

순환참조 해결법

1.
@JsonIgnore : 이 어노테이션을 붙이면 JSON 데이터에 해당 property는 null로 들어가게 된다. 즉, 데이터에 아예 포함시키지 않는다.
2.
@JsonManagedReference 와 @JsonBackReference : 부모 클래스(Posts entity)의 Comment 필드에 @JsonManagedReference를, 자식 클래스(Comment entity)의 Posts 필드에 @JsonBackReference를 추가해주면 순환 참조를 막을 수 있다.
3.
@JsonIgnoreProperties : 부모 클래스(Posts entity)의 Comment 필드에 @JsonIgnoreProperties({"posts"}) 를 붙여주면 순환 참조를 막을 수 있다.
4.
DTO 사용 : 위와 같은 상황이 발생하게된 주원인은 '양방향 매핑'이기도 하지만, 더 정확하게는 Entity 자체를 response로 리턴한데에 있다. entity 자체를 return 하지 말고, DTO 객체를 만들어 필요한 데이터만 옮겨담아 Client로 리턴하면 순환 참조 관련 문제는 애초에 방지 할 수 있다.
5.
양방향 매핑을 단방향 매핑으로

해결 : @JsonManagedReference 와 @JsonBackReference 달아서 해결함!!

public class Menu { @JsonBackReference @ManyToOne @OnDelete(action = OnDeleteAction.CASCADE) @JoinColumn(name = "post_id", nullable = false) private Post post;
Less
복사
public class Post { @JsonManagedReference @OneToMany(mappedBy = "post") private List<Menu> menus;
Less
복사
{ "status": 200, "message": "글 상세페이지 조회에 성공했습니다.", "data": { "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "participants": [ "가나다라" ], "menus": [ [ { "id": 1, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 }, { "id": 2, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 }, { "id": 3, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 } ] ], "myMenus": [ { "id": 1, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 }, { "id": 2, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 }, { "id": 3, "user": { "id": 1, "email": "email@dot.com", "password": "$2a$10$lGhYCvjkDNzZyq8RkanrZ.3NRctUwVvZAbS8c9sFepIFz7U6wJUUq", "nickname": "가나다라" }, "menuname": "통새우버거", "price": 5000 } ], "sumPrice": 15000, "deadline": "2024-01-09T23:03:49" } }
JSON
복사
잘 나온다!!
다시 Stream으로 바꿔봄
private List<List<Menu>> getAllMenus(List<UserPost> userposts, Post post){ List<List<Menu>> menus = userposts.stream().map((UserPost userpost)->getUserMenus(userpost.getUser(),userpost.getPost())).toList(); return menus; }
LiveScript
복사
post 필요없는거 같아서 삭제함
private List<List<Menu>> getAllMenus(List<UserPost> userposts){ List<List<Menu>> menus = userposts.stream().map((UserPost userpost)->userpost.getPost().getMenus()).toList(); return menus; }
LiveScript
복사
잘된다!

생각해봄

애초에 dto를 쓰는데 이유가 있는 거였다 ㅋㅋ
Menu를 안 돌려보내고 Menu response를 돌려보냈으면 이럴 일이 애초에 없고 user 정보도 안 나올거잖아...
User 정보가 왜 써있나 했다
Menu에 User가 들어있으니까 그걸 또 쓴거구나
Menu에 Post가 들어있으니까 그거 정보 쓰다가 그 안에 Menu가 있으니까 그거 쓰다가 무한반복이 그래서 시작된거군….
뭔가 DetailedPostResponse 짜는 중 무의식중에 dto안 만들고 그냥 Menu를 돌려보냈었는데 앞으로는 절대 그럴 일이 없을듯 ㅋㅋㅋ

더 나은 해결책

public record MenuResponse( String menuname, Integer price ) { }
Arduino
복사
public record MenusResponse( String nickname, List<MenuResponse> menus ) { }
Arduino
복사
걍 참여자 이름도 따로 넣지 않고 메뉴에다 넣고 myMenus를 없애고 allMenus 하나로 하는게 좋을듯
@Builder public record DetailedPostResponse ( String address, String store, Integer minPrice, Integer deliveryCost, List<List<MenuResponse>> menus, Integer sumPrice, LocalDateTime deadline ){ }
PHP
복사
private List<MenusResponse> getAllMenus(List<UserPost> userposts){ List<MenusResponse> menus = userposts.stream().map((UserPost userpost)-> new MenusResponse(userpost.getUser().getNickname(), userpost.getPost().getMenus() .stream().map((Menu menu)->new MenuResponse(menu.getMenuname(),menu.getPrice())).toList() )).toList(); return menus; }
LiveScript
복사
{ "status": 200, "message": "글 상세페이지 조회에 성공했습니다.", "data": { "address": "주소주소", "store": "storestore", "minPrice": 20000, "deliveryCost": 2000, "menus": [ { "nickname": "가나다라", "menus": [ { "menuname": "통새우버거", "price": 5000 }, { "menuname": "통새우버거", "price": 5000 }, { "menuname": "통새우버거", "price": 5000 } ] } ], "sumPrice": 15000, "deadline": "2024-01-09T23:03:49" } }
JSON
복사
잘 된다!!