Java 如何解决hibernate双向映射导致的json序列化器中的循环引用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3340485/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 22:26:46  来源:igfitidea点击:

How to solve circular reference in json serializer caused by hibernate bidirectional mapping?

javahibernatejsonserialization

提问by WSK

I am writing a serializer to serialize POJO to JSON but stuck in circular reference problem. In hibernate bidirectional one-to-many relation, parent references child and child references back to parent and here my serializer dies. (see example code below)
How to break this cycle? Can we get owner tree of an object to see whether object itself exists somewhere in its own owner hierarchy? Any other way to find if the reference is going to be circular? or any other idea to resolve this problem?

我正在编写一个序列化程序来将 POJO 序列化为 JSON,但遇到了循环引用问题。在休眠双向一对多关系中,父引用子引用和子引用回到父引用,这里我的序列化器死了。(见下面的示例代码)
如何打破这个循环?我们可以获取对象的所有者树以查看对象本身是否存在于其自己的所有者层次结构中的某处吗?任何其他方法来查找引用是否将是循环的?或任何其他想法来解决这个问题?

采纳答案by matt b

Can a bi-directional relationship even be represented in JSON? Some data formats are not good fits for some types of data modelling.

甚至可以用 JSON 表示双向关系吗?某些数据格式不适合某些类型的数据建模。

One method for dealing with cycles when dealing with traversing object graphs is to keep track of which objects you've seen so far (using identity comparisons), to prevent yourself from traversing down an infinite cycle.

在处理遍历对象图时处理循环的一种方法是跟踪您到目前为止看到的对象(使用身份比较),以防止自己遍历无限循环。

回答by Arthur Ronald

I rely on Google JSONTo handle this kind of issue by using The feature

我依靠Google JSON通过使用该功能来处理此类问题

Excluding Fields From Serialization and Deserialization

从序列化和反序列化中排除字段

Suppose a bi-directional relationship between A and B class as follows

假设A和B类之间的双向关系如下

public class A implements Serializable {

    private B b;

}

And B

和乙

public class B implements Serializable {

    private A a;

}

Now use GsonBuilder To get a custom Gson object as follows (Notice setExclusionStrategiesmethod)

现在使用 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();

Now our circular reference

现在我们的循环引用

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

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

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

Take a look at GsonBuilderclass

看看GsonBuilder

回答by StaxMan

Hymanson1.6 (released september 2010) has specific annotation-based support for handling such parent/child linkage, see http://wiki.fasterxml.com/HymansonFeatureBiDirReferences. (Wayback Snapshot)

Hymanson1.6(2010 年 9 月发布)对处理此类父/子链接具有特定的基于注释的支持,请参阅http://wiki.fasterxml.com/HymansonFeatureBiDirReferences。(回溯快照

You can of course already exclude serialization of parent link already using most JSON processing packages (Hymanson, gson and flex-json at least support it), but the real trick is in how to deserialize it back (re-create parent link), not just handle serialization side. Although sounds like for now just exclusion might work for you.

您当然可以排除已经使用大多数 JSON 处理包(Hymanson、gson 和 flex-json 至少支持它)的父链接的序列化,但真正的技巧是如何反序列化它(重新创建父链接),而不是只是处理序列化方面。虽然现在听起来只是排除可能对你有用。

EDIT (April 2012): Hymanson 2.0now supports true identity references(Wayback Snapshot), so you can solve it this way also.

编辑(2012 年 4 月):Hymanson 2.0现在支持真实身份引用Wayback Snapshot),因此您也可以通过这种方式解决。

回答by Don Bosco R

For example, ProductBean has got serialBean. The mapping would be bi-directional relationship. If we now try to use gson.toJson(), it will end up with circular reference. In order to avoid that problem, you can follow these steps:

例如,ProductBean 有 serialBean。映射将是双向关系。如果我们现在尝试使用gson.toJson(),它将以循环引用告终。为了避免这个问题,您可以按照以下步骤操作:

  1. Retrieve the results from datasource.
  2. Iterate the list and make sure the serialBean is not null, and then
  3. Set productBean.serialBean.productBean = null;
  4. Then try to use gson.toJson();
  1. 从数据源检索结果。
  2. 迭代列表并确保serialBean 不为空,然后
  3. productBean.serialBean.productBean = null;
  4. 然后尝试使用 gson.toJson();

That should solve the problem

那应该可以解决问题

回答by eugene

