Skip to content

Commit 4fd933d

Browse files
committed
HHH-8839 : Eager Map with entity key causes IllegalStateException: Collection element (many-to-many) table alias cannot be empty
(cherry picked from commit 35edd56)
1 parent 57ac3f0 commit 4fd933d

File tree

5 files changed

+359
-18
lines changed

5 files changed

+359
-18
lines changed

hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/spaces/CollectionQuerySpaceImpl.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.hibernate.loader.plan.build.spi.ExpandingCollectionQuerySpace;
2727
import org.hibernate.loader.plan.build.spi.ExpandingQuerySpaces;
2828
import org.hibernate.loader.plan.spi.Join;
29+
import org.hibernate.loader.plan.spi.JoinDefinedByMetadata;
2930
import org.hibernate.persister.collection.CollectionPersister;
3031
import org.hibernate.persister.collection.CollectionPropertyNames;
3132
import org.hibernate.persister.collection.QueryableCollection;
@@ -36,6 +37,8 @@
3637
*/
3738
public class CollectionQuerySpaceImpl extends AbstractQuerySpace implements ExpandingCollectionQuerySpace {
3839
private final CollectionPersister persister;
40+
private JoinDefinedByMetadata elementJoin;
41+
private JoinDefinedByMetadata indexJoin;
3942

4043
public CollectionQuerySpaceImpl(
4144
CollectionPersister persister,
@@ -78,6 +81,35 @@ else if ( propertyName.equals( CollectionPropertyNames.COLLECTION_INDICES ) ) {
7881

7982
@Override
8083
public void addJoin(Join join) {
84+
if ( JoinDefinedByMetadata.class.isInstance( join ) ) {
85+
final JoinDefinedByMetadata joinDefinedByMetadata = (JoinDefinedByMetadata) join;
86+
if ( joinDefinedByMetadata.getJoinedPropertyName().equals( CollectionPropertyNames.COLLECTION_ELEMENTS ) ) {
87+
if ( elementJoin == null ) {
88+
elementJoin = joinDefinedByMetadata;
89+
}
90+
else {
91+
throw new IllegalStateException( "Attempt to add an element join, but an element join already exists." );
92+
}
93+
}
94+
else if ( joinDefinedByMetadata.getJoinedPropertyName().equals( CollectionPropertyNames.COLLECTION_INDICES ) ) {
95+
if ( indexJoin == null ) {
96+
indexJoin = joinDefinedByMetadata;
97+
}
98+
else {
99+
throw new IllegalStateException( "Attempt to add an index join, but an index join already exists." );
100+
}
101+
}
102+
else {
103+
throw new IllegalArgumentException(
104+
String.format(
105+
"Collection propertyName must be either %s or %s; instead the joined property name was %s.",
106+
CollectionPropertyNames.COLLECTION_ELEMENTS,
107+
CollectionPropertyNames.COLLECTION_INDICES,
108+
joinDefinedByMetadata.getJoinedPropertyName()
109+
)
110+
);
111+
}
112+
}
81113
internalGetJoins().add( join );
82114
}
83115

@@ -86,4 +118,7 @@ public ExpandingQuerySpaces getExpandingQuerySpaces() {
86118
return super.getExpandingQuerySpaces();
87119
}
88120

121+
public void addJoin(JoinDefinedByMetadata join) {
122+
addJoin( (Join) join );
123+
}
89124
}

hibernate-core/src/main/java/org/hibernate/loader/plan/build/spi/ExpandingCollectionQuerySpace.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
package org.hibernate.loader.plan.build.spi;
2525

2626
import org.hibernate.loader.plan.spi.CollectionQuerySpace;
27+
import org.hibernate.loader.plan.spi.Join;
28+
import org.hibernate.loader.plan.spi.JoinDefinedByMetadata;
29+
import org.hibernate.persister.collection.CollectionPropertyNames;
2730

2831
/**
2932
* Describes a collection query space that allows adding joins with other
@@ -34,4 +37,19 @@
3437
* @author Gail Badner
3538
*/
3639
public interface ExpandingCollectionQuerySpace extends CollectionQuerySpace, ExpandingQuerySpace {
40+
41+
/**
42+
* Adds a join with another query space for either a collection element or index. If {@code join}
43+
* is an instance of {@link JoinDefinedByMetadata}, then the only valid values returned by
44+
* {@link JoinDefinedByMetadata#getJoinedPropertyName} are {@link CollectionPropertyNames#COLLECTION_ELEMENTS}
45+
* and {@link CollectionPropertyNames#COLLECTION_INDICES}, for the collection element or index, respectively.
46+
*
47+
* @param join The element or index join to add.
48+
*
49+
* @throws java.lang.IllegalArgumentException if {@code join} is an instance of {@link JoinDefinedByMetadata}
50+
* and {@code join.getJoinedPropertyName() is neither {@link CollectionPropertyNames#COLLECTION_ELEMENTS}
51+
* nor {@link CollectionPropertyNames#COLLECTION_INDICES}}.
52+
* @throws java.lang.IllegalStateException if there is already an existing join with the same joined property name.
53+
*/
54+
public void addJoin(Join join);
3755
}

hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424
package org.hibernate.loader.plan.exec.internal;
2525

26+
import org.hibernate.AssertionFailure;
2627
import org.hibernate.engine.FetchStyle;
2728
import org.hibernate.engine.FetchTiming;
2829
import org.hibernate.engine.spi.SessionFactoryImplementor;
@@ -284,28 +285,43 @@ private void renderCollectionJoin(Join join, JoinFragment joinFragment) {
284285
// For many-to-many, the follow-on join will join to the associated entity element table. For one-to-many,
285286
// the collection table is the associated entity table, so the follow-on join will not be rendered..
286287

288+
// currently we do not explicitly track the joins under the CollectionQuerySpace to know which is
289+
// the element join and which is the index join (maybe we should?).
290+
291+
JoinDefinedByMetadata collectionElementJoin = null;
292+
JoinDefinedByMetadata collectionIndexJoin = null;
293+
for ( Join collectionJoin : rightHandSide.getJoins() ) {
294+
if ( JoinDefinedByMetadata.class.isInstance( collectionJoin ) ) {
295+
final JoinDefinedByMetadata collectionJoinDefinedByMetadata = (JoinDefinedByMetadata) collectionJoin;
296+
if ( CollectionPropertyNames.COLLECTION_ELEMENTS.equals( collectionJoinDefinedByMetadata.getJoinedPropertyName() ) ) {
297+
if ( collectionElementJoin != null ) {
298+
throw new AssertionFailure(
299+
String.format(
300+
"More than one element join defined for: %s",
301+
rightHandSide.getCollectionPersister().getRole()
302+
)
303+
);
304+
}
305+
collectionElementJoin = collectionJoinDefinedByMetadata;
306+
}
307+
if ( CollectionPropertyNames.COLLECTION_INDICES.equals( collectionJoinDefinedByMetadata.getJoinedPropertyName() ) ) {
308+
if ( collectionIndexJoin != null ) {
309+
throw new AssertionFailure(
310+
String.format(
311+
"More than one index join defined for: %s",
312+
rightHandSide.getCollectionPersister().getRole()
313+
)
314+
);
315+
}
316+
collectionIndexJoin = collectionJoinDefinedByMetadata;
317+
}
318+
}
319+
}
320+
287321
if ( rightHandSide.getCollectionPersister().isOneToMany()
288322
|| rightHandSide.getCollectionPersister().isManyToMany() ) {
289323
// relatedly, for collections with entity elements (one-to-many, many-to-many) we need to register the
290324
// sql aliases to use for the entity.
291-
//
292-
// currently we do not explicitly track the joins under the CollectionQuerySpace to know which is
293-
// the element join and which is the index join (maybe we should?). Another option here is to have the
294-
// "collection join" act as the entity element join in this case (much like I do with entity identifiers).
295-
// The difficulty there is that collections can theoretically could be multiple joins in that case (one
296-
// for element, one for index). However, that's a bit of future-planning as today Hibernate does not
297-
// properly deal with the index anyway in terms of allowing dynamic fetching across a collection index...
298-
//
299-
// long story short, for now we'll use an assumption that the last join in the CollectionQuerySpace is the
300-
// element join (that's how the joins are built as of now..)
301-
//
302-
// todo : remove this assumption ^^; maybe we make CollectionQuerySpace "special" and rather than have it
303-
// hold a list of joins, we have it expose the 2 (index, element) separately.
304-
305-
Join collectionElementJoin = null;
306-
for ( Join collectionJoin : rightHandSide.getJoins() ) {
307-
collectionElementJoin = collectionJoin;
308-
}
309325
if ( collectionElementJoin == null ) {
310326
throw new IllegalStateException(
311327
String.format(
@@ -329,6 +345,24 @@ private void renderCollectionJoin(Join join, JoinFragment joinFragment) {
329345
);
330346
}
331347

348+
if ( rightHandSide.getCollectionPersister().hasIndex() &&
349+
rightHandSide.getCollectionPersister().getIndexType().isEntityType() ) {
350+
// for collections with entity index we need to register the
351+
// sql aliases to use for the entity.
352+
if ( collectionIndexJoin == null ) {
353+
throw new IllegalStateException(
354+
String.format(
355+
"Could not locate collection index join within collection join [%s : %s]",
356+
rightHandSide.getUid(),
357+
rightHandSide.getCollectionPersister()
358+
)
359+
);
360+
}
361+
aliasResolutionContext.generateEntityReferenceAliases(
362+
collectionIndexJoin.getRightHandSide().getUid(),
363+
rightHandSide.getCollectionPersister().getIndexDefinition().toEntityDefinition().getEntityPersister()
364+
);
365+
}
332366
addJoins(
333367
join,
334368
joinFragment,
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//$Id$
2+
package org.hibernate.test.annotations.indexcoll.eager;
3+
import java.util.Date;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
import javax.persistence.CascadeType;
7+
import javax.persistence.Column;
8+
import javax.persistence.ElementCollection;
9+
import javax.persistence.Entity;
10+
import javax.persistence.EnumType;
11+
import javax.persistence.FetchType;
12+
import javax.persistence.GeneratedValue;
13+
import javax.persistence.Id;
14+
import javax.persistence.JoinColumn;
15+
import javax.persistence.JoinTable;
16+
import javax.persistence.ManyToMany;
17+
import javax.persistence.MapKeyColumn;
18+
import javax.persistence.MapKeyEnumerated;
19+
import javax.persistence.MapKeyJoinColumn;
20+
import javax.persistence.MapKeyJoinColumns;
21+
import javax.persistence.MapKeyTemporal;
22+
import javax.persistence.TemporalType;
23+
24+
import org.hibernate.test.annotations.indexcoll.Gas;
25+
import org.hibernate.test.annotations.indexcoll.GasKey;
26+
27+
/**
28+
* @author Emmanuel Bernard
29+
*/
30+
@Entity
31+
public class Atmosphere {
32+
33+
public static enum Level {
34+
LOW,
35+
HIGH
36+
}
37+
38+
@Id
39+
@GeneratedValue
40+
public Integer id;
41+
42+
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
43+
@MapKeyColumn(name="gas_name")
44+
public Map<String, Gas> gases = new HashMap<String, Gas>();
45+
46+
@MapKeyTemporal(TemporalType.DATE)
47+
@ElementCollection(fetch = FetchType.EAGER)
48+
@MapKeyColumn(nullable=false)
49+
public Map<Date, String> colorPerDate = new HashMap<Date,String>();
50+
51+
@ElementCollection(fetch = FetchType.EAGER)
52+
@MapKeyEnumerated(EnumType.STRING)
53+
@MapKeyColumn(nullable=false)
54+
public Map<Level, String> colorPerLevel = new HashMap<Level,String>();
55+
56+
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
57+
@MapKeyJoinColumn(name="gas_id" )
58+
@JoinTable(name = "Gas_per_key")
59+
public Map<GasKey, Gas> gasesPerKey = new HashMap<GasKey, Gas>();
60+
61+
@ElementCollection(fetch = FetchType.EAGER)
62+
@Column(name="composition_rate")
63+
@MapKeyJoinColumns( { @MapKeyJoinColumn(name="gas_id" ) } ) //use @MapKeyJoinColumns explicitly for tests
64+
@JoinTable(name = "Composition", joinColumns = @JoinColumn(name = "atmosphere_id"))
65+
public Map<Gas, Double> composition = new HashMap<Gas, Double>();
66+
67+
//use default JPA 2 column name for map key
68+
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
69+
@MapKeyColumn
70+
@JoinTable(name="Atm_Gas_Def")
71+
public Map<String, Gas> gasesDef = new HashMap<String, Gas>();
72+
73+
//use default HAN legacy column name for map key
74+
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
75+
@MapKeyColumn
76+
@JoinTable(name="Atm_Gas_DefLeg")
77+
public Map<String, Gas> gasesDefLeg = new HashMap<String, Gas>();
78+
79+
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
80+
@MapKeyJoinColumn
81+
@JoinTable(name = "Gas_p_key_def")
82+
public Map<GasKey, Gas> gasesPerKeyDef = new HashMap<GasKey, Gas>();
83+
84+
}

0 commit comments

Comments
 (0)