From 1d41a3f761d7af01812b3553a9c68ba5532fca35 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Thu, 22 Dec 2016 16:07:51 -0500 Subject: [PATCH] HHH-9834 - Fix failure when deleting and inserting values with the same Map key. --- .../metadata/CollectionMetadataGenerator.java | 36 +++++++----- .../strategy/ValidityAuditStrategy.java | 55 ++++++++++++------- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/CollectionMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/CollectionMetadataGenerator.java index e09d66db0a..71a9d022d8 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/CollectionMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/CollectionMetadataGenerator.java @@ -449,7 +449,7 @@ public final class CollectionMetadataGenerator { mainGenerator.getAuditStrategy(), referencingIdData, auditMiddleEntityName, - isEmbeddableElementType() + isRevisionTypeInId() ); // Adding the XML mapping for the referencing entity, if the relation isn't inverse. @@ -471,7 +471,7 @@ public final class CollectionMetadataGenerator { queryGeneratorBuilder, referencedPrefix, propertyAuditingData.getJoinTable().inverseJoinColumns(), - isCollectionElementKeyProperty() + !isLobMapElementType() ); // ****** @@ -723,6 +723,7 @@ public final class CollectionMetadataGenerator { MiddleComponentData indexComponentData) { final Type type = propertyValue.getType(); final boolean embeddableElementType = isEmbeddableElementType(); + final boolean lobMapElementType = isLobMapElementType(); if ( type instanceof SortedSetType ) { currentMapper.addComposite( propertyAuditingData.getPropertyData(), @@ -761,7 +762,7 @@ public final class CollectionMetadataGenerator { elementComponentData, indexComponentData, propertyValue.getComparator(), - embeddableElementType + embeddableElementType || lobMapElementType ) ); } @@ -775,7 +776,7 @@ public final class CollectionMetadataGenerator { MapProxy.class, elementComponentData, indexComponentData, - embeddableElementType + embeddableElementType || lobMapElementType ) ); } @@ -854,9 +855,9 @@ public final class CollectionMetadataGenerator { // Adding the revision type property to the entity xml. mainGenerator.addRevisionType( - isEmbeddableElementType() ? middleEntityXmlId : middleEntityXml, + isRevisionTypeInId() ? middleEntityXmlId : middleEntityXml, middleEntityXml, - isEmbeddableElementType() + isRevisionTypeInId() ); // All other properties should also be part of the primary key of the middle entity. @@ -1023,20 +1024,27 @@ public final class CollectionMetadataGenerator { } /** - * Checks whether the collection element should participate in the primary key association. This - * is only applicable for non-component, non-associative collection element types. + * Returns whether the revision type column part of the collection table's primary key. * - * @return {@code true} if should be part of the primary key, {@code false} if not. + * @return {@code true} if the revision type should be part of the primary key, otherwise {@code false}. */ - private boolean isCollectionElementKeyProperty() { - // When List or Set are used, the element will always be part of the primary key. - // When using a Map, the element isn't required as the Map Key will suffice. + private boolean isRevisionTypeInId() { + return isEmbeddableElementType() || isLobMapElementType(); + } + + /** + * Returns whether the collection is a map-type and that the map element is defined as a Clob/NClob type. + * + * @return {@code true} if the element is a Clob/NClob type, otherwise {@code false}. + */ + private boolean isLobMapElementType() { if ( propertyValue instanceof org.hibernate.mapping.Map ) { final Type type = propertyValue.getElement().getType(); + // we're only interested in basic types if ( !type.isComponentType() && !type.isAssociationType() ) { - return !( type instanceof MaterializedClobType || type instanceof MaterializedNClobType ); + return ( type instanceof MaterializedClobType ) || ( type instanceof MaterializedNClobType ); } } - return true; + return false; } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java index 2fd61bc812..4c6ee6a390 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java @@ -36,6 +36,9 @@ import org.hibernate.property.access.spi.Getter; import org.hibernate.sql.Update; import org.hibernate.type.CollectionType; import org.hibernate.type.ComponentType; +import org.hibernate.type.MapType; +import org.hibernate.type.MaterializedClobType; +import org.hibernate.type.MaterializedNClobType; import org.hibernate.type.Type; import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS; @@ -260,25 +263,8 @@ public class ValidityAuditStrategy implements AuditStrategy { } } - final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) session ).getFactory(); - final Type propertyType = sessionFactory.getMetamodel().entityPersister( entityName ).getPropertyType( propertyName ); - if ( propertyType.isCollectionType() ) { - CollectionType collectionPropertyType = (CollectionType) propertyType; - // Handling collection of components. - if ( collectionPropertyType.getElementType( sessionFactory ) instanceof ComponentType ) { - // Adding restrictions to compare data outside of primary key. - // todo: is it necessary that non-primary key attributes be compared? - for ( Map.Entry dataEntry : persistentCollectionChangeData.getData().entrySet() ) { - if ( !originalIdPropName.equals( dataEntry.getKey() ) ) { - if ( dataEntry.getValue() != null ) { - qb.getRootParameters().addWhereWithParam( dataEntry.getKey(), true, "=", dataEntry.getValue() ); - } - else { - qb.getRootParameters().addNullRestriction( dataEntry.getKey(), true ); - } - } - } - } + if ( isNonIdentifierWhereConditionsRequired( entityName, propertyName, (SessionImplementor) session ) ) { + addNonIdentifierWhereConditions( qb, persistentCollectionChangeData.getData(), originalIdPropName ); } addEndRevisionNullRestriction( auditEntitiesConfiguration, qb.getRootParameters() ); @@ -299,6 +285,37 @@ public class ValidityAuditStrategy implements AuditStrategy { sessionCacheCleaner.scheduleAuditDataRemoval( session, persistentCollectionChangeData.getData() ); } + private boolean isNonIdentifierWhereConditionsRequired(String entityName, String propertyName, SessionImplementor session) { + final Type propertyType = session.getSessionFactory().getMetamodel().entityPersister( entityName ).getPropertyType( propertyName ); + if ( propertyType.isCollectionType() ) { + final CollectionType collectionType = (CollectionType) propertyType; + final Type collectionElementType = collectionType.getElementType( session.getSessionFactory() ); + if ( collectionElementType instanceof ComponentType ) { + // required for Embeddables + return true; + } + else if ( collectionElementType instanceof MaterializedClobType || collectionElementType instanceof MaterializedNClobType ) { + // for Map<> using @Lob annotations + return collectionType instanceof MapType; + } + } + return false; + } + + private void addNonIdentifierWhereConditions(QueryBuilder qb, Map data, String originalIdPropertyName) { + final Parameters parameters = qb.getRootParameters(); + for ( Map.Entry entry : data.entrySet() ) { + if ( !originalIdPropertyName.equals( entry.getKey() ) ) { + if ( entry.getValue() != null ) { + parameters.addWhereWithParam( entry.getKey(), true, "=", entry.getValue() ); + } + else { + parameters.addNullRestriction( entry.getKey(), true ); + } + } + } + } + private void addEndRevisionNullRestriction(AuditEntitiesConfiguration auditEntitiesConfiguration, Parameters rootParameters) { rootParameters.addWhere( auditEntitiesConfiguration.getRevisionEndFieldName(), true, "is", "null", false ); }