티스토리 뷰

728x90

1. 이 포스트는 하이버네이트의 양방향 One to Many나 Many to Many관계에서 발생하는 무한재귀호출에 대한 것이다.

 

2. 이 문제는 jackson이 객체를 네트워크로 보낼 데이터로 변환(serialization)하면서 circle이 발생하는 entity를 정상적으로 처리하지 못하여 발생하는 문제이다.

  2-1 반드시 내부적으로 순환관계가 있어야 발생하는 문제이다. 단방향으로 정의하면 문제가 생기지 않는다.

  2-2 환자와 진료데이터를 가지고 설명한다.

    2-2-1 환자는 여러 진료데이터를 가지고 있고, 

    2-2-2 진료데이터는 단 한 명의 환자 정보를 가지고 있다.

 

 

 

3. 환자와 진료데이터 entity

  3-1 환자 Entity

    3-1-1 중요한 것은 환자와 데이터가 서로에 대한 참조변수를 가지고 있는 부분이다.

 

package pe.pilseong.clinicals.entity;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Entity
@Table(name = "patient")
@Data
@EqualsAndHashCode(callSuper=false)
@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class,
  property = "id"
)
public class Patient extends AbstractEntity {

  @Column(name = "first_name")
  private String firstName;

  @Column(name = "last_name")
  private String lastName;

  @Column(name = "age")
  private Integer age;

  @OneToMany(mappedBy = "patient")
  private List<ClinicalData> data;

  public void addClinicalData(ClinicalData data) {
    if (this.data == null) {
      this.data = new ArrayList<>();
    } 
    this.data.add(data);

    data.setPatient(this);
  }
}

 

  3-2 진료데이터 entity

 

package pe.pilseong.clinicals.entity;

import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import org.hibernate.annotations.CreationTimestamp;

import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "clinicaldata")
@Getter
@Setter
@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class,
  property = "id"
)
public class ClinicalData extends AbstractEntity {
  
  @ManyToOne
  @JoinColumn(name = "patient_id", nullable = false)
  private Patient patient;

  @Column(name = "component_name")
  private String componentName;

  @Column(name = "component_value")
  private String componentValue;

  @Column(name = "measured_date_time")
  @CreationTimestamp
  private Timestamp measuredDatetime;

  @Override
  public String toString() {
    return "ClinicalData [componentName=" + componentName + 
      ", componentValue=" + componentValue + ", measuredDatetime=" + 
      measuredDatetime + "]";
  }
}

 

  3-3 의미없는 @MappedSuperclass 완결성을 위해 붙여 놓는다.

 

package pe.pilseong.clinicals.entity;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

import lombok.Data;

@MappedSuperclass
@Data
public class AbstractEntity {
    
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
}

 

4. 이 문제를 해결하기 위한 방법은 여러가지가 있는데, 2가지의 임기응변과 3가지의 해결책이 있다.

  4-0 5가지 방법이 있는데 예제를 생략한 것은 굳이 알 필요가 없어서 이다.

    4-0-1 임기응변은 @JsonIgnore만으로 족하고 해결책으로는 @JsonIdentityInfo만으로 족하다.

    4-0-2 다른 방식으로 쓰여진 코드가 있으면 인터넷에 찾아보면 되는데 어려운 내용이 아니다.

 

  4-1 2가지 임기응변은 한쪽 Entity에서 다른 쪽의 Enity를 끊어버리는 것으로 한쪽에만 데이터가 표시된다.

    4-1-1 @JsonIgnore과 @JsonManagedReference, @JsonBackReference 쌍이 있다. 

      4-1-1-1 @JsonIgnore은 속성에 붙이는 것으로 붙여진 속성은 Jackson에서 Json으로 변환하지 않는다.

      4-1-1-2 @JsonManagedReference, @JsonBackReference 쌍도 동일한 방식으로 동작한다. 같이 써야 한다.

        4-1-1-2-1 다만, 어느 Entity를 보출할지를 정할 수 있을 뿐이다. 사실 아무런 의미가 없다.

        4-1-1-2-2 @JsonManagedReference도 속성에 붙이는데 이 속성이 붙어 있으면 Json으로 변환이 된다.

        4-1-1-2-3 @JsonBackReference는 수식되는 속성은 Json변환에서 제외된다.

    4-1-5 @JsonManagedReference은 쓰지 않는 게 좋다. post로 데이터를 생성할 때 오류가 뜨는 경우가 있다.

 

  4-2 3가지 해결책은 Serialization이 정상적으로 되도록 hint를 jackson에게 제공하는 것이다.

    4-2-0 해결책이라는 말은 양쪽 모두 포함되는 정보를 모두 json으로 변환하기 때문이다.

    4-2-1 별도의 DTO객체를 개발자가 작성하여 아예 문제가 생기지 않도록 한다. 제일 좋은 방법이다.

    4-2-2  filter클래스를 작성하고 @JsonView를 사용한다. 좋은 방법이긴 한데 클래스를 생성해야 해서 귀찮다.

    4-2-3 @JsonView가 귀찮으면 @JsonIdentityInfo를 사용하여 직렬화된 객체에 ObjectID를 박아버린다.

      4-2-3-1 이 방법을 위의 코드에서 사용하고 있고,

      4-2-3-2 이 annotation의 property는 속성은 위의 예제의 경우 기본값 id가 entity속성에 있으므로 사실 필요없다.

 

728x90
댓글