如何解决hibernate双向映射引起的JSON串行器中的循环引用?

我正在编写序列化程序来将POJO序列化为JSON,但是却陷入了循环引用问题。 在hibernate双向一对多关系中,父类引用子对象和子对象引用,回到父对象,这里我的序列化器就死掉了。 (请参阅下面的示例代码)
如何打破这个循环? 我们可以得到一个对象的所有者树来查看对象本身是否存在于它自己的所有者层次中的某个地方? 任何其他方式来find参考是否将是循环? 或任何其他想法来解决这个问题?

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

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

我依靠Google JSON通过使用该function来处理这类问题

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

假设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类

Jackson 1.6(2010年9月发布)具有特定的基于注释的支持来处理此类父/子链接,请参阅http://wiki.fasterxml.com/JacksonFeatureBiDirReferences 。 ( Wayback快照 )

你当然已经可以排除已经使用大多数JSON处理包(jackson,gson和flex-json至less支持它)的父链接的序列化,但真正的技巧是如何反序列化它(重新创build父链接),而不是只是处理序列化的一面。 虽然听起来像现在只是排除可能会为你工作。

编辑(2012年4月): jackson2.0现在支持真正的身份参考 ( Wayback快照 ),所以你可以这样解决这个问题。

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

  1. 创build一个注解类,用于您想要排除的字段
  2. 定义一个实现Google ExclusionStrategy界面的类
  3. 创build一个简单的方法来使用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用于解决无限recursion(StackOverflowError)

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

以下是如何使用它。 考虑下面的例子,在循环图中有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); } 

稍后,通过parsing序列化的数据并修改next属性指向实际的对象,如果在本例中使用带@的命名引用,则可以使用循环引用轻松地重新创build实际对象。

使用类似于亚瑟的解决scheme,而不是我使用的setExclusionStrategies

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

并使用@Expose gson注解字段,我需要在JSON中,其他字段被排除在外。

这就是我终于解决了这个问题。 这至less与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; } 

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

 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关键字用于表示一个字段不应被序列化。

答案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 [请求处理失败; 嵌套exception是org.springframework.http.converter.HttpMessageNotWritableException:无法写入JSON:无限recursion(StackOverflowError)(通过引用链:com.service.pegazo.bo.RequestMessageProfessional [“requestMessage”] – > com.service.pegazo。 bo.RequestMessage [ “requestMessageProfessionals”]

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

  1. 从数据源中检索结果。
  2. 迭代列表并确保serialBean不为null,然后
  3. 设置productBean.serialBean.productBean = null;
  4. 然后尝试使用gson.toJson();

这应该可以解决问题