programing

휴지 상태 쌍방향 매핑에 의해 발생하는 json 시리얼라이저의 순환 참조를 해결하는 방법

telecom 2023. 3. 1. 09:31
반응형

휴지 상태 쌍방향 매핑에 의해 발생하는 json 시리얼라이저의 순환 참조를 해결하는 방법

JSON에 POJO를 시리얼라이즈하기 위해 시리얼라이저를 쓰고 있지만, 순환 참조에 문제가 있습니다.1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번, 1번 1번, 1번, 1번, 1번, 3번, 3번 3번, 4번, 4번, 4번, 4번, 4번, 4번, 4번, 4번, 4번, 4번, 4번, 4번, 4번, 4번, 4번, , 4번, 4번, 4번, 4번, 4번, 4
떻게 이이 ?환 환? ???오브젝트의 오너 트리를 취득하여 오브젝트 자체가 자신의 오너 계층 어딘가에 존재하는지 여부를 확인할 수 있습니까?문제를 할 수 ?또는 이 문제를 해결할 다른 아이디어가 있습니까?

Google JSON을 사용하여 이러한 문제를 처리합니다.

시리얼라이제이션 및 디시리얼라이제이션

A와 B 클래스 사이의 쌍방향 관계를 다음과 같이 가정합니다.

public class A implements Serializable {

    private B b;

}

그리고 B

public class B implements Serializable {

    private A a;

}

이제 Gson Builder를 사용하여 다음과 같은 커스텀 Gson 개체를 가져옵니다(Notice set Exclusion Strategies 메서드).

Gson gson = new GsonBuilder()
    .setExclusionStrategies(new ExclusionStrategy() {

        public boolean shouldSkipClass(Class<?> clazz) {
            return (clazz == B.class);
        }

        /**
          * Custom field exclusion goes here
          */
        public boolean shouldSkipField(FieldAttributes f) {
            return false;
        }

     })
    /**
      * Use serializeNulls method if you want To serialize null values 
      * By default, Gson does not serialize null values
      */
    .serializeNulls()
    .create();

이제 우리의 순환 참조는

A a = new A();
B b = new B();

a.setB(b);
b.setA(a);

String json = gson.toJson(a);
System.out.println(json);

Gson Builder 클래스 보기

Jackson 1.6(2010년 9월 출시)은 이러한 부모/자녀 링크 처리에 대한 특정 주석 기반 지원을 제공합니다(http://wiki.fasterxml.com/JacksonFeatureBiDirReferences 참조).(웨이백 스냅샷)

물론 이미 대부분의 JSON 처리 패키지(적어도 잭슨, gson 및 flex-json)를 사용하고 있는 부모 링크의 시리얼화는 제외할 수 있지만, 실제 문제는 시리얼라이제이션 측만 처리하는 것이 아니라 어떻게 역시리얼라이즈(부모 링크의 재작성)하는가에 있습니다.지금으로선 배제가 통할 것 같지만요

편집 (2012년 4월):Jackson 2.0은 이제 진정한 ID 참조(웨이백 스냅샷)를 지원하므로 이러한 방법으로도 문제를 해결할 수 있습니다.

쌍방향 관계를 JSON으로 나타낼 수 있습니까?일부 데이터 형식은 일부 데이터 모델링 유형에 적합하지 않습니다.

개체 그래프를 통과할 때 사이클을 처리하는 방법 중 하나는 지금까지 본 개체를 추적하여(아이덴티티 비교를 사용하여) 무한 사이클을 통과하는 것을 방지하는 것입니다.

이 문제에 대처하기 위해서, 다음의 어프로치를 실시했습니다(애플리케이션 전체의 프로세스를 표준화해, 코드를 명확하고 재사용 가능하게 합니다).

  1. 제외할 필드에 사용할 주석 클래스 만들기
  2. Google의 ExclusionStrategy 인터페이스를 구현하는 클래스를 정의합니다.
  3. GsonBuilder를 사용하여 GSON 개체를 생성하는 간단한 메서드를 만듭니다(Arthur 설명과 유사).
  4. 필요에 따라 제외할 필드에 주석을 추가합니다.
  5. com.google.gson에 시리얼라이제이션 규칙을 적용합니다.Gson 객체
  6. 오브젝트 시리얼화

코드는 다음과 같습니다.

1)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface GsonExclude {

}

2)

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;

public class GsonExclusionStrategy implements ExclusionStrategy{

    private final Class<?> typeToExclude;

    public GsonExclusionStrategy(Class<?> clazz){
        this.typeToExclude = clazz;
    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return ( this.typeToExclude != null && this.typeToExclude == clazz )
                    || clazz.getAnnotation(GsonExclude.class) != null;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(GsonExclude.class) != null;
    }

}

3)

static Gson createGsonFromBuilder( ExclusionStrategy exs ){
    GsonBuilder gsonbuilder = new GsonBuilder();
    gsonbuilder.setExclusionStrategies(exs);
    return gsonbuilder.serializeNulls().create();
}

