0

In one of my Java entities (MyState) I have a reference to itself. The annotations look as follows:

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

This used to work fine, until the table grew and more and more states are pointing to each other. Now when I try to fetch the newest MyState a StackOverflowError occurs because the nesting is too deep. What better site to ask my question than here? ;-)

I eventually need to access the root state (the very first in a long chain of states), but also the previous state.

Is there any way to avoid getting all the references? I have tried to add a new field 'rootState' which points to the original state. This is of course a short chain of 2 states, so that works well.

I do, however, also need the previous state. Should I try to manually break the chain by setting the previousState of the previousState to null, or are there better options?

--- edit

I checked to make sure there isn't a cycle in the states that point to each other, there isn't.

Part of the stacktrace:

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....
Paul S
  • 434
  • 4
  • 13
  • This might help you understanding all other data structure availalble for representing **hierarchical-data** https://stackoverflow.com/questions/4048151/what-are-the-options-for-storing-hierarchical-data-in-a-relational-database – Shafin Mahmud Aug 14 '18 at 14:18
  • But I think what you are trying (Adjucency List), there shouldn't be any problem something like `StackOverflowError` unless you have data inconsitency that have cyclic dependecy. Make sure you dont have that. And Yes, you must have a **root** entry thats `previousState` has to be `null` – Shafin Mahmud Aug 14 '18 at 14:20
  • After further investigation (it happened again), i am sure that there is no cycle. I also added a part of the stacktrace – Paul S Oct 08 '18 at 08:27

2 Answers2

1

This should work until you have a cyclic dependency. With the cyclic dependency the StackOverflowError can happen because of an infinite loop fetching recursively the previous state.

For example when you have two entities A and B. A is pointing to B and B is pointing to A then you have the infinite loop and with EAGER fetching strategy you'll end up with StackOverflowError.

You need to check that you don't have any cyclic dependency in DB.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Shaolin
  • 154
  • 10
  • I was pretty sure it wasn't a cyclic dependency, because it used to work fine and stopped working when the chain was too long. Unfortunately I cannot reproduce the error anymore and the stacktrace went missing from the logs. If it should happen again I will definitely check if it was a cycle or not. A cycle would indeed cause this behavior. – Paul S Aug 15 '18 at 06:29
1

Obviously it is an error. If you have an entity Person and that entity has a nested reference to another Person, it means that the nested Person has another nested link, and so it does the sub-nested one and so on. you don't have to reference a nested Person with a Person Object. Use instead a unique id.

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

now you have 2 options: use a foreign key or leave it as it is. If you choose the first option you have to map that nestedId, but i would recommend you to start using the second option. This way you have a Person with an id that references a "father" Person. if that nestedId is != null OR > 0 (according to the type you assign to the id) it means the Person is nested, instead if the id is not populated or the id has the default int value that is 0 it means the Person is a "root", so not nested. Please refer to "Hierarchical Data" for more details.

Fausto
  • 183
  • 12
  • The disadvantage of refering to the id of the nested record is that I have to fetch it manually from the database if I need it. This is of course what hibernate does under the hood as well, so performance wise there would be no impact. I will go with this solution, where I add the id of the root state (so I don't have to walk the entire chain) and the id of the previous state. This would even increase performance, Thanks! – Paul S Aug 15 '18 at 06:36