我们最近遇到了JPA和@ManyToOne / @OneToMany Relations的查询问题,导致堆栈溢出。但这仅在应用程序服务器重新启动后才会发生,因为已创建实体
问题: 当我通常使用部署的war文件启动我的应用程序服务器时,我可以用一些内容填充我的数据库并查询它,无论如何。 但是,当我重新启动服务器而不清除数据库并且我尝试执行相同的查询时,我得到了奇怪的行为:
我收到了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;
这意味着,父母知道他们的孩子,反之亦然,但至少应该使用retentionPolicy来避免典型的GSON Stackoverflow,因为父母被排除在外。
我不知道整个问题是否与JPA或GSON有关,但真正让我怀疑的是,为什么这只会在服务器重启后发生。它表示某种会话问题,我无法弄清楚,我没有找到任何其他线程或问题,所以我只会问这里。
我知道有懒惰和急切的提取类型,但使用它们无法解释,为什么这个问题只出现在服务器重启时。
非常感谢, 凯
答案 0 :(得分:2)
GSON不支持周期,所以这很可能是你的原因。我认为在GSON中避免循环的正常方法是使变量成为瞬态,我没有听说@Retention(RetentionPolicy.RUNTIME)工作,也许它确实...
您可能没有维护双向关系(这是非常错误的),因此在清除共享缓存之前不要有循环。
您还可以尝试禁用缓存,或禁用编织以缩小问题范围。
您可能想尝试其他JSON序列化程序,例如EclipseLink Moxy,它使用JAXB注释并支持循环。
这里有一个例子,http://java-persistence-performance.blogspot.com/2013/08/optimizing-java-serialization-java-vs.html
答案 1 :(得分:1)
好的,我终于找到了答案,找到它很痛苦:
在Mission Entity中,我将孩子和父母留在了Collections中。然而,其中只有一个是由GSON序列化的,这个&#34;父母&#34;被排除在外(@GsonExclude = @Retention(RetentionPolicy.RUNTIME)&amp; @Target(ElementType.FIELD),我刚刚在前面明确提到过它们。)
以下代码是&#34;错误&#34;任务实体的版本:
@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;
还有其他人看到了这个问题吗? &#34; mappedBy&#34;映射错误的引用。当然,在尝试检索任务的孩子时,我需要将其映射到父母的父母身上。字段,以查找给定任务是父项的所有条目。这同样适用于&#34;父母&#34;。检索所有父母我需要找到地图Mission2Mission实体,其中&#34;任务&#34; (孩子)就是这个。
上面的代码反之亦然,导致可能的inf循环,因为Mission2Mission实体(见上文)反过来做了(它真的排除了父代而不是子代)。
交换映射字段(任务和父级)就像一个魅力。花了很长时间才搞清楚。
无论如何,谢谢你的输入。缓存提示在这里主要提高了调试速度:)
答案 2 :(得分:0)
通常,“通过网络”发送JPA实体并不是一个好主意,原因有很多(性能,安全性,上面的循环引用问题等)。因此,最好使用DTO对象。关于这一点,有很多线索,例如:What is a good strategy for converting jpa entities into restful resources。