JPA 연습 중 발생한 IllegalStateException 예외를 해결하는 방법을 기록해 보고자 한다.
해당 예외의 발생원인은 ParentController에서 양방향 연관관계를 맺은 Parent 엔티티 자체를 리턴할 경우 직렬화 과정에서 Parent 클래스에 Public 접근제어자를 가진 필드가 있는지 또는 필드를 반환하는 getter 메소드가 있는지 등을 파악하여 직렬화 과정을 진행중에 children 필드를 만나면 Child 클래스 직렬화가 시작되고 직렬화 도중 parent 필드를 만나면 다시 또 Parent 클래스를 직렬화 하기 시작하면서 순환 참조가 발생한다.
해결 방법 1. @JsonIgnore 사용
해당 어노테이션이 선언된 필드는 직렬화 역직렬화 과정에서 제외되어 변환되지 않는다.
Child 클래스 parent 필드에 선언하면 직렬화 과정에서 제외되기 때문에 순환참조가 발생하지 않는다.
해결 방법 2. ResponseDto 사용
Parent 클래스 자체를 리턴하지 않고 ResponseDto를 생성하여 원하는 데이터만 대입 하여 리턴 한다. 가장 권장되는 방법이며 순환참조가 발생을 방지할 수 있다.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long parentId;
String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
// @JsonBackReference
List<Child> children = new ArrayList<>();
}
@Entity
@Setter
@NoArgsConstructor
@Getter
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
Long childId;
@Getter
String name;
@ManyToOne
@JoinColumn(name = "parent_id")
//@JsonIgnore
Parent parent;
public void setParent(Parent parent){
if(this.parent != null){
this.parent.getChildren().remove(this);
}
this.parent = parent;
parent.getChildren().add(this);
}
}
@RestController
@RequestMapping("/parents")
@RequiredArgsConstructor
public class ParentController {
private final ParentService parentService;
@PostMapping
public ResponseEntity<?> postChild(@RequestBody ParentDto.Post postDto) {
Parent parent = new Parent();
List<Child> childList = postDto.getChildDtos().stream()
.map(childDto -> {
Child child = new Child();
child.setName(childDto.getName());
child.setParent(parent);
return child;
}).collect(Collectors.toList());
parent.setChildren(childList);
parent.setName(postDto.getName());
Parent savedParent = parentService.createParent(parent);
List<ChildDto.Response> childDtoList = savedParent.getChildren().stream()
.map(child -> {
return ChildDto.Response.builder()
.childId(child.getChildId())
.name(child.getName())
.build();
}).collect(Collectors.toList());
ParentDto.Response responseDto = ParentDto.Response.builder()
.parentId(savedParent.getParentId())
.name(savedParent.getName())
.children(childDtoList)
.build();
return new ResponseEntity<>(savedParent, HttpStatus.CREATED);
}
@GetMapping("{id}")
public ResponseEntity<?> getParent(long id){
Parent findParent = parentService.findParent(id);
return new ResponseEntity<>(findParent, HttpStatus.OK);
}
}
public class ParentDto {
@Getter
public static class Post {
private String name;
private List<ChildDto.Post> childDtos;
}
@Getter
@Builder
public static class Response{
private Long parentId;
private String name;
private List<ChildDto.Response> children = new ArrayList<>();
}
}
public class ChildDto {
@Getter
public static class Post {
private String name;
}
@Getter
@Builder
public static class Response{
private Long childId;
private String name;
}
}