Merge and refresh queries fail with stack overflow on particular associations

Description

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)

Environment

Hibernate ORM 4.3.6.Final
Derby 10.4 (but it doesnt matter)

Assignee

Unassigned

Reporter

Steve Ash

Fix versions

None

Labels

backPortable

None

Suitable for new contributors

None

Requires Release Note

None

Pull Request

None

backportDecision

None

Components

Affects versions

Priority

Major
Configure