【问题标题】:Hibernate and JSON - is there a definitive solution to circular dependencies?Hibernate 和 JSON - 循环依赖是否有明确的解决方案?
【发布时间】:2014-12-26 17:53:49
【问题描述】:

这些天来,我一直在与 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<Thread> threads = new HashSet<Thread>(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 的特性;如前所述,它们都引发了异常。

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

在 User 和 Thread 类上。但是,它并不能解决问题。 在 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" : [ ]
}

【问题讨论】:

  • 在我的例子中,我创建了特定的 DTO 来将存储在 Hibernate 对象中的数据传输到 JSON,并且在我的设计中避免了这种双向关系。或者,另一方面,我根本不使用带有 JSON 的 Hibernate。
  • 为什么关系是“用户”映射的一种方式而“作者”映射的另一种方式?
  • @LuiggiMendoza 你用过哪种 DTO?
  • 我创建了一个新的 Hibernate/JPA/Any-other-framework-less annotated 类,一个简单的 POJO,具有通信所需的字段。这是更多的工作,你甚至可以说是重复的努力,但这是你在尝试使用这些技术时假设的一种权衡。另一种解决方案是使用适当的设计,您的对象中不涉及循环引用。
  • @LuiggiMendoza 感谢您的帮助。实际上,我用其他简单的类做了一些测试,确实它似乎工作。另外,我想知道我的实体的问题是否与某种“内部参考”有关;我的意思是,我的Thread 实体链接到一组子线程。因此,我还进行了一些其他测试,将ParentEntity(我使用链接中提供的名称)字段List&lt;ParentEntity&gt; parentEntitiesParentEntity parentEntity 添加到。再次,测试通过了。所以,我必须密切关注我的班级。

标签: java json hibernate jackson gson


【解决方案1】:

杰克逊

如前所述,我能够使用

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

对于建议的每个实体herescope 属性是必要的,以确保名称“id”在范围内是唯一的。实际上,没有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

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

【讨论】:

【解决方案2】:

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

对于双向关联,当Parent有一对多的children集合并且child有对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 序列化时间循环依赖。

【讨论】:

  • 感谢您的帮助 :) 我使用 @JsonIdentityInfo 部分解决了问题,正如您从其中一个答案中看到的那样。那么,您认为这里的最佳选择是什么?在这种情况下,为什么 JsonIgnore 比 JsonIdentityInfo 更可取?这两种选择有哪些优点/缺点?
  • 只要它忽略重复的信息,两种解决方案都会产生相同的结果。
  • 当然,我问是因为我遇到了其他问题,其中 JsonIdentityInfo 无法按预期工作,而 JsonIgnore 无法使用,因为“nullable=false”选项。我认为其中一个的使用存在特定问题。
【解决方案3】:

我以这种方式使用 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<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;

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

【讨论】:

    【解决方案4】:

    将 Hibernate POJO 序列化到客户端不是很好的设计。因为您可能会将一些数据发送到客户位置,而他无权查看这些数据。您应该创建客户端 POJO 并将数据从休眠 POJO 复制到客户端 POJO,然后将其发送给客户端。如果你不想这样做,你可以使用@JsonIgnore 或 Eagerly Fetch all data。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-21
      • 1970-01-01
      • 2020-11-12
      相关资源
      最近更新 更多