Hibernate双方向マッピングによって引き起こされるjsonシリアライザーの循環参照を解決するにはどうすればよいですか?


82

POJOをJSONにシリアル化するシリアライザーを作成していますが、循環参照の問題が発生します。Hibernateの双方向の1対多の関係では、親が子を参照し、子が親を参照し、ここで私のシリアライザーが停止します。(以下のサンプルコードを参照)
このサイクルを壊す方法は?オブジェクトの所有者ツリーを取得して、オブジェクト自体がそれ自体の所有者階層のどこかに存在するかどうかを確認できますか?参照が循環するかどうかを確認する他の方法はありますか?またはこの問題を解決するための他のアイデアはありますか?


問題の解決に役立つコードを貼り付けるつもりでしたか?
ラッセル

2
eugeneのアノテーションベースのソリューションは問題ありませんが、この場合、追加のアノテーションとExclusionStrategyの実装は必要ありません。そのためにJavaの「transient」キーワードを使用するだけです。これは標準のJavaオブジェクトのシリアル化で機能しますが、Gsonそれを尊重します。
MeTTeO 2012年

回答:


11

双方向の関係をJSONで表すこともできますか?一部のデータ形式は、一部のタイプのデータモデリングには適していません。

オブジェクトグラフのトラバースを処理するときにサイクルを処理する1つの方法は、(ID比較を使用して)これまでに表示したオブジェクトを追跡し、無限のサイクルをトラバースしないようにすることです。


私は同じことをしました、そしてそれは働きます、しかしそれがすべてのマッピングシナリオで働くかどうかはわかりません。少なくとも今のところ、私は準備が整っており、よりエレガントなアイデアを考え続ける
つもりです

もちろん可能です-これにはネイティブのデータ型や構造がありません。ただし、XML、JSON、またはその他のほとんどのデータ形式で表現できるものは何でもかまいません。
StaxMan 2010

4
私は興味があります-JSONで循環参照をどのように表現しますか?
マットb

1
複数の方法:一般的にはJSONだけでなく、JSONといくつかのメタデータの組み合わせであり、最も一般的にはJSONとのバインドに使用するクラス定義であることに注意してください。JSONの場合、ある種のオブジェクトIDを使用するか、リンケージを再作成するかが問題になります(たとえば、Jackson libには、親子リンケージを表す特定の方法があります)。
StaxMan 2010

46

この機能を使用してこの種の問題を処理するには、GoogleJSONに依存しています

シリアル化と逆シリアル化からのフィールドの除外

次のように、AクラスとBクラスの間に双方向の関係があるとします。

public class A implements Serializable {

    private B b;

}

そしてB

public class B implements Serializable {

    private A a;

}

次に、GsonBuilderを使用して、次のようにカスタムGsonオブジェクトを取得します(setExclusionStrategiesメソッドに注意してください)。

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);

GsonBuilderクラスを見てください


アーサーに親切な提案をありがとう。しかし実際の質問は、いわゆる一般的な「shouldSkipClass」メソッドを構築するための最良の方法は何かということです。今のところ、私はマットのアイデアに取り組み、問題を解決しましたが、それでも懐疑的です。将来、このソリューションは認証シナリオで機能しなくなる可能性があります。
WSK 2010

9
これは、循環参照を削除することで「解決」します。生成されたJSONから元のデータ構造を再構築する方法はありません。
Sotirios Delimanolis 2015

34

Jackson 1.6(2010年9月にリリース)には、このような親子リンクを処理するための特定の注釈ベースのサポートがあります。http: //wiki.fasterxml.com/JacksonFeatureBiDirReferencesを参照してください。(ウェイバックスナップショット

もちろん、ほとんどのJSON処理パッケージ(jackson、gson、flex-jsonは少なくともそれをサポートしています)をすでに使用している親リンクのシリアル化をすでに除外できますが、本当のトリックはそれを逆シリアル化する方法(親リンクを再作成する)にあります。シリアル化側を処理するだけです。今のところのように聞こえますが、除外するだけでうまくいくかもしれません。

編集(2012年4月):Jackson 2.0は真のID参照ウェイバックスナップショット)をサポートするようになったため、この方法でも解決できます。


方向が常に同じではない場合にこれを機能させる方法..両方のフィールドに両方の注釈を付けようとしましたが、機能しませんでした:クラスA {@JsonBackReference( "abc")@ JsonManagedReference( "xyz")プライベートBb; }クラスB {@JsonManagedReference( "abc")@ JsonBackReference( "xyz")private A a; }
azi 2014

