HHH-8879 HHH-8994 : Embeddable with associations used as a Map key

This commit is contained in:
Gail Badner 2015-03-26 17:44:37 -07:00
parent 5d365cfa00
commit 64c3e4c783
10 changed files with 363 additions and 20 deletions

View File

@ -23,6 +23,7 @@
*/ */
package org.hibernate.loader.plan.build.internal.returns; package org.hibernate.loader.plan.build.internal.returns;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.PropertyPath; import org.hibernate.loader.PropertyPath;
import org.hibernate.loader.plan.build.internal.spaces.CompositePropertyMapping; import org.hibernate.loader.plan.build.internal.spaces.CompositePropertyMapping;
import org.hibernate.loader.plan.build.internal.spaces.QuerySpaceHelper; import org.hibernate.loader.plan.build.internal.spaces.QuerySpaceHelper;
@ -34,6 +35,7 @@ import org.hibernate.loader.plan.spi.CollectionFetchableIndex;
import org.hibernate.loader.plan.spi.CollectionReference; import org.hibernate.loader.plan.spi.CollectionReference;
import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.CollectionPropertyNames; import org.hibernate.persister.collection.CollectionPropertyNames;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.PropertyMapping; import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.type.CompositeType; import org.hibernate.type.CompositeType;
@ -50,6 +52,9 @@ public abstract class AbstractCollectionReference implements CollectionReference
private final CollectionFetchableIndex index; private final CollectionFetchableIndex index;
private final CollectionFetchableElement element; private final CollectionFetchableElement element;
private final boolean allowElementJoin;
private final boolean allowIndexJoin;
protected AbstractCollectionReference( protected AbstractCollectionReference(
ExpandingCollectionQuerySpace collectionQuerySpace, ExpandingCollectionQuerySpace collectionQuerySpace,
PropertyPath propertyPath, PropertyPath propertyPath,
@ -57,13 +62,31 @@ public abstract class AbstractCollectionReference implements CollectionReference
this.collectionQuerySpace = collectionQuerySpace; this.collectionQuerySpace = collectionQuerySpace;
this.propertyPath = propertyPath; this.propertyPath = propertyPath;
this.index = buildIndexGraph( collectionQuerySpace, shouldIncludeJoins ); this.allowElementJoin = shouldIncludeJoins;
this.element = buildElementGraph( collectionQuerySpace, shouldIncludeJoins );
// Currently we can only allow a join for the collection index if all of the following are true:
// - collection element joins are allowed;
// - index is an EntityType;
// - index values are not "formulas" (e.g., a @MapKey index is translated into "formula" value(s)).
// Hibernate cannot currently support eager joining of associations within a component (@Embeddable) as an index.
if ( shouldIncludeJoins &&
collectionQuerySpace.getCollectionPersister().hasIndex() &&
collectionQuerySpace.getCollectionPersister().getIndexType().isEntityType() ) {
final String[] indexFormulas =
( (QueryableCollection) collectionQuerySpace.getCollectionPersister() ).getIndexFormulas();
final int nNonNullFormulas = ArrayHelper.countNonNull( indexFormulas );
this.allowIndexJoin = nNonNullFormulas == 0;
}
else {
this.allowIndexJoin = false;
}
// All other fields must be initialized before building this.index and this.element.
this.index = buildIndexGraph();
this.element = buildElementGraph();
} }
private CollectionFetchableIndex buildIndexGraph( private CollectionFetchableIndex buildIndexGraph() {
ExpandingCollectionQuerySpace collectionQuerySpace,
boolean shouldIncludeJoins) {
final CollectionPersister persister = collectionQuerySpace.getCollectionPersister(); final CollectionPersister persister = collectionQuerySpace.getCollectionPersister();
if ( persister.hasIndex() ) { if ( persister.hasIndex() ) {
final Type type = persister.getIndexType(); final Type type = persister.getIndexType();
@ -80,7 +103,7 @@ public abstract class AbstractCollectionReference implements CollectionReference
(EntityType) persister.getIndexType(), (EntityType) persister.getIndexType(),
collectionQuerySpace.getExpandingQuerySpaces().generateImplicitUid(), collectionQuerySpace.getExpandingQuerySpaces().generateImplicitUid(),
collectionQuerySpace.canJoinsBeRequired(), collectionQuerySpace.canJoinsBeRequired(),
shouldIncludeJoins allowIndexJoin
); );
return new CollectionFetchableIndexEntityGraph( this, entityQuerySpace ); return new CollectionFetchableIndexEntityGraph( this, entityQuerySpace );
} }
@ -100,7 +123,7 @@ public abstract class AbstractCollectionReference implements CollectionReference
(CompositeType) persister.getIndexType(), (CompositeType) persister.getIndexType(),
collectionQuerySpace.getExpandingQuerySpaces().generateImplicitUid(), collectionQuerySpace.getExpandingQuerySpaces().generateImplicitUid(),
collectionQuerySpace.canJoinsBeRequired(), collectionQuerySpace.canJoinsBeRequired(),
shouldIncludeJoins allowIndexJoin
); );
return new CollectionFetchableIndexCompositeGraph( this, compositeQuerySpace ); return new CollectionFetchableIndexCompositeGraph( this, compositeQuerySpace );
} }
@ -109,9 +132,7 @@ public abstract class AbstractCollectionReference implements CollectionReference
return null; return null;
} }
private CollectionFetchableElement buildElementGraph( private CollectionFetchableElement buildElementGraph() {
ExpandingCollectionQuerySpace collectionQuerySpace,
boolean shouldIncludeJoins) {
final CollectionPersister persister = collectionQuerySpace.getCollectionPersister(); final CollectionPersister persister = collectionQuerySpace.getCollectionPersister();
final Type type = persister.getElementType(); final Type type = persister.getElementType();
if ( type.isAssociationType() ) { if ( type.isAssociationType() ) {
@ -126,7 +147,7 @@ public abstract class AbstractCollectionReference implements CollectionReference
(EntityType) persister.getElementType(), (EntityType) persister.getElementType(),
collectionQuerySpace.getExpandingQuerySpaces().generateImplicitUid(), collectionQuerySpace.getExpandingQuerySpaces().generateImplicitUid(),
collectionQuerySpace.canJoinsBeRequired(), collectionQuerySpace.canJoinsBeRequired(),
shouldIncludeJoins allowElementJoin
); );
return new CollectionFetchableElementEntityGraph( this, entityQuerySpace ); return new CollectionFetchableElementEntityGraph( this, entityQuerySpace );
} }
@ -146,7 +167,7 @@ public abstract class AbstractCollectionReference implements CollectionReference
(CompositeType) persister.getElementType(), (CompositeType) persister.getElementType(),
collectionQuerySpace.getExpandingQuerySpaces().generateImplicitUid(), collectionQuerySpace.getExpandingQuerySpaces().generateImplicitUid(),
collectionQuerySpace.canJoinsBeRequired(), collectionQuerySpace.canJoinsBeRequired(),
shouldIncludeJoins allowElementJoin
); );
return new CollectionFetchableElementCompositeGraph( this, compositeQuerySpace ); return new CollectionFetchableElementCompositeGraph( this, compositeQuerySpace );
} }
@ -154,6 +175,16 @@ public abstract class AbstractCollectionReference implements CollectionReference
return null; return null;
} }
@Override
public boolean allowElementJoin() {
return allowElementJoin;
}
@Override
public boolean allowIndexJoin() {
return allowIndexJoin;
}
@Override @Override
public String getQuerySpaceUid() { public String getQuerySpaceUid() {
return collectionQuerySpace.getUid(); return collectionQuerySpace.getUid();

View File

@ -86,14 +86,13 @@ public abstract class AbstractCollectionLoadQueryDetails extends AbstractLoadQue
new CollectionReturnReader( rootReturn ), new CollectionReturnReader( rootReturn ),
new CollectionReferenceInitializerImpl( rootReturn, collectionReferenceAliases ) new CollectionReferenceInitializerImpl( rootReturn, collectionReferenceAliases )
); );
if ( rootReturn.getCollectionPersister().getElementType().isEntityType() ) { if ( rootReturn.allowElementJoin() && rootReturn.getCollectionPersister().getElementType().isEntityType() ) {
final EntityReference elementEntityReference = rootReturn.getElementGraph().resolveEntityReference(); final EntityReference elementEntityReference = rootReturn.getElementGraph().resolveEntityReference();
readerCollector.add( readerCollector.add(
new EntityReferenceInitializerImpl( elementEntityReference, collectionReferenceAliases.getEntityElementAliases() ) new EntityReferenceInitializerImpl( elementEntityReference, collectionReferenceAliases.getEntityElementAliases() )
); );
} }
if ( rootReturn.getCollectionPersister().hasIndex() && if ( rootReturn.allowIndexJoin() && rootReturn.getCollectionPersister().getIndexType().isEntityType() ) {
rootReturn.getCollectionPersister().getIndexType().isEntityType() ) {
final EntityReference indexEntityReference = rootReturn.getIndexGraph().resolveEntityReference(); final EntityReference indexEntityReference = rootReturn.getIndexGraph().resolveEntityReference();
final EntityReferenceAliases indexEntityReferenceAliases = aliasResolutionContext.generateEntityReferenceAliases( final EntityReferenceAliases indexEntityReferenceAliases = aliasResolutionContext.generateEntityReferenceAliases(
indexEntityReference.getQuerySpaceUid(), indexEntityReference.getQuerySpaceUid(),
@ -134,8 +133,7 @@ public abstract class AbstractCollectionLoadQueryDetails extends AbstractLoadQue
@Override @Override
protected void applyRootReturnSelectFragments(SelectStatementBuilder selectStatementBuilder) { protected void applyRootReturnSelectFragments(SelectStatementBuilder selectStatementBuilder) {
if ( getQueryableCollection().hasIndex() && if ( getRootCollectionReturn().allowIndexJoin() && getQueryableCollection().getIndexType().isEntityType() ) {
getQueryableCollection().getIndexType().isEntityType() ) {
final EntityReference indexEntityReference = getRootCollectionReturn().getIndexGraph().resolveEntityReference(); final EntityReference indexEntityReference = getRootCollectionReturn().getIndexGraph().resolveEntityReference();
final EntityReferenceAliases indexEntityReferenceAliases = getAliasResolutionContext().resolveEntityReferenceAliases( final EntityReferenceAliases indexEntityReferenceAliases = getAliasResolutionContext().resolveEntityReferenceAliases(
indexEntityReference.getQuerySpaceUid() indexEntityReference.getQuerySpaceUid()

View File

@ -77,4 +77,20 @@ public interface CollectionReference {
* @return The PropertyPath * @return The PropertyPath
*/ */
public PropertyPath getPropertyPath(); public PropertyPath getPropertyPath();
/**
* Should a collection element join be allowed? Returning <code>true</code>
* indicates that an element join can safely be added.
*
* @return true, if a collection index join is allowed.
*/
public boolean allowElementJoin();
/**
* Should a collection index join be allowed? Returning <code>true</code>
* indicates that an index join can safely be added.
*
* @return true, if a collection index join is allowed.
*/
public boolean allowIndexJoin();
} }

View File

@ -109,7 +109,7 @@ public final class CompositionSingularSubAttributesHelper {
(OuterJoinLoadable) collectionPersister.getOwnerEntityPersister(), (OuterJoinLoadable) collectionPersister.getOwnerEntityPersister(),
(CompositeType) collectionPersister.getIndexType(), (CompositeType) collectionPersister.getIndexType(),
collectionPersister.getTableName(), collectionPersister.getTableName(),
collectionPersister.getIndexColumnNames() collectionPersister.toColumns( "index" )
); );
} }

View File

@ -144,7 +144,7 @@ public class MetamodelGraphWalker {
if ( attributeDefinitions == null ) { if ( attributeDefinitions == null ) {
return; return;
} }
for ( AttributeDefinition attributeDefinition : attributeSource.getAttributes() ) { for ( AttributeDefinition attributeDefinition : attributeDefinitions ) {
visitAttributeDefinition( attributeDefinition ); visitAttributeDefinition( attributeDefinition );
} }
} }

View File

@ -0,0 +1,37 @@
//$Id$
package org.hibernate.test.annotations.indexcoll;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Currency {
private Integer id;
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column
private String currency;
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
}

View File

@ -0,0 +1,48 @@
//$Id$
package org.hibernate.test.annotations.indexcoll;
import java.math.BigDecimal;
import java.util.Map;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.annotations.CascadeType;
@Entity
public class ExchangeOffice {
public ExchangeOffice() {
super();
}
@Id @GeneratedValue
private Integer id;
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
@javax.persistence.OneToMany(mappedBy = "parent")
@javax.persistence.MapKey(name="key")
private Map<ExchangeRateKey, ExchangeRate> exchangeRates = new java.util.HashMap<ExchangeRateKey, ExchangeRate>();
public Map<ExchangeRateKey,ExchangeRate> getExchangeRates() {
return exchangeRates;
}
@ElementCollection
private Map<ExchangeRateKey, BigDecimal> exchangeRateFees = new java.util.HashMap<ExchangeRateKey, BigDecimal>();
public Map<ExchangeRateKey,BigDecimal> getExchangeRateFees() {
return exchangeRateFees;
}
}

View File

@ -0,0 +1,63 @@
//$Id$
package org.hibernate.test.annotations.indexcoll;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class ExchangeRate {
public ExchangeRate() {
super();
}
@Id @GeneratedValue
private Integer id;
@Column
private double rate;
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Embedded
private ExchangeRateKey key = new ExchangeRateKey();
public ExchangeRateKey getKey() {
return key;
}
public void setKey(ExchangeRateKey key) {
this.key = key;
}
@javax.persistence.ManyToOne(fetch = FetchType.LAZY )
private ExchangeOffice parent = null;
public ExchangeOffice getParent() {
return parent;
}
public void setParent(ExchangeOffice parent) {
this.parent = parent;
}
}

View File

@ -0,0 +1,56 @@
//$Id$
package org.hibernate.test.annotations.indexcoll;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.FetchType;
@Embeddable
public class ExchangeRateKey
{
public ExchangeRateKey() {
super();
}
public ExchangeRateKey( long date, Currency currency1, Currency currency2) {
super();
this.date = date;
this.currency1 = currency1;
this.currency2 = currency2;
}
@Column(nullable = false)
protected long date;
@javax.persistence.ManyToOne(fetch = FetchType.LAZY )
protected Currency currency1;
@javax.persistence.ManyToOne(fetch = FetchType.LAZY )
protected Currency currency2;
@Override
public boolean equals (Object obj) {
if (this == obj) return true;
if (!(obj instanceof ExchangeRateKey)) return false;
ExchangeRateKey q = (ExchangeRateKey) obj;
return q.date == date && q.currency1 == this.currency1 && q.currency2 == this.currency2;
}
@Override
public int hashCode() {
int hashcode = 0;
hashcode += date;
hashcode += (currency1 != null ? currency1.hashCode() : 0);
hashcode += (currency2 != null ? currency2.hashCode() : 0);
return hashcode;
}
}

View File

@ -33,9 +33,11 @@ import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test; import org.junit.Test;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -562,6 +564,95 @@ public class IndexedCollectionTest extends BaseNonConfigCoreFunctionalTestCase {
s.close(); s.close();
} }
@Test
@TestForIssue( jiraKey = "HHH-8879")
public void testMapKeyEmbeddableWithEntityKey() throws Exception {
Session s;
Transaction tx;
s = openSession();
tx = s.beginTransaction();
Currency currency1= new Currency();
Currency currency2= new Currency();
s.persist( currency1 );
s.persist( currency2 );
Integer id1 = currency1.getId();
Integer id2 = currency2.getId();
ExchangeRateKey cq = new ExchangeRateKey(20140101, currency1, currency2);
ExchangeRate m = new ExchangeRate();
m.setKey( cq );
s.persist( m );
ExchangeOffice wm = new ExchangeOffice();
s.persist( wm );
wm.getExchangeRates().put( cq, m );
m.setParent( wm );
Integer id = wm.getId();
s.flush();
tx.commit();
s.close();
s = openSession();
tx = s.beginTransaction();
wm = (ExchangeOffice) s.byId(ExchangeOffice.class).load(id);
assertNotNull(wm);
wm.getExchangeRates().size();
currency1 = (Currency) s.byId(Currency.class).load(id1);
assertNotNull(currency1);
currency2 = (Currency) s.byId(Currency.class).load(id2);
assertNotNull(currency2);
cq = new ExchangeRateKey(20140101, currency1, currency2);
m = wm.getExchangeRates().get( cq );
assertNotNull(m);
tx.commit();
s.close();
}
@Test
@TestForIssue( jiraKey = "HHH-8994")
public void testEmbeddableWithEntityKey() throws Exception {
Session s;
Transaction tx;
s = openSession();
tx = s.beginTransaction();
Currency currency1= new Currency();
Currency currency2= new Currency();
s.persist( currency1 );
s.persist( currency2 );
Integer id1 = currency1.getId();
Integer id2 = currency2.getId();
ExchangeRateKey cq = new ExchangeRateKey(20140101, currency1, currency2);
ExchangeOffice wm = new ExchangeOffice();
s.persist( wm );
final BigDecimal fee = BigDecimal.valueOf( 12, 2 );
wm.getExchangeRateFees().put( cq, fee );
Integer id = wm.getId();
s.flush();
tx.commit();
s.close();
s = openSession();
tx = s.beginTransaction();
wm = (ExchangeOffice) s.byId(ExchangeOffice.class).load(id);
assertNotNull(wm);
wm.getExchangeRateFees().size();
currency1 = (Currency) s.byId(Currency.class).load(id1);
assertNotNull(currency1);
currency2 = (Currency) s.byId(Currency.class).load(id2);
assertNotNull(currency2);
cq = new ExchangeRateKey(20140101, currency1, currency2);
assertEquals( fee, wm.getExchangeRateFees().get( cq ) );
tx.commit();
s.close();
}
@Test @Test
public void testEntityKeyElementTarget() throws Exception { public void testEntityKeyElementTarget() throws Exception {
Session s = openSession(); Session s = openSession();
@ -670,7 +761,10 @@ public class IndexedCollectionTest extends BaseNonConfigCoreFunctionalTestCase {
AlphabeticalDirectory.class, AlphabeticalDirectory.class,
GasKey.class, GasKey.class,
Trainee.class, Trainee.class,
Training.class Training.class,
Currency.class,
ExchangeOffice.class,
ExchangeRate.class,
}; };
} }
} }