In addressing this problem, I took the following approach (standardizing the process across my application, making the code clear and reusable):

在解决这个问题时,我采用了以下方法(在我的应用程序中标准化流程,使代码清晰可重用):

  1. Create an annotation class to be used on fields you'd like excluded
  2. Define a class which implements Google's ExclusionStrategy interface
  3. Create a simple method to generate the GSON object using the GsonBuilder (similar to Arthur's explanation)
  4. Annotate the fields to be excluded as needed
  5. Apply the serialization rules to your com.google.gson.Gson object
  6. Serialize your object
  1. 创建一个注释类以用于您想要排除的字段
  2. 定义一个实现 Google 的 ExclusionStrategy 接口的类
  3. 创建使用GsonBuilder生成GSON对象的简单方法(类似于Arthur的解释)
  4. 根据需要注释要排除的字段
  5. 将序列化规则应用于您的 com.google.gson.Gson 对象
  6. 序列化你的对象

Here's the code:

这是代码:

1)

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)

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)

3)

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

4)

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)

5)

In the first case, null is supplied to the constructor, you can specify another class to be excluded - both options are added below

在第一种情况下,向构造函数提供 null,您可以指定要排除的另一个类 - 下面添加了两个选项

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

6)

6)

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

or, to exclude the Date object

或者,排除 Date 对象

String jsonRepresentation = _gsonObj.toJson(_myobject);

回答by abhishekcghosh

If you are using Javascript, there's a very easy solution to that using the replacerparameter of JSON.stringify()method where you can pass a function to modify the default serialization behavior.

如果您使用的是 Javascript,有一个非常简单的解决方案,使用方法的replacer参数JSON.stringify(),您可以在其中传递一个函数来修改默认序列化行为。

Here's how you can use it. Consider the below example with 4 nodes in a cyclic graph.

这是您如何使用它。考虑以下循环图中有 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);
}

Later, you can easily recreate the actual object with the cyclic references by parsing the serialized data and modifying the nextproperty to point to the actual object if it's using a named reference with a @like in this example.

稍后,您可以通过解析序列化数据并修改next属性以指向实际对象(如果它@在此示例中使用带有类似名称的命名引用)轻松地重新创建具有循环引用的实际对象。

回答by gndp

Used a solution similar to Arthur's but instead of setExclusionStrategiesI used

使用了类似于 Arthur 的解决方案,但setExclusionStrategies我没有使用

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

and used @Exposegson annotation for fields which I need in the json, other fields are excluded.

@Expose为我在 json 中需要的字段使用了gson 注释,其他字段被排除在外。

回答by MarcelCH

the answer number 8 is the better, i think so if you know what field is throwing a error the you only set the fild in null and solved.

答案数字 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); // **
        }
    }

To make the code readable a big comment is moved from the comment // **to below.

为了使代码可读,将大注释从注释// **移到下面。

java.lang.StackOverflowError [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError) (through reference chain: com.service.pegazo.bo.RequestMessageProfessional["requestMessage"]->com.service.pegazo.bo.RequestMessage["requestMessageProfessionals"]

java.lang.StackOverflowError [请求处理失败;嵌套异常是 org.springframework.http.converter.HttpMessageNotWritableException:无法写入 JSON:无限递归(StackOverflowError)(通过参考链:com.service.pegazo.bo.RequestMessageProfessional["requestMessage"]->com.service.pegazo。 bo.RequestMessage["requestMessageProfessionals"]

回答by Kevin ABRIOUX

This error can appened when you have two objects :

当您有两个对象时,可能会出现此错误:

class object1{
    private object2 o2;
}

class object2{
    private object1 o1;
}

With using GSon for serialization, i have got this error :

使用 GSon 进行序列化时,出现此错误:

java.lang.IllegalStateException: circular reference error

Offending field: o1

To solved this, just add key word transient :

要解决这个问题,只需添加关键字瞬态:

class object1{
    private object2 o2;
}

class object2{
    transient private object1 o1;
}

As you can see here : Why does Java have transient fields?

正如您在这里看到的:为什么 Java 有瞬态字段?

The transient keyword in Java is used to indicate that a field should not be serialized.

Java 中的transient 关键字用于指示不应序列化字段。

回答by pirho

This is how i finally solved it in my case. This works at least with Gson & Hymanson.

这就是我最终在我的情况下解决它的方式。这至少适用于 Gson & Hymanson。

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