Hibernate和JSON - 是否存在循环依赖的最终解决方案?

时间:2014-10-30 15:36:28

标签: java json hibernate jackson gson

我现在正在与Hibernate实体和JSON斗争,尽管有很多关于该对象的问题,但我还是无法在存在循环依赖的情况下进行序列化。我和Gson以及杰克逊都试过了,但我没有取得很多进展。 这是我的对象的摘录。 这是"父母"类。

@Entity
public class User extends RecognizedServerEntities implements java.io.Serializable
{
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = false)
    @Cascade({CascadeType.SAVE_UPDATE})
    private Set<Thread> threads = new HashSet<Thread>(0);
    //...other attributes, getters and setters
}

这是&#34;孩子&#34;类

@Entity
@Table(name = "thread")
public class Thread extends RecognizedServerEntities implements java.io.Serializable
{
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author", nullable = true)
    private User user;
    //...other attributes, getters and setters
}

我写了一个简单的类来测试gson和jackson的特性;如上所述,他们都提出异常。

public class MyJsonsTest
{
    private static User u;
    public static void main(String[] args)
    {
        u = new User("mail", "password", "nickname", new Date());
        u.setId(1); // Added with EDIT 1
    //  testGson();
        testJackson();
    }

    private static void testJackson()
    {
        Thread t = new Thread("Test", u, new Date(), new Date());
        t.setId(1); // Added with EDIT 1
        u.getThreads().add(t);

        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        try
        {
            mapper.writeValue(new File("result.json"), u);
        }
        catch {/[various exceptions catched, but a JsonMappingException was thrown]}
    }

    private static void testGson()
    {
        Gson gson = new Gson();
        System.out.println(u.toString());
        System.out.println(gson.toJson(u, User.class));

        Thread t = new Thread("Test", u, new Date(), new Date());
        u.getThreads().add(t);

        //This raise an exception overflow
        System.out.println(gson.toJson(u, User.class));
    }
}

为了解决这个问题,在杰克逊方面,我试图使用这个注释

@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
用户和线程类上的

。但是,它并没有解决问题。 在gson方面,我读到了GraphAdapterBuilder类,但我无法正确使用它。我找不到任何jar,所以我从here复制/粘贴了源代码。但是,此行存在编译时错误

 private final ConstructorConstructor constructorConstructor = new ConstructorConstructor();

因为ConstructorConstructor()未定义;正确的语法应该是

ConstructorConstructor(Map<Type>, InstanceCreator<?> instanceCreators)

那么,这个问题是否有明确的解决方案?显然,我无法使用transient变量。

编辑1

我终于找到了杰克逊的问题。在测试类中,我忘记初始化id字段(在实际场景中,它由数据库初始化),这就是异常的原因。当我最终设置id时,一切正常。这是输出

{
  "id" : 1,
  "email" : "mail",
  "password" : "password",
  "nick" : "nickname",
  "registeredDate" : 1414703168409,
  "threads" : [ {
    "id" : 1,
    "thread" : null,
    "user" : 1,
    "title" : "Test",
    "lastModifiedDate" : 1414703168410,
    "createdDate" : 1414703168410,
    "messages" : [ ],
    "threads" : [ ]
  } ],
  "messages" : [ ]
}

4 个答案:

答案 0 :(得分:3)


<强>杰克逊

如上所述,我能够使用

解决问题
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=MyEntity.class)` 
建议here的每个实体

scope属性是必要的,以确保名称&#34; id&#34;在范围内是独一无二的。实际上,如果没有scope属性,您可以看到here,它会抛出一个例外

com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey@3372bb3f] (through reference chain: ParentEntity["children"]->java.util.ArrayList[0]->ChildEntity["id"])
...stacktrace...
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey@3372bb3f]
...stacktrace...

<强> GSON

我仍然没有找到一种干净的方法来序列化循环依赖。

答案 1 :(得分:2)

在处理循环依赖时,您需要构建父子JSON层次结构,因为编组必须从根级联到最内层子级。

对于双向关联,当Parent具有一对多子项集合且子项具有对Parent的多对一引用时,您需要使用{{3注释多对一端。 }}:

@Entity
@Table(name = "thread")
public class Thread extends RecognizedServerEntities implements java.io.Serializable
{
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;

    @org.codehaus.jackson.annotate.JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author", nullable = true)
    private User user;
    //...other attributes, getters and setters
}

这样您将不再具有Json序列化时间循环依赖。

答案 2 :(得分:1)

我是用这种方式使用 org.codehaus.jackson.annotate.JsonManagedReference org.codehaus.jackson.annotate.JsonBackReference 完成的...

看看我如何使用 @JsonManagedReference

 @Id
 @TableGenerator(name="CatId", table="catTable",pkColumnName="catKey",pkColumnValue="catValue", allocationSize=1)
 @GeneratedValue(strategy=GenerationType.TABLE, generator="CatId")
 @Column(name = "CategId", unique = true, nullable = false)
 private long categoryId;
 private String productCategory;
 @JsonManagedReference("product-category")
 @OneToMany(targetEntity=ProductDatabase.class,mappedBy="category", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
 private List<ProductDatabase> catProducts;

然后在另一端使用 @JsonBackReference ,如下所示。

@Id@GeneratedValue
private int productId;
private String description;
private int productPrice;
private String productName;
private String ProductImageURL;
@JsonBackReference("product-category")
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "CategId")
private Category category;

只需应用这些注释并检查它是否适合您。

答案 3 :(得分:0)

将Hibernate POJO序列化为客户端并不是一个好的设计。因为您可能会将一些数据发送到客户端位置,而这些位置并未授权他查看。您应该创建客户端POJO并将数据从休眠POJO复制到您要发送给客户端的客户端POJO。如果您不想这样做,可以使用@JsonIgnore或热切地获取所有数据。