休眠中的自引用实体导致StackOverflowErrors

时间:2018-08-14 13:22:41

标签: java hibernate self-reference

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

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

这曾经很好用,直到表增长并且越来越多的状态指向彼此为止。现在,当我尝试获取最新的MyState时,由于嵌套太深而发生StackOverflowError。有什么比这里更好的网站问我的问题? ;-)

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

有什么方法可以避免获取所有引用?我试图添加一个指向原始状态的新字段“ rootState”。当然,这是2个状态的短链,因此效果很好。

但是,我确实还需要先前的状态。我应该尝试通过将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....

2 个答案:

答案 0 :(得分:1)

这应该起作用,直到您具有循环依赖性为止。由于存在循环依赖性,因此可能会发生StackOverflowError,这是因为无限循环递归地获取了先前的状态。

例如,当您有两个实体A和B。A指向B,B指向A时,您将遇到无限循环,并且使用EAGER提取策略,您将最终遇到StackOverflowError。

您需要检查数据库中是否没有任何循环依赖项。

答案 1 :(得分:1)

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

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

现在您有两种选择:使用外键或将其保持不变。 如果选择第一个选项,则必须映射该nestedId,但我建议您开始使用第二个选项。这样,您就可以拥有一个具有引用“父亲”人物的ID的人物。如果该nestedId为!= null或> 0(根据您为ID分配的类型),则表示该人是嵌套的;相反,如果未填充该ID或该ID的默认int值为0,则表示该人是“根”,因此不嵌套。有关更多详细信息,请参阅“分层数据”。