4)

public class MyObjectToBeSerialized implements Serializable{

    private static final long serialVersionID = 123L;

    Integer serializeThis;
    String serializeThisToo;
    Date optionalSerialize;

    @GsonExclude
    @ManyToOne(fetch=FetchType.LAZY, optional=false)
    @JoinColumn(name="refobj_id", insertable=false, updatable=false, nullable=false)
    private MyObjectThatGetsCircular dontSerializeMe;

    ...GETTERS AND SETTERS...
}

5)

첫 번째 경우 null이 생성자에 제공되므로 제외할 다른 클래스를 지정할 수 있습니다. 두 옵션은 모두 아래에 추가됩니다.

Gson gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(null) );
Gson _gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(Date.class) );

6)

MyObjectToBeSerialized _myobject = someMethodThatGetsMyObject();
String jsonRepresentation = gsonObj.toJson(_myobject);

또는 Date 객체를 제외하려면

String jsonRepresentation = _gsonObj.toJson(_myobject);

Jackon을 사용하여 시리얼화할 경우 @JsonBackReference를 바이다이얼 매핑에 적용하기만 하면 순환 참조 문제가 해결됩니다.

주의: @JsonBackReference는 무한재귀(StackOverflowError)를 해결하기 위해 사용됩니다.

Arthur와했지만 Arthur 대신 setExclusionStrategies하였습니다.

Gson gson = new GsonBuilder()
                .excludeFieldsWithoutExposeAnnotation()
                .create();

and and 。@Exposejson 、 gson 、 gson 、 gson j 、 른 른 른 、 른 른 른 j j j j j j j j j j 。

스프링 부트를 사용하는 경우, 잭슨은 순환/양방향 데이터로부터 응답을 생성하는 동안 오류를 발생시키므로,

@Json Ignore Properties

순환을 무시하다

At Parent:
@OneToMany(mappedBy="dbApp")
@JsonIgnoreProperties("dbApp")
private Set<DBQuery> queries;

At child:
@ManyToOne
@JoinColumn(name = "db_app_id")
@JsonIgnoreProperties("queries")
private DBApp dbApp;

Javascript를 Javascript를 replacer「」JSON.stringify()기본 시리얼화 동작을 수정하는 함수를 전달할 수 있습니다.

사용법은 다음과 같습니다.다음 예에서는 4개의 노드를 순환 그래프로 나타냅니다.

// node constructor
function Node(key, value) {
    this.name = key;
    this.value = value;
    this.next = null;
}

//create some nodes
var n1 = new Node("A", 1);
var n2 = new Node("B", 2);
var n3 = new Node("C", 3);
var n4 = new Node("D", 4);

// setup some cyclic references
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n1;

function normalStringify(jsonObject) {
    // this will generate an error when trying to serialize
    // an object with cyclic references
    console.log(JSON.stringify(jsonObject));
}

function cyclicStringify(jsonObject) {
    // this will successfully serialize objects with cyclic
    // references by supplying @name for an object already
    // serialized instead of passing the actual object again,
    // thus breaking the vicious circle :)
    var alreadyVisited = [];
    var serializedData = JSON.stringify(jsonObject, function(key, value) {
        if (typeof value == "object") {
            if (alreadyVisited.indexOf(value.name) >= 0) {
                // do something other that putting the reference, like 
                // putting some name that you can use to build the 
                // reference again later, for eg.
                return "@" + value.name;
            }
            alreadyVisited.push(value.name);
        }
        return value;
    });
    console.log(serializedData);
}

하고 를 하여 실제 를 쉽게 수 .next이 붙은 참조를 @이 예시와 같이.

제 경우엔 이렇게 해결했어요.이것은 적어도 Gson & Jackson에게는 효과가 있다.

private static final Gson gson = buildGson();

private static Gson buildGson() {
    return new GsonBuilder().addSerializationExclusionStrategy( getExclusionStrategy() ).create();  
}

private static ExclusionStrategy getExclusionStrategy() {
    ExclusionStrategy exlStrategy = new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fas) {
            return ( null != fas.getAnnotation(ManyToOne.class) );
        }
        @Override
        public boolean shouldSkipClass(Class<?> classO) {
            return ( null != classO.getAnnotation(ManyToOne.class) );
        }
    };
    return exlStrategy;
} 

이 제공하는 은 「」입니다.JsonIdentityInfo주석을 사용하여 순환 참조를 방지합니다.튜토리얼은 이쪽에서 확인하실 수 있습니다.

이 에러는, 2개의 오브젝트가 있는 경우에 발생할 수 있습니다.

class object1{
    private object2 o2;
}

class object2{
    private object1 o1;
}

시리얼라이제이션에 GSon을 사용하면 다음 오류가 발생합니다.

java.lang.IllegalStateException: circular reference error

Offending field: o1

