JPA ManyToMany与Eclipselink& GSON循环引用导致服务器重启后堆栈溢出

时间:2014-01-24 18:43:31

标签: jpa reference gson many-to-many circular-reference

我遇到了许多双向JPA映射问题,导致堆栈溢出错误 虽然我的应用程序中总共有5个实体,但我认为该问题仅与其中的2个实体有关,为简单起见,这些实体将在下面进行描述。

实体:

申请

用户

应用程序可以有很多开发人员

用户可以开发许多应用程序

以下是它们的映射方式:

//应用程序实体:

@ManyToMany
private List<Users> users

//用户实体

@ManyToMany(mappedBy = "users", fetch = FetchType.LAZY)

private List <Applications> applications;

这导致在数据库中创建了3个表:

申请&lt; ID APPNAME

APPLICATION_USERS&lt;连接表包含USERS_ID APPLICATIONS_ID

USERS&lt; ID USER NAME

这是一个循环引用,一直运行直到堆栈溢出。

首次部署空表时,应用程序正常工作。 用户注册应用程序,这会在APPLICATIONS表中创建一行(如果应用程序不存在) 还会在USERS表中创建一行(如果用户不存在)和连接表APPLICATION_USERS 使用名为APPLICATIONS_ID和的APPLICATION表中的ID填充 来自USERS表的ID称为USERS_ID。

您可以根据需要添加任意数量的应用程序或用户,并且应用程序可以完美运行。 我已经验证数据正在加载并完全按预期保存到3个表中 以下是用户注册应用程序后表中数据的示例:

申请

ID    51

APPLICATION_USERS
 USERS_ID APPLICATIONS_ID

1       51

USERS
   ID    1

现在,当服务器停止并重新启动或使用create-tables重新部署应用程序时 (vs drop-and-create-tables)(并且数据存在于表中)然后我在每个实体toString()函数中得到堆栈溢出。 我已经在Applications toString()函数和Users toString()函数的调试中使用断点运行了这个,我可以单击resume并观察每个toString()函数被反复调用直到 堆栈溢出结果。

这是控制台日志:

(正在执行的实体查询)

  

[EL Fine]:2014-01-21   14:48:44.383 - 的ServerSession(1615948530) - 连接(49767657) - 线程(线程[HTTP-BIO-8080-EXEC-9,5,主]) - 选择   t1.ID,t1.APPIDENTIFIER,t1.DATECREATED,t1.DATEMODIFIED,   t1.DEVICETYPE FROM APPLICATIONS_Users t0,APPLICATIONS t1 WHERE   ((t0.users_ID =?)AND(t1.ID = t0.applications_ID))

(调用第二个实体查询)

  

[EL Fine]:2014-01-21   14:50:02.444 - 的ServerSession(1615948530) - 连接(1871047709) - 线程(线程[HTTP-BIO-8080-EXEC-9,5,主]) - 选择   t1.ID,t1.DATECREATED,t1.DATEMODIFIED,t1.EMAIL,t1.FIRSTNAME,   t1.FULLNAME,t1.LASTLOGINDATE,t1.LASTNAME,t1.USERNAME FROM   APPLICATIONS_Users t0,用户t1 WHERE((t0.applications_ID =?)AND   (t1.ID = t0.users_ID))

     

[EL最佳]:2014-01-21   14:50:02.471 - 的ServerSession(1615948530) - 连接(1601422824) - 线程(线程[HTTP-BIO-8080-EXEC-9,5,主]) - 连接   发布到连接池[read]。

     

java.lang.StackOverflowError的

   at java.util.Vector.get(Vector.java:693)

   at java.util.AbstractList$Itr.next(AbstractList.java:345)

   at java.util.AbstractCollection.toString(AbstractCollection.java:421)

   at java.util.Vector.toString(Vector.java:940)

   at org.eclipse.persistence.indirection.IndirectList.toString(IndirectList.java:797)

   at java.lang.String.valueOf(String.java:2826)

   at java.lang.StringBuilder.append(StringBuilder.java:115)

   at com.sap.crashlogserver.dao.entities.Applications.toString(Applications.java:150)

   at java.lang.String.valueOf(String.java:2826)

   at java.lang.StringBuilder.append(StringBuilder.java:115)

   at java.util.AbstractCollection.toString(AbstractCollection.java:422)

   at java.util.Vector.toString(Vector.java:940)

   at org.eclipse.persistence.indirection.IndirectList.toString(IndirectList.java:797)

   at java.lang.String.valueOf(String.java:2826)

   at java.lang.StringBuilder.append(StringBuilder.java:115)

   at com.sap.crashlogserver.dao.entities.Users.toString(Users.java:168)

   at java.lang.String.valueOf(String.java:2826)

   at java.lang.StringBuilder.append(StringBuilder.java:115)

   at java.util.AbstractCollection.toString(AbstractCollection.java:422)

   at java.util.Vector.toString(Vector.java:940)

   at org.eclipse.persistence.indirection.IndirectList.toString(IndirectList.java:797)

   at java.lang.String.valueOf(String.java:2826)

   at java.lang.StringBuilder.append(StringBuilder.java:115)

   at com.sap.crashlogserver.dao.entities.Applications.toString(Applications.java:150)

   at java.lang.String.valueOf(String.java:2826)

   at java.lang.StringBuilder.append(StringBuilder.java:115)

   at java.util.AbstractCollection.toString(AbstractCollection.java:422)

   at java.util.Vector.toString(Vector.java:940)

根据我读过的一些帖子,我尝试过: 1.反转映射, 2.将@JsonIgnore添加到某些实体字段中 3.使用fetch = FetchType.LAZY

和许多其他配置调整但没有一个解决了这个问题。 一些建议,如使用瞬态字段。我不确定我的eclipselink的JPA实现是否支持。

我还读了一个线程,建议我实现gson.ExclusionStrategy。 还没有尝试过。

这就是故事。我是Java和JPA的新手。 这对我来说是一个非常困难的问题。 如果您有任何建议可以帮我解决,我们将不胜感激。

3 个答案:

答案 0 :(得分:1)

也许这对某人有帮助。

我遇到了同样的问题。我使用带有自定义@InvisibleJson的注释ExclusionStrategy来隐藏这些字段

public class SafeDataExclusionStrategy implements ExclusionStrategy {

    @Override
    public boolean shouldSkipClass(Class<?> c) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes c) {
        return c.getAnnotation(InvisibleJson.class) != null;
    }

}

@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface InvisibleJson {}

@InvisibleJson
@ManyToMany(mappedBy="roles")
public List<Staff> staff;

所以GSON并没有挖掘员工的内心。

答案 1 :(得分:0)

您的对象toString方法在整个模型上递归调用toString。堆栈最初没有显示它被调用的原因,但可能是由于某些日志记录设置。修复你的toString方法并关闭日志记录,

答案 2 :(得分:0)

非常简单的解决方案,对您希望允许GSON序列化/反序列化的所有变量和列表使用注释@Expose

一个基本上只允许GSON使用名称的简单示例:

@Expose
private String name;

@ManyToMany
private List<Users> users 

然后,在使用GsonBuilder的地方,执行以下操作:

GsonBuilder builder = new GsonBuilder().disableHtmlEscaping();
builder.excludeFieldsWithoutExposeAnnotation();  //<-- This tells GSON look for @Expose
gson = builder.create();

此外,您可以将@Expose放在列表中,但是您需要进入用户实体并确保将@Expose放在该实体中的东西上,以便GSON深入其中。