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.TreeMap;
import java.util.TreeSet;
import javax.persistence.JoinColumn;
import org.dom4j.Element;
import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.envers.ModificationStore;
@ -68,6 +70,7 @@ import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.OneToMany;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.Value;
import org.hibernate.type.BagType;
@ -79,16 +82,14 @@ import org.hibernate.type.SetType;
import org.hibernate.type.SortedMapType;
import org.hibernate.type.SortedSetType;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
import org.dom4j.Element;
/**
* Generates metadata for a collection-valued property.
*
* @author Adam Warski (adam at warski dot org)
* @author HernпїЅn Chanfreau
* @author Chris Cranford
*/
public final class CollectionMetadataGenerator {
private static final EnversMessageLogger LOG = Logger.getMessageLogger(
@ -123,8 +124,10 @@ public final class CollectionMetadataGenerator {
*/
public CollectionMetadataGenerator(
AuditMetadataGenerator mainGenerator,
Collection propertyValue, CompositeMapperBuilder currentMapper,
String referencingEntityName, EntityXmlMappingData xmlMappingData,
Collection propertyValue,
CompositeMapperBuilder currentMapper,
String referencingEntityName,
EntityXmlMappingData xmlMappingData,
PropertyAuditingData propertyAuditingData) {
this.mainGenerator = mainGenerator;
this.propertyValue = propertyValue;
@ -164,7 +167,10 @@ public final class CollectionMetadataGenerator {
private MiddleIdData createMiddleIdData(IdMappingData idMappingData, String prefix, String entityName) {
return new MiddleIdData(
mainGenerator.getVerEntCfg(), idMappingData, prefix, entityName,
mainGenerator.getVerEntCfg(),
idMappingData,
prefix,
entityName,
mainGenerator.getEntitiesConfigurations().containsKey( entityName )
);
}
@ -219,7 +225,9 @@ public final class CollectionMetadataGenerator {
referencingIdData,
referencedEntityName,
referencedIdData,
isEmbeddableElementType()
isEmbeddableElementType(),
mappedBy,
isMappedByKey( propertyValue, mappedBy )
);
// Creating common mapper data.
@ -300,7 +308,8 @@ public final class CollectionMetadataGenerator {
*/
@SuppressWarnings({"unchecked"})
private void addRelatedToXmlMapping(
Element xmlMapping, String prefix,
Element xmlMapping,
String prefix,
MetadataTools.ColumnNameIterator columnNameIterator,
IdMappingData relatedIdMapping) {
final Element properties = (Element) relatedIdMapping.getXmlRelationMapping().clone();
@ -827,17 +836,16 @@ public final class CollectionMetadataGenerator {
}
private String getMappedBy(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() );
}
final PersistentClass referencedClass = getReferenceCollectionClass( collectionValue );
final ValueHolder valueHolder = new ValueHolder( collectionValue );
return getMappedBy( referencedClass, valueHolder );
}
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.
final String auditMappedBy = propertyAuditingData.getAuditMappedBy();
if ( auditMappedBy != null ) {
@ -845,7 +853,7 @@ public final class CollectionMetadataGenerator {
}
// searching in referenced class
String mappedBy = this.searchMappedBy( referencedClass, collectionValue );
String mappedBy = this.searchMappedBy( referencedClass, valueHolder );
if ( mappedBy == null ) {
LOG.debugf(
@ -855,9 +863,9 @@ public final class CollectionMetadataGenerator {
);
PersistentClass tempClass = referencedClass;
while ( (mappedBy == null) && (tempClass.getSuperclass() != null) ) {
while ( mappedBy == null && tempClass.getSuperclass() != null ) {
LOG.debugf( "Searching in superclass: %s", tempClass.getSuperclass().getClassName() );
mappedBy = this.searchMappedBy( tempClass.getSuperclass(), collectionValue );
mappedBy = this.searchMappedBy( tempClass.getSuperclass(), valueHolder );
tempClass = tempClass.getSuperclass();
}
}
@ -872,6 +880,13 @@ public final class CollectionMetadataGenerator {
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"})
private String searchMappedBy(PersistentClass referencedClass, Collection collectionValue) {
final Iterator<Property> assocClassProps = referencedClass.getPropertyIterator();
@ -885,43 +900,9 @@ public final class CollectionMetadataGenerator {
return property.getName();
}
}
return null;
}
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;
// HHH-7625
// Support ToOne relations with mappedBy that point to an @IdClass key property.
return searchMappedByKey( referencedClass, collectionValue );
}
@SuppressWarnings({"unchecked"})
@ -940,4 +921,69 @@ public final class CollectionMetadataGenerator {
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 Chris Cranford
*/
public abstract class AbstractIdMapper implements IdMapper {
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) {
if ( equals ) {
parameters.addNullRestriction( alias, propertyName );

View File

@ -14,6 +14,7 @@ import org.hibernate.service.ServiceRegistry;
/**
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public interface IdMapper {
ServiceRegistry getServiceRegistry();
@ -96,4 +97,17 @@ public interface IdMapper {
* @param equals Should this query express the "=" relation or the "<>" relation.
*/
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.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.tools.query.Parameters;
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 Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/
public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGenerator {
private final String queryString;
private final String queryRemovedString;
private final String mappedBy;
private final boolean multipleIdMapperKey;
public OneAuditEntityQueryGenerator(
GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg,
AuditStrategy auditStrategy, MiddleIdData referencingIdData,
String referencedEntityName, MiddleIdData referencedIdData, boolean revisionTypeInId) {
GlobalConfiguration globalCfg,
AuditEntitiesConfiguration verEntCfg,
AuditStrategy auditStrategy,
MiddleIdData referencingIdData,
String referencedEntityName,
MiddleIdData referencedIdData,
boolean revisionTypeInId,
String mappedBy,
boolean mappedByKey) {
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:
* SELECT e FROM versionsReferencedEntity e
@ -72,8 +92,17 @@ public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGen
final QueryBuilder qb = new QueryBuilder( versionsReferencedEntityName, REFERENCED_ENTITY_ALIAS );
qb.addProjection( null, REFERENCED_ENTITY_ALIAS, null, false );
// WHERE
// e.id_ref_ed = :id_ref_ed
referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( qb.getRootParameters(), null, true );
if ( multipleIdMapperKey ) {
// 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;
}
@ -124,4 +153,9 @@ public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGen
protected String getQueryRemovedString() {
return queryRemovedString;
}
private IdMapper getMultipleIdPrefixedMapper() {
final String prefix = verEntCfg.getOriginalIdPropName() + "." + mappedBy + ".";
return referencingIdData.getOriginalMapper().prefixMappedProperties( prefix );
}
}