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

时间:2010-07-27 03:11:50

标签: java hibernate json serialization

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

14 个答案:

答案 0 :(得分:45)

我依靠Google JSON使用功能

来处理此类问题
  

Excluding Fields From Serialization and Deserialization

假设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班级

答案 1 :(得分:33)

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

你当然已经排除了已使用大多数JSON处理包(jackson,gson和flex-json至少支持它)的父链接的序列化,但真正的诀窍在于如何反序列化它(重新创建父链接) ),而不仅仅是处理序列化方面。虽然现在的声音只是排除可能对你有用。

编辑(2012年4月):Jackson 2.0现在支持真identity referencesWayback Snapshot),所以你也可以这样解决。

答案 2 :(得分:12)

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

  1. 创建要在您要排除的字段
  2. 上使用的注记类
  3. 定义一个实现Google的ExclusionStrategy界面的类
  4. 创建一个使用GsonBuilder生成GSON对象的简单方法(类似于Arthur的解释)
  5. 根据需要注释要排除的字段
  6. 将序列化规则应用于com.google.gson.Gson对象
  7. 序列化您的对象
  8. 以下是代码:

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

答案 3 :(得分:10)

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

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

答案 4 :(得分:3)

如果您使用Jackon进行序列化,只需将 @JsonBackReference 应用于双向直接映射 它将解决循环引用问题。

注意:@JsonBackReference用于解决无限递归(StackOverflowError)

答案 5 :(得分:2)

使用类似于Arthur的解决方案,但我使用

而不是setExclusionStrategies
Gson gson = new GsonBuilder()
                .excludeFieldsWithoutExposeAnnotation()
                .create();

并对json中需要的字段使用@Expose gson注释,其他字段被排除。

答案 6 :(得分: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;

答案 7 :(得分:1)

如果您使用的是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属性以指向实际对象(如果它使用带有{{的命名引用),您可以轻松地使用循环引用重新创建实际对象1}}就像在这个例子中一样。

答案 8 :(得分:1)

这就是我在我的案例中最终解决它的方式。这至少适用于Gson&amp;杰克逊。

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

答案 9 :(得分:0)

如果您有两个对象,则会出现此错误:

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

正如您在此处所见:Why does Java have transient fields?

  

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

答案 10 :(得分:0)

Jackson提供JsonIdentityInfo注释以防止循环引用。您可以查看教程here

答案 11 :(得分:0)

如果使用GSON在JSON中转换Java类,则可以避免导致循环引用和不定式循环的字段,只需在要显示在JSON中的字段中放置@Expose注释,然后没有注释@Expose的字段不会出现在JSON中。

例如,如果我们尝试使用类Route的字段路由序列化User类,并且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注释。

我以JSON格式显示通过GSON类User进行序列化的结果。

{
    "name": "ignacio"  
}

我们可以看到,字段路由和点赞不存在JSON格式,只有字段名称。因此,避免使用循环引用。

如果要使用它,则必须以特定方式创建对象GSON。

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

最后,我们使用创建的转换器GSON转换休眠用户模型的java类。

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

答案 12 :(得分:-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”] - &gt; com.service.pegazo .bo.RequestMessage [ “requestMessageProfessionals”]

答案 13 :(得分:-11)

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

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