上記のように、オブジェクトID(@JsonIdentityInfo)は、一般的な参照を機能させる方法です。マネージド/バックリファレンスには特定の指示が必要なため、ケースでは機能しません。
StaxMan 2014

12

この問題に対処する際に、私は次のアプローチを取りました(アプリケーション全体でプロセスを標準化し、コードを明確で再利用可能にします)。

  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);

1
これにより、子オブジェクトが処理されなくなります。子jsonを含めて、周期性を停止できますか
ir2pid

3

あなたはシリアライズするJackonを使用している場合は、単に適用@JsonBackReferenceをごバイdirectinalマッピングするためには、循環参照の問題を解決します。

注:@JsonBackReferenceは、無限再帰(StackOverflowError)を解決するために使用されます


@JsonIgnoreJpaRepositoryプロパティのマッピングに失敗しましたが@JsonBackReference、循環参照を解決し、問題のある属性の適切なマッピングを許可しました
Bramastic 2017

2

するのではなく、アーサーさんに似ていますが、解決策を使用しsetExclusionStrategies、私が使用

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

@Exposejsonで必要なフィールドにgsonアノテーションを使用した場合、他のフィールドは除外されます。


ありがとう、相棒。これは私のために働いています。長い研究の末、私はついにこのエラーから抜け出す方法を見つけました。
kepy 9718年

2

Spring Bootを使用している場合、循環/双方向データからの応答の作成中にJacksonがエラーをスローするため、

@JsonIgnoreProperties

真円度を無視する

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

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

1

Javascriptを使用している場合はreplacerJSON.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名前付き参照を使用している場合は、実際のオブジェクトを指すようにプロパティを変更することで、循環参照を使用して実際のオブジェクトを簡単に再作成できます@


1

これが私の場合、最終的にそれを解決した方法です。これは、少なくとも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;
} 

0

このエラーは、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キーワードは、フィールドをシリアル化してはならないことを示すために使用されます。



0

GSONを使用してJSONでJavaクラスを変換する場合、循環参照と無限ループの原因となるフィールドを回避できます。JSONに表示するフィールドに注釈@Exposeを配置するだけで、アノテーション@ExposeはJSONに表示されません。

循環参照は、たとえば、クラスUserをクラスRouteのフィールドroutesでシリアル化しようとし、クラスRouteにクラスUserのフィールドuserがある場合、GSONはクラスUserをシリアル化しようとし、ルートをシリアル化しようとすると、クラスRouteをシリアル化し、クラスRouteでフィールドuserをシリアル化しようとし、クラス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でクラスUserをシリアル化した結果をJSON形式で表示します。

{
    "name": "ignacio"  
}

フィールドルートなどはJSON形式ではなく、フィールド名のみが存在することがわかります。このため、循環参照は避けられます。

これを使用する場合は、特定の方法でオブジェクトGSONを作成する必要があります。

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

最後に、作成された会話者GSONを使用して、休止状態のユーザーのモデルのJavaクラスを変換します。

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

-3

答え番号8の方が良いと思います。したがって、どのフィールドがエラーをスローしているのかがわかっている場合は、fildを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.lang.StackOverflowError [リクエスト処理に失敗しました; ネストされた例外はorg.springframework.http.converter.HttpMessageNotWritableExceptionです:JSONを書き込めませんでした:無限再帰(StackOverflowError)(参照チェーンを介して:com.service.pegazo.bo.RequestMessageProfessional ["requestMessage"]-> com.service.pegazo。 bo.RequestMessage ["requestMessageProfessionals"]


1
「回答番号8」はありません。参照された回答の作成者の名前を指定することをお勧めします。ここに投稿したテキストは読めませんでした。回答がどのように投稿されているかを見て、きれいにレイアウトしてください。最後に、これが元の質問にどのように答えるかわかりません。答えを説明するために詳細を追加してください。
AdrianHHH 2015年

-11

たとえば、ProductBeanにはserialBeanがあります。マッピングは双方向の関係になります。ここでを使用しようとするとgson.toJson()、循環参照になります。この問題を回避するには、次の手順に従います。

  1. データソースから結果を取得します。
  2. リストを繰り返し、serialBeanがnullでないことを確認してから、
  3. セットする productBean.serialBean.productBean = null;
  4. 次に、使用してみてください gson.toJson();

それは問題を解決するはずです

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.