1

我们最近遇到了 JPA 和 @ManyToOne / @OneToMany 关系查询的问题,导致堆栈溢出。但这仅在创建实体后重新启动应用程序服务器时发生

问题:当我通常启动我的应用程序服务器并部署我的战争文件时,我可以用一些内容填充我的数据库并无论如何查询它。但是,当我在不清除数据库的情况下重新启动服务器并尝试执行相同的查询时,会出现奇怪的行为:

我得到一个 Stackoverflow 异常:

java.lang.StackOverflowError

at java.lang.StringBuffer.append(StringBuffer.java:224)

at java.io.StringWriter.write(StringWriter.java:84)

at java.io.StringWriter.append(StringWriter.java:126)

at java.io.StringWriter.append(StringWriter.java:24)

at com.google.gson.stream.JsonWriter.beforeValue(JsonWriter.java:610)

at com.google.gson.stream.JsonWriter.open(JsonWriter.java:317)

at com.google.gson.stream.JsonWriter.beginObject(JsonWriter.java:300)

at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:190)

at com.google.gson.Gson$FutureTypeAdapter.write(Gson.java:879)

at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)

at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)

at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)

[...]

为了完整起见:我的实体具有以下关系:

任务实体:

@OneToMany(mappedBy = "mission", cascade=CascadeType.ALL)  
private Collection<Mission2Mission> children;

@OneToMany(mappedBy = "parent", cascade=CascadeType.ALL)
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.FIELD)
private Collection<Mission2Mission> parents;

Mission2Mission 实体:

@ManyToOne  
@Retention(RetentionPolicy.RUNTIME)   
@Target(ElementType.FIELD)  
private Mission parent;  

@ManyToOne   
private Mission mission;

这意味着,父母知道他们的孩子,反之亦然,但至少应该通过使用保留策略来避免典型的 GSON Stackoverflow,因为父母被排除在外。

我不知道这整个问题是否与 JPA 或 GS​​ON 有关,但真正让我想知道的是,为什么这只会在服务器重新启动后发生。它表示某种会话问题,我无法弄清楚,我还没有找到关于这个特定问题的任何其他线程或问题,所以我就在这里问一下。

我知道有惰性和急切的获取类型,但使用它们并不能解释为什么这个问题只在服务器重新启动时出现。

非常感谢,凯

4

3 回答 3

2

GSON 不支持循环,因此这很可能是您的原因。我认为在 GSON 中避免循环的正常方法是使变量瞬态,我没有听说过 @Retention(RetentionPolicy.RUNTIME) 工作,也许它确实......

您可能没有维护双向关系(这是非常错误的),因此在清除共享缓存之前不要有循环。

您还可以尝试禁用缓存,或禁用编织以缩小问题范围。

您可能想尝试其他 JSON 序列化程序,例如 EclipseLink Moxy,它使用 JAXB 注释并支持循环。

这里有一个例子,http://java-persistence-performance.blogspot.com/2013/08/optimizing-java-serialization-java-vs.html

于 2013-08-29T14:36:19.627 回答
1

好的,我终于找到了答案,找到它很痛苦:

在 Mission Entity 中,我在 Collections 中维护了孩子和父母。但是,其中只有一个要被 GSON 序列化,这个“父母”被排除在外(@GsonExclude = @Retention(RetentionPolicy.RUNTIME)& @Target(ElementType.FIELD),我之前只是为了澄清而明确提到了它们)。

下面的代码是 Mission-Entity 的“错误”版本:

@OneToMany(mappedBy = "mission", fetch=FetchType.LAZY, cascade=CascadeType.ALL)@XmlTransient 
private Collection<Mission2Mission> children;

@OneToMany(mappedBy = "parent", fetch=FetchType.LAZY, cascade=CascadeType.ALL)@GsonExclude@XmlTransient
private Collection<Mission2Mission> parents;

还有其他人看到问题吗?“mappedBy”映射了错误的引用。当然,当尝试检索任务的子项时,我需要将其映射到“父”字段,以便找到所有条目,其中给定任务是父项。这同样适用于“父母”。检索我需要找到的所有父母地图 Mission2Mission 实体,其中“任务”(孩子)是这个。

上面的代码反之亦然,这会导致可能的 inf 循环,因为 Mission2Mission 实体(见上文)反之亦然(它确实排除了父级而不是子级)。

交换映射字段(任务和父级)就像一个魅力。花了很长时间才弄清楚。

无论如何,感谢您的输入。缓存提示在这里大大提高了调试速度:)

于 2013-08-30T14:10:14.620 回答
0

通常,出于多种原因(性能、安全性、上述循环引用问题等),“通过网络”发送 JPA 实体并不是一个好主意。因此,最好使用 DTO 对象。SO上有很多关于此的线程,例如这里:什么是将jpa实体转换为restful资源的好策略

于 2013-08-30T14:22:38.663 回答