이 문제를 해결하려면 키워드 transient를 추가합니다.

class object1{
    private object2 o2;
}

class object2{
    transient private object1 o1;
}

보시는 바와 같이 Java에는 왜 일시적인 필드가 있습니까?

Java의 transient 키워드는 필드를 시리얼화하지 않음을 나타내기 위해 사용됩니다.

GSON을 사용하여 JSON에서 Java 클래스를 변환하는 경우 순환 참조 및 무한 루프의 원인이 되는 필드를 피할 수 있습니다.JSON에 표시하는 필드에는 @Expose라는 주석만 넣으면 됩니다.@Expose라는 주석이 없는 필드는 JSON에 표시되지 않습니다.

예를 들어 클래스 사용자를 클래스 Route의 필드 루트로 시리얼화하려고 하고 클래스 Route에 클래스 User의 필드 사용자가 있는 경우 GSON은 클래스 User의 필드 사용자를 시리얼화하려고 하며 루트를 시리얼화하려고 하면 클래스 Route와 클래스 Route의 필드 사용자를 시리얼화하려고 하고 다시 시도합니다.클래스 User를 시리얼화하기 위해 부정 루프를 유발하는 순환 참조가 있습니다.앞서 말한 User 및 Route 클래스를 표시합니다.

import com.google.gson.annotations.Expose;

클래스 사용자

@Entity
@Table(name = "user")
public class User {
    
@Column(name = "name", nullable = false)
@Expose
private String name;

@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
private Set<Route> routes;

@ManyToMany(fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinTable(name = "like_", joinColumns = @JoinColumn(name = "id_user"),
        inverseJoinColumns = @JoinColumn(name = "id_route"),
        foreignKey = @ForeignKey(name = ""),
        inverseForeignKey = @ForeignKey(name = ""))
private Set<Route> likes;

클래스 루트

  @Entity
  @Table(name = "route")
  public class Route {
      
  @ManyToOne()
  @JoinColumn(nullable = false, name = "id_user", foreignKey = 
  @ForeignKey(name = "c"))    
  private User user;

무한 루프를 피하기 위해 GSON을 제공하는 @Expose 주석을 사용합니다.

GSON과의 serialize 결과를 JSON 형식으로 보여줍니다.

{
    "name": "ignacio"  
}

필드 루트 및 like가 JSON 형식으로 존재하지 않고 필드 이름만 존재하는 것을 알 수 있습니다.이 때문에 순환 참조는 회피됩니다.

그것을 사용하려면 특정 방법으로 오브젝트 GSON을 작성해야 합니다.

Gson converterJavaToJson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();

마지막으로 GSON이 작성한 컨버터를 사용하여 휴지 상태 사용자 모델의 Java 클래스를 변환합니다.

 User user = createUserWithHibernate();
 String json = converterJavaToJson.toJson(user);

8번이 좋을수록 좋습니다.그래서 어떤 필드가 오류를 발생시키고 있는지 알고 있다면 field를 null로 설정하고 해결만 하면 된다고 생각합니다.

List<RequestMessage> requestMessages = lazyLoadPaginated(first, pageSize, sortField, sortOrder, filters, joinWith);
    for (RequestMessage requestMessage : requestMessages) {
        Hibernate.initialize(requestMessage.getService());
        Hibernate.initialize(requestMessage.getService().getGroupService());
        Hibernate.initialize(requestMessage.getRequestMessageProfessionals());
        for (RequestMessageProfessional rmp : requestMessage.getRequestMessageProfessionals()) {
            Hibernate.initialize(rmp.getProfessional());
            rmp.setRequestMessage(null); // **
        }
    }

코드를 읽을 수 있도록 큰 코멘트를 코멘트에서 이동합니다.// **아래까지.

java.displaces를 클릭합니다.Stack Overflow Error [요구 처리에 실패했습니다.네스트된 예외는 org.springframework.http.converter입니다.Http Message Not Writable예외:JSON: 무한 재귀(StackOverflowError)를 쓸 수 없습니다(참조 체인: com.service.pegazo.bo을 통해).Request Message Professional ["Message"]-> com.service.pegazo.bo 를 참조해 주세요.Request Message ["Request Message Professionals"]

예를 들어 ProductBean에는 serialBean이 있습니다.매핑은 쌍방향 관계입니다.지금 우리가 사용하려고 하면gson.toJson()순환 참조가 됩니다.이 문제를 회피하려면 , 다음의 순서를 실행합니다.

  1. 데이터 소스에서 결과를 가져옵니다.
  2. 목록을 반복하여 serialBean이 null이 아님을 확인한 후
  3. ★★productBean.serialBean.productBean = null;
  4. 한 번 .gson.toJson();

그것으로 문제가 해결될 것이다.

언급URL : https://stackoverflow.com/questions/3340485/how-to-solve-circular-reference-in-json-serializer-caused-by-hibernate-bidirecti

반응형