문제 발생 : 재귀가 끝나지 않음
@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
복사
잘 된다!!