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

在这些日子里,我正在努力使用Hibernate实体和JSON,尽管有很多关于该对象的问题,但我还是无法在存在循环依赖性的情况下进行序列化。 我和Gson和jackson都试过了,但我没有取得很多进展。 这是我的对象的摘录。 这是“父”类。

@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 threads = new HashSet(0); //...other attributes, getters and setters } 

这就是“儿童”课程

 @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的特性; 如上所述,他们都提出exception。

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

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

 @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id") 

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

  private final ConstructorConstructor constructorConstructor = new ConstructorConstructor(); 

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

 ConstructorConstructor(Map, InstanceCreator instanceCreators) 

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

编辑1

我终于找到了jackson的问题。 在测试类中,我忘记初始化id字段(在实际场景中,它由数据库初始化),这就是exception的原因。 当我最终设置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" : [ ] } 


jackson

如上所述,我能够解决问题

 @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=MyEntity.class)` 

对于这里建议的每个实体。 scope属性对于确保名称“id”在范围内是唯一的是必需的。 实际上,没有scope属性,正如你在这里看到的那样,它会引发exception

 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

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

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

对于双向关联,当Parent具有一对多子项集合且子项具有对Parent的多对一引用时,您需要使用@JsonIgnore注释多对一方:

 @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序列化时间循环依赖性。

我用这种方式使用org.codehaus.jackson.annotate.JsonManagedReferenceorg.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 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; 

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

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