|
I have a object graph like:
-
Parent
-
@Id property "id", long, generated
-
@OnetoMany Set<Child> property "children"
-
Child
-
@ManyToOne @Id Parent property "parent"
-
@Id String property "qualifier"
That is the child's primary key is a compound key that is made up of the PK of the parent + some more qualifying information. Perhaps we can argue about the value of such database schemas, but it doesn't seem completely insane. It's certainly legal.
This works fine for most things: load, query with Parent p join fetch p.children. But if you try and use it in a merge or refresh query – you get a stack overflow. Making it worse if the parent happens to have at least one other another association whose property name is alphabetically lower than the "children" property above – then everything works. Thus this bug is easily masked but blows up spectacularly when uncovered (as it did on our project).
I dug around a little bit and it has something to do with the CascadeEntityJoinWalker – when it is used to build the merge and update queries (AbstractEntityPersister#createLoaders) it adds at most one association as a left join. If that one happens to have an id that has a compound property with a parent's type – then it "loops" trying to forceLoad the parent, which then of course notices the left join to the child (Loader#extractKeysFromResultSet).
I am attaching a zip of a self contained maven project with a single unit test containing testMerge which is failing if you want to replicate the problem.
This snippet of the stack trace (which is repeated until stack overflow) kind of shows the path of the problem:
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:213) at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:275) at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:151) 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.type.ComponentType.resolve(ComponentType.java:666) at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:838) at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:720) at org.hibernate.loader.Loader.processResultSet(Loader.java:952) at org.hibernate.loader.Loader.doQuery(Loader.java:920) at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354) at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:324) at org.hibernate.loader.Loader.loadEntity(Loader.java:2148) at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:78) at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:68) at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4126) at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:503) at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:468) at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:213) at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:275) at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:151) at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1070)
|