【问题标题】:self-referencing entity in hibernate causes StackOverflowErrors休眠中的自引用实体导致 StackOverflowErrors
【发布时间】:2018-08-14 13:22:40
【问题描述】:

在我的一个 Java 实体 (MyState) 中,我引用了它自己。注释如下:

@OneToOne
@JoinColumn(name = "previousStateId", nullable = true,
        foreignKey = @ForeignKey(name = "fk_state_previousstate"))
private MyState previousState;

这曾经工作得很好,直到表格变大并且越来越多的状态相互指向。现在,当我尝试获取最新的 MyState 时,会发生 StackOverflowError,因为嵌套太深。还有什么比这里更好的网站来问我的问题? ;-)

我最终需要访问根状态(一长串状态中的第一个),但也需要访问前一个状态。

有什么办法可以避免得到所有的参考资料吗?我试图添加一个指向原始状态的新字段“rootState”。这当然是两个状态的短链,所以效果很好。

但是,我确实也需要以前的状态。我应该尝试通过将previousState 的previousState 设置为null 来手动断开链条,还是有更好的选择?

--- 编辑

我检查以确保在相互指向的状态中没有循环,没有。

部分堆栈跟踪:

Caused by: java.lang.StackOverflowError
    at com.mchange.v2.c3p0.impl.NewPooledConnection.handleThrowable(NewPooledConnection.java:492)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.getWarnings(NewProxyPreparedStatement.java:1045)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.handleAndClearWarnings(SqlExceptionHelper.java:317)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.logAndClearWarnings(SqlExceptionHelper.java:273)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.close(JdbcCoordinatorImpl.java:529)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.release(JdbcCoordinatorImpl.java:421)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:160)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:102)
    at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:186)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4126)
    at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:502)
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:467)
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:212)
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:258)
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:150)
    at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1070)
    at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:989)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:716)
    at org.hibernate.type.EntityType.resolve(EntityType.java:502)
    at org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:170)
    at org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:144)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:244)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:215)
    at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:140)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:138)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:102)
    at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:186)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4126)
    at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:502)
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:467)
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:212)
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:258)
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:150)
    .... and on and on and on....

【问题讨论】:

  • 这可能有助于您理解所有其他可用于表示 hierarchical-data stackoverflow.com/questions/4048151/… 的数据结构
  • 但是我认为你正在尝试的(Adjucency List)应该没有像StackOverflowError 这样的问题,除非你有循环依赖的数据不一致。确保你没有那个。是的,您必须有一个 root 条目,即 previousState 必须是 null
  • 经过进一步调查(又发生了),我确定没有循环。我还添加了堆栈跟踪的一部分

标签: java hibernate self-reference


【解决方案1】:

显然这是一个错误。 如果您有一个实体 Person 并且该实体具有对另一个 Person 的嵌套引用,则意味着嵌套的 Person 具有另一个嵌套链接,因此它会执行子嵌套链接,依此类推。您不必使用 Person 对象引用嵌套的 Person。请改用唯一 ID。

public class Person{
    private int idNestedPerson;
    //fields + getters/setters
}

现在您有两个选择:使用外键或保持原样。 如果您选择第一个选项,则必须映射该 nestedId,但我建议您开始使用第二个选项。这样,您就有了一个 id 引用“父亲”Person 的 Person。如果该 nestedId 为 != null 或 > 0(根据您分配给 id 的类型),则表示 Person 是嵌套的,相反,如果 id 未填充或 id 具有默认的 int 值为 0,则表示 Person是一个“根”,所以没有嵌套。详情请参阅“分层数据”。

【讨论】:

  • 引用嵌套记录的id的缺点是如果需要我必须手动从数据库中获取。这当然也是 hibernate 在后台所做的,因此在性能方面不会有任何影响。我将采用这个解决方案,在其中添加根状态的 id(因此我不必遍历整个链)和前一个状态的 id。这甚至会提高性能,谢谢!
【解决方案2】:

这应该可以工作,直到你有一个循环依赖。由于循环依赖,StackOverflowError 可能会发生,因为无限循环递归地获取先前的状态。

例如,如果您有两个实体 A 和 B。A 指向 B,B 指向 A,那么您将进入无限循环,并且使用 EAGER 获取策略最终会出现 StackOverflowError。

你需要检查你在 DB 中没有任何循环依赖。

【讨论】:

  • 我很确定它不是循环依赖,因为它曾经可以正常工作并且当链太长时停止工作。不幸的是,我无法再重现该错误,并且堆栈跟踪从日志中丢失了。如果它再次发生,我一定会检查它是否是一个循环。一个循环确实会导致这种行为。
猜你喜欢
  • 2011-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-14
  • 1970-01-01
  • 2019-02-06
  • 1970-01-01
相关资源
最近更新 更多