JPA JsonMappingException: Infinite recursion (StackOverflowError) - 무한루프



 JPA를 사용해 개발을 하던 도중 낯선 에러를 보게 되었습니다.



Caused by: com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain:



JsonMapping 을 하던 도중 무한 순환을 하여 StackOverflow 에러가 났다는 에러였습니다.

뭔가 하고 찾고 분석한 결과 원인을 알아내고 수정할 수 있었습니다.

문제의 컨트롤러에서 아래와 같이 ResponseEntity 를 리턴타입으로 가지고 있었습니다.



public ResponseEntity<?> searchOrders() {



return ResponseEntity.ok(new ApiResponse(123, "Success", entityObject));



Http Response body 에 object 를 serialized 할 때 jackson 을 사용하는데 jackson 이 데이터를 뽑을 때 getter 기준으로 뽑아낸다고 합니다.

그런데 제가 한 실수가 일단 성공하고 return 이 잘되는지 확인을 먼저 하려는 마음에 사용하던 Entity 객체를 return 하였는데 

이 Entity가 OneToMany 로 자식 엔티티를 참조하고 자식 Entity는 ManyToOne 으로 부모 Entity를 참조하고 있는 상태였습니다.



@Entity
@Table(name = "test")
public class Test {
    

...


@OneToMany(mappedBy = "test")
private List<SubTest> subTests = new ArrayList<>();
}



@Entity
@Table(name = "biz_order_products")
public class BizOrderProduct {

...


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "testId")
private Test test;

}



그래서 양방향 참조를 하는 관계에서 getter 를 하게 되니 서로 참조하고 있고 get 을 하면 순환을 하여 무한루프에 빠지게 되고 이 데이터가 메모리의 Stack 영역에 계속해서 쌓이게 되어 메모리의 Heap 영역을 침범하게 되는 StackOverflow 에러가 발생하게 된 것 이였습니다.


이를 해결하기 위해선 


1. 새로운 객체를 만들어 이를 return.

2. @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "순환이 일어나는 참조객체에 맵핑되는 컬럼") 으로 순환을 끊기.

3. 양방향 참조를 단방향 참조로 만들기.

4. fetch join을 사용하기.


가 있습니다.

해결 방법이 더 있을 수 있다면 댓글로 알려주시면 감사하겠습니다.





'Database > JPA & JPQL' 카테고리의 다른 글

[JPA] Id Annotation과 return cached data  (0) 2018.07.05
Pageable로 Limit 걸기  (0) 2018.06.23
JPQL이란?  (0) 2018.06.18



BiFunction과 TriFunction


Java 8BiFunction 이라는 것이 추가되었습니다.



private BiFunction<Integer, String, String> createStatusErrorMessage = (code, status) -> {
StringBuilder sb = new StringBuilder();

//do something.

return sb.toString();
};



기본적인 생김새는 위와 같습니다.

앞의 두개의 타입으로 파라미터를 받고 3번째 타입으로 return 하겠다는 의미입니다.

TriFunction도 있을 거라고 생각을 했는데 없어서 따로 만들어야 합니다.



@FunctionalInterface
public interface TriFunction<A, B, C, R> {

R apply(A a, B b, C c);

default <V> TriFunction<A, B, C, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (A a, B b, C c) -> after.apply(apply(a, b, c));
}
}



위와 같이 FunctionalInterface annotation을 넣은 TriFunction interface를 만듭니다.

생김새는 BiFunction과 거의 같습니다.



private TriFunction<String, String, String, String> createStatusErrorMessage = (paymentStatus, pgStatus, pgType) -> {
StringBuilder statusErrorBuilder = new StringBuilder();

//do something.

return statusErrorBuilder.toString();
};



BiFunction과 TriFunction의 사용방법도 같습니다.

예를 들어 Parameter를 분류하여 ErrorMessage 를 생성하는 용도로 사용한다고 하겠습니다.



private static Map<String, TriFunction<String, String, String, String>> errorBuilderMap = new HashMap<>();

public ErrorMessageBuilder() {
errorBuilderMap.put("STATUS", createStatusErrorMessage);
}


public String build() {
return errorBuilderMap.get(errorMessageBuilder.errorType)
.apply(errorMessageBuilder.data1, errorMessageBuilder.data2, errorMessageBuilder.data3);
}



위와 같이 Map<String, TriFunction> 에 Key로 사용할 에러 메세지 타이틀과 에러 메세지를 만들 TriFunction을 넣습니다.

그리고 파라미터를 입력받아 TriFunction을 get하여 TriFunction.apply(parameter1, parameter2, parameter3) 하면 해당 TriFunction이 호출되어 실행되고 값을 리턴합니다. 







[JPA] Id Annotation과 return cached data


JPA로 Query method를 작성해 사용하던 도중 이상함을 느꼈습니다.



List<UserTrack> findAll();



예를 들어 위와 같은 Query method를 사용하여 쿼리를 작성하여 사용했는데 이상하게 같은 데이터를 중복, 반복해서 return 하는 현상이 일어났습니다.

해당 문제는 Entity에서 @Id Annotation을 건 컬럼이 DB Table에서 유니크한 값을 가지고 있지 않은 경우에 발생합니다.



@Entity
public class UserTrack {
@Id
private Long age;

private String name;
}



만약 위와 같이 @Id Annotation을 걸었는데 테이블에서 해당 컬럼의 데이터가 중복될 경우 애매하여 이전에 찾아서 Cache 되었던 data가 있는지 찾습니다.


이를 해결하려면 


1. 해당 테이블의 PK에 Id Annotation을 겁니다.

2. PK가 없을 경우에는 EmbeddedId 를 만들어 복합키로 유니크한 Id를 만들어 줍니다.


이렇게 하면 제대로 select 해서 return 하는 것을 확인할 수 있습니다.



'Database > JPA & JPQL' 카테고리의 다른 글

JPA JsonMappingException: Infinite recursion (StackOverflowError) - 무한루프  (0) 2018.08.02
Pageable로 Limit 걸기  (0) 2018.06.23
JPQL이란?  (0) 2018.06.18

+ Recent posts