HHH-7625 - Fix inverse OneToMany with a Composite key mapping failure.

This commit is contained in:
Chris Cranford 2016-07-31 18:13:12 -05:00
parent 1da8f01740
commit 22f23d8da0
4 changed files with 177 additions and 63 deletions

View File

@ -15,8 +15,10 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import org.dom4j.Element;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.envers.ModificationStore; import org.hibernate.envers.ModificationStore;
@ -68,6 +70,7 @@ import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.OneToMany; import org.hibernate.mapping.OneToMany;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property; import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table; import org.hibernate.mapping.Table;
import org.hibernate.mapping.Value; import org.hibernate.mapping.Value;
import org.hibernate.type.BagType; import org.hibernate.type.BagType;
@ -79,16 +82,14 @@ import org.hibernate.type.SetType;
import org.hibernate.type.SortedMapType; import org.hibernate.type.SortedMapType;
import org.hibernate.type.SortedSetType; import org.hibernate.type.SortedSetType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.dom4j.Element;
/** /**
* Generates metadata for a collection-valued property. * Generates metadata for a collection-valued property.
* *
* @author Adam Warski (adam at warski dot org) * @author Adam Warski (adam at warski dot org)
* @author HernпїЅn Chanfreau * @author HernпїЅn Chanfreau
* @author Chris Cranford
*/ */
public final class CollectionMetadataGenerator { public final class CollectionMetadataGenerator {
private static final EnversMessageLogger LOG = Logger.getMessageLogger( private static final EnversMessageLogger LOG = Logger.getMessageLogger(
@ -123,8 +124,10 @@ public final class CollectionMetadataGenerator {
*/ */
public CollectionMetadataGenerator( public CollectionMetadataGenerator(
AuditMetadataGenerator mainGenerator, AuditMetadataGenerator mainGenerator,
Collection propertyValue, CompositeMapperBuilder currentMapper, Collection propertyValue,
String referencingEntityName, EntityXmlMappingData xmlMappingData, CompositeMapperBuilder currentMapper,
String referencingEntityName,
EntityXmlMappingData xmlMappingData,
PropertyAuditingData propertyAuditingData) { PropertyAuditingData propertyAuditingData) {
this.mainGenerator = mainGenerator; this.mainGenerator = mainGenerator;
this.propertyValue = propertyValue; this.propertyValue = propertyValue;
@ -164,7 +167,10 @@ public final class CollectionMetadataGenerator {
private MiddleIdData createMiddleIdData(IdMappingData idMappingData, String prefix, String entityName) { private MiddleIdData createMiddleIdData(IdMappingData idMappingData, String prefix, String entityName) {
return new MiddleIdData( return new MiddleIdData(
mainGenerator.getVerEntCfg(), idMappingData, prefix, entityName, mainGenerator.getVerEntCfg(),
idMappingData,
prefix,
entityName,
mainGenerator.getEntitiesConfigurations().containsKey( entityName ) mainGenerator.getEntitiesConfigurations().containsKey( entityName )
); );
} }
@ -219,7 +225,9 @@ public final class CollectionMetadataGenerator {
referencingIdData, referencingIdData,
referencedEntityName, referencedEntityName,
referencedIdData, referencedIdData,
isEmbeddableElementType() isEmbeddableElementType(),
mappedBy,
isMappedByKey( propertyValue, mappedBy )
); );
// Creating common mapper data. // Creating common mapper data.
@ -300,7 +308,8 @@ public final class CollectionMetadataGenerator {
*/ */
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
private void addRelatedToXmlMapping( private void addRelatedToXmlMapping(
Element xmlMapping, String prefix, Element xmlMapping,
String prefix,
MetadataTools.ColumnNameIterator columnNameIterator, MetadataTools.ColumnNameIterator columnNameIterator,
IdMappingData relatedIdMapping) { IdMappingData relatedIdMapping) {
final Element properties = (Element) relatedIdMapping.getXmlRelationMapping().clone(); final Element properties = (Element) relatedIdMapping.getXmlRelationMapping().clone();
@ -827,17 +836,16 @@ public final class CollectionMetadataGenerator {
} }
private String getMappedBy(Collection collectionValue) { private String getMappedBy(Collection collectionValue) {
PersistentClass referencedClass = null; final PersistentClass referencedClass = getReferenceCollectionClass( collectionValue );
if ( collectionValue.getElement() instanceof OneToMany ) { final ValueHolder valueHolder = new ValueHolder( collectionValue );
final OneToMany oneToManyValue = (OneToMany) collectionValue.getElement(); return getMappedBy( referencedClass, valueHolder );
referencedClass = oneToManyValue.getAssociatedClass(); }
}
else if ( collectionValue.getElement() instanceof ManyToOne ) {
// Case for bi-directional relation with @JoinTable on the owning @ManyToOne side.
final ManyToOne manyToOneValue = (ManyToOne) collectionValue.getElement();
referencedClass = manyToOneValue.getMetadata().getEntityBinding( manyToOneValue.getReferencedEntityName() );
}
private String getMappedBy(Table collectionTable, PersistentClass referencedClass) {
return getMappedBy( referencedClass, new ValueHolder( collectionTable ) );
}
private String getMappedBy(PersistentClass referencedClass, ValueHolder valueHolder) {
// If there's an @AuditMappedBy specified, returning it directly. // If there's an @AuditMappedBy specified, returning it directly.
final String auditMappedBy = propertyAuditingData.getAuditMappedBy(); final String auditMappedBy = propertyAuditingData.getAuditMappedBy();
if ( auditMappedBy != null ) { if ( auditMappedBy != null ) {
@ -845,7 +853,7 @@ public final class CollectionMetadataGenerator {
} }
// searching in referenced class // searching in referenced class
String mappedBy = this.searchMappedBy( referencedClass, collectionValue ); String mappedBy = this.searchMappedBy( referencedClass, valueHolder );
if ( mappedBy == null ) { if ( mappedBy == null ) {
LOG.debugf( LOG.debugf(
@ -855,9 +863,9 @@ public final class CollectionMetadataGenerator {
); );
PersistentClass tempClass = referencedClass; PersistentClass tempClass = referencedClass;
while ( (mappedBy == null) && (tempClass.getSuperclass() != null) ) { while ( mappedBy == null && tempClass.getSuperclass() != null ) {
LOG.debugf( "Searching in superclass: %s", tempClass.getSuperclass().getClassName() ); LOG.debugf( "Searching in superclass: %s", tempClass.getSuperclass().getClassName() );
mappedBy = this.searchMappedBy( tempClass.getSuperclass(), collectionValue ); mappedBy = this.searchMappedBy( tempClass.getSuperclass(), valueHolder );
tempClass = tempClass.getSuperclass(); tempClass = tempClass.getSuperclass();
} }
} }
@ -872,6 +880,13 @@ public final class CollectionMetadataGenerator {
return mappedBy; return mappedBy;
} }
private String searchMappedBy(PersistentClass persistentClass, ValueHolder valueHolder) {
if ( valueHolder.getCollection() != null ) {
return searchMappedBy( persistentClass, valueHolder.getCollection() );
}
return searchMappedBy( persistentClass, valueHolder.getTable() );
}
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
private String searchMappedBy(PersistentClass referencedClass, Collection collectionValue) { private String searchMappedBy(PersistentClass referencedClass, Collection collectionValue) {
final Iterator<Property> assocClassProps = referencedClass.getPropertyIterator(); final Iterator<Property> assocClassProps = referencedClass.getPropertyIterator();
@ -885,43 +900,9 @@ public final class CollectionMetadataGenerator {
return property.getName(); return property.getName();
} }
} }
return null; // HHH-7625
} // Support ToOne relations with mappedBy that point to an @IdClass key property.
return searchMappedByKey( referencedClass, collectionValue );
private String getMappedBy(Table collectionTable, PersistentClass referencedClass) {
// If there's an @AuditMappedBy specified, returning it directly.
final String auditMappedBy = propertyAuditingData.getAuditMappedBy();
if ( auditMappedBy != null ) {
return auditMappedBy;
}
// searching in referenced class
String mappedBy = this.searchMappedBy( referencedClass, collectionTable );
// not found on referenced class, searching on superclasses
if ( mappedBy == null ) {
LOG.debugf(
"Going to search the mapped by attribute for %s in superclasses of entity: %s",
propertyName,
referencedClass.getClassName()
);
PersistentClass tempClass = referencedClass;
while ( (mappedBy == null) && (tempClass.getSuperclass() != null) ) {
LOG.debugf( "Searching in superclass: %s", tempClass.getSuperclass().getClassName() );
mappedBy = this.searchMappedBy( tempClass.getSuperclass(), collectionTable );
tempClass = tempClass.getSuperclass();
}
}
if ( mappedBy == null ) {
throw new MappingException(
"Unable to read the mapped by attribute for " + propertyName + " in "
+ referencedClass.getClassName() + "!"
);
}
return mappedBy;
} }
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
@ -940,4 +921,69 @@ public final class CollectionMetadataGenerator {
return null; return null;
} }
@SuppressWarnings({"unchecked"})
private String searchMappedByKey(PersistentClass referencedClass, Collection collectionValue) {
final Iterator<Value> assocIdClassProps = referencedClass.getKeyClosureIterator();
while ( assocIdClassProps.hasNext() ) {
final Value value = assocIdClassProps.next();
// make sure its a 'Component' because IdClass is registered as this type.
if ( value instanceof Component ) {
final Component component = (Component) value;
final Iterator<Property> componentPropertyIterator = component.getPropertyIterator();
while ( componentPropertyIterator.hasNext() ) {
final Property property = componentPropertyIterator.next();
final Iterator<Selectable> propertySelectables = property.getValue().getColumnIterator();
final Iterator<Selectable> collectionSelectables = collectionValue.getKey().getColumnIterator();
if ( Tools.iteratorsContentEqual( propertySelectables, collectionSelectables ) ) {
return property.getName();
}
}
}
}
return null;
}
private PersistentClass getReferenceCollectionClass(Collection collectionValue) {
PersistentClass referencedClass = null;
if ( collectionValue.getElement() instanceof OneToMany ) {
final OneToMany oneToManyValue = (OneToMany) collectionValue.getElement();
referencedClass = oneToManyValue.getAssociatedClass();
}
else if ( collectionValue.getElement() instanceof ManyToOne ) {
// Case for bi-directional relation with @JoinTable on the owning @ManyToOne side.
final ManyToOne manyToOneValue = (ManyToOne) collectionValue.getElement();
referencedClass = manyToOneValue.getMetadata().getEntityBinding( manyToOneValue.getReferencedEntityName() );
}
return referencedClass;
}
private boolean isMappedByKey(Collection collectionValue, String mappedBy) {
final PersistentClass referencedClass = getReferenceCollectionClass( collectionValue );
if ( referencedClass != null ) {
final String keyMappedBy = searchMappedByKey( referencedClass, collectionValue );
return mappedBy.equals( keyMappedBy );
}
return false;
}
private class ValueHolder {
private Collection collection;
private Table table;
public ValueHolder(Collection collection) {
this.collection = collection;
}
public ValueHolder(Table table) {
this.table = table;
}
public Collection getCollection() {
return collection;
}
public Table getTable() {
return table;
}
}
} }

View File

@ -14,6 +14,7 @@ import org.hibernate.service.ServiceRegistry;
/** /**
* @author Adam Warski (adam at warski dot org) * @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/ */
public abstract class AbstractIdMapper implements IdMapper { public abstract class AbstractIdMapper implements IdMapper {
private final ServiceRegistry serviceRegistry; private final ServiceRegistry serviceRegistry;
@ -119,6 +120,25 @@ public abstract class AbstractIdMapper implements IdMapper {
} }
} }
@Override
public void addNamedIdEqualsToQuery(Parameters parameters, String prefix1, IdMapper mapper, boolean equals) {
final List<QueryParameterData> paramDatas1 = mapToQueryParametersFromId( null );
final List<QueryParameterData> paramDatas2 = mapper.mapToQueryParametersFromId( null );
final Parameters parametersToUse = getParametersToUse( parameters, paramDatas1 );
final Iterator<QueryParameterData> paramDataIter1 = paramDatas1.iterator();
final Iterator<QueryParameterData> paramDataIter2 = paramDatas2.iterator();
while ( paramDataIter1.hasNext() ) {
final QueryParameterData paramData1 = paramDataIter1.next();
final QueryParameterData paramData2 = paramDataIter2.next();
parametersToUse.addWhereWithNamedParam(
paramData1.getProperty( prefix1 ),
equals ? "=" : "<>",
paramData2.getQueryParameterName()
);
}
}
private void handleNullValue(Parameters parameters, String alias, String propertyName, boolean equals) { private void handleNullValue(Parameters parameters, String alias, String propertyName, boolean equals) {
if ( equals ) { if ( equals ) {
parameters.addNullRestriction( alias, propertyName ); parameters.addNullRestriction( alias, propertyName );

View File

@ -14,6 +14,7 @@ import org.hibernate.service.ServiceRegistry;
/** /**
* @author Adam Warski (adam at warski dot org) * @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/ */
public interface IdMapper { public interface IdMapper {
ServiceRegistry getServiceRegistry(); ServiceRegistry getServiceRegistry();
@ -96,4 +97,17 @@ public interface IdMapper {
* @param equals Should this query express the "=" relation or the "<>" relation. * @param equals Should this query express the "=" relation or the "<>" relation.
*/ */
void addNamedIdEqualsToQuery(Parameters parameters, String prefix, boolean equals); void addNamedIdEqualsToQuery(Parameters parameters, String prefix, boolean equals);
/**
* Adds query statements, which contains named parameters that express the property that the id of the entity
* with alias prefix is equal to the given object using the specified mapper.
*
* @param parameters Parameters, to which to add the statements.
* @param prefix Prefix to add to the properties (may be null).
* @param mapper The identifier mapper to use
* @param equals Should this query express the "=" relation or the "<>" relation.
*
* @since 5.2.2
*/
void addNamedIdEqualsToQuery(Parameters parameters, String prefix, IdMapper mapper, boolean equals);
} }

View File

@ -8,6 +8,8 @@ package org.hibernate.envers.internal.entities.mapper.relation.query;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration; import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.configuration.internal.GlobalConfiguration; import org.hibernate.envers.configuration.internal.GlobalConfiguration;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.entities.mapper.id.MultipleIdMapper;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData; import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.tools.query.Parameters; import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder; import org.hibernate.envers.internal.tools.query.QueryBuilder;
@ -23,17 +25,35 @@ import static org.hibernate.envers.internal.entities.mapper.relation.query.Query
* *
* @author Adam Warski (adam at warski dot org) * @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/ */
public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGenerator { public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGenerator {
private final String queryString; private final String queryString;
private final String queryRemovedString; private final String queryRemovedString;
private final String mappedBy;
private final boolean multipleIdMapperKey;
public OneAuditEntityQueryGenerator( public OneAuditEntityQueryGenerator(
GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg, GlobalConfiguration globalCfg,
AuditStrategy auditStrategy, MiddleIdData referencingIdData, AuditEntitiesConfiguration verEntCfg,
String referencedEntityName, MiddleIdData referencedIdData, boolean revisionTypeInId) { AuditStrategy auditStrategy,
MiddleIdData referencingIdData,
String referencedEntityName,
MiddleIdData referencedIdData,
boolean revisionTypeInId,
String mappedBy,
boolean mappedByKey) {
super( verEntCfg, referencingIdData, revisionTypeInId ); super( verEntCfg, referencingIdData, revisionTypeInId );
this.mappedBy = mappedBy;
if ( ( referencedIdData.getOriginalMapper() instanceof MultipleIdMapper ) && mappedByKey ) {
multipleIdMapperKey = true;
}
else {
multipleIdMapperKey = false;
}
/* /*
* The valid query that we need to create: * The valid query that we need to create:
* SELECT e FROM versionsReferencedEntity e * SELECT e FROM versionsReferencedEntity e
@ -72,8 +92,17 @@ public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGen
final QueryBuilder qb = new QueryBuilder( versionsReferencedEntityName, REFERENCED_ENTITY_ALIAS ); final QueryBuilder qb = new QueryBuilder( versionsReferencedEntityName, REFERENCED_ENTITY_ALIAS );
qb.addProjection( null, REFERENCED_ENTITY_ALIAS, null, false ); qb.addProjection( null, REFERENCED_ENTITY_ALIAS, null, false );
// WHERE // WHERE
// e.id_ref_ed = :id_ref_ed if ( multipleIdMapperKey ) {
referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( qb.getRootParameters(), null, true ); // HHH-7625
// support @OneToMany(mappedBy) to @ManyToOne @IdClass attribute.
// e.originalId.id_ref_ed.id = :id_ref_ed
final IdMapper mapper = getMultipleIdPrefixedMapper();
mapper.addNamedIdEqualsToQuery( qb.getRootParameters(), null, referencingIdData.getPrefixedMapper(), true );
}
else {
// e.id_ref_ed = :id_ref_ed
referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( qb.getRootParameters(), null, true );
}
return qb; return qb;
} }
@ -124,4 +153,9 @@ public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGen
protected String getQueryRemovedString() { protected String getQueryRemovedString() {
return queryRemovedString; return queryRemovedString;
} }
private IdMapper getMultipleIdPrefixedMapper() {
final String prefix = verEntCfg.getOriginalIdPropName() + "." + mappedBy + ".";
return referencingIdData.getOriginalMapper().prefixMappedProperties( prefix );
}
} }