From ea00d6984ba3947a17c972ad3c659e6544d6aa84 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Wed, 29 Oct 2025 10:30:18 +0100 Subject: [PATCH 1/2] HHH-19897 Add test for issue --- .../MappedByNotAuditedToManyTest.java | 67 +++++++++++++++++++ .../MappedByNotAuditedManyToOneTest.java | 66 ++++++++++++++++++ .../MappedByNotAuditedOneToOneTest.java | 64 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytomany/MappedByNotAuditedToManyTest.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytoone/bidirectional/MappedByNotAuditedManyToOneTest.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/onetoone/bidirectional/MappedByNotAuditedOneToOneTest.java diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytomany/MappedByNotAuditedToManyTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytomany/MappedByNotAuditedToManyTest.java new file mode 100644 index 000000000000..70558f85f7bc --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytomany/MappedByNotAuditedToManyTest.java @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.envers.integration.manytomany; + +import java.util.List; + +import org.hibernate.cfg.Configuration; +import org.hibernate.envers.Audited; +import org.hibernate.envers.NotAudited; +import org.hibernate.envers.boot.EnversMappingException; + +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.junit.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToMany; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; + +@Jira("https://hibernate.atlassian.net/browse/HHH-19897") +public class MappedByNotAuditedToManyTest { + @Test + public void testMappingException() { + final Configuration cfg = new Configuration().addAnnotatedClasses( ToManyParent.class, ToManyChild.class ); + ServiceRegistryUtil.applySettings( cfg.getStandardServiceRegistryBuilder() ); + final var enversMappingException = assertThrows( + "Expecting EnversMappingException to be thrown due to @NotAudited on owning side of bidirectional association", + EnversMappingException.class, + cfg::buildSessionFactory + ); + assertThat( enversMappingException ).hasMessageContainingAll( + "Could not resolve mapped by property for association", + "parents" + ); + } + + @Entity(name = "ToOneEntity") + @Audited + static class ToManyParent { + @Id + private Long id; + + private String parentData; + + @ManyToMany + @NotAudited + private List children; + } + + @Entity(name = "ToOneChild") + @Audited + static class ToManyChild { + @Id + private Long id; + + private String childData; + + @OneToMany(mappedBy = "children") + private List parents; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytoone/bidirectional/MappedByNotAuditedManyToOneTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytoone/bidirectional/MappedByNotAuditedManyToOneTest.java new file mode 100644 index 000000000000..db3483759407 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytoone/bidirectional/MappedByNotAuditedManyToOneTest.java @@ -0,0 +1,66 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.envers.integration.manytoone.bidirectional; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import org.hibernate.cfg.Configuration; +import org.hibernate.envers.Audited; +import org.hibernate.envers.NotAudited; +import org.hibernate.envers.boot.EnversMappingException; + +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; + +@Jira("https://hibernate.atlassian.net/browse/HHH-19897") +public class MappedByNotAuditedManyToOneTest { + @Test + public void testMappingException() { + final Configuration cfg = new Configuration().addAnnotatedClasses( ToOneParent.class, ToOneChild.class ); + ServiceRegistryUtil.applySettings( cfg.getStandardServiceRegistryBuilder() ); + final var enversMappingException = assertThrows( + "Expecting EnversMappingException to be thrown due to @NotAudited on owning side of bidirectional association", + EnversMappingException.class, + cfg::buildSessionFactory + ); + assertThat( enversMappingException ).hasMessageContainingAll( + "Could not resolve mapped by property for association", + "parents" + ); + } + + @Entity(name = "ToOneEntity") + @Audited + static class ToOneParent { + @Id + private Long id; + + private String parentData; + + @ManyToOne + @NotAudited + private ToOneChild child; + } + + @Entity(name = "ToOneChild") + @Audited + static class ToOneChild { + @Id + private Long id; + + private String childData; + + @OneToMany(mappedBy = "child") + private List parents; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/onetoone/bidirectional/MappedByNotAuditedOneToOneTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/onetoone/bidirectional/MappedByNotAuditedOneToOneTest.java new file mode 100644 index 000000000000..c819903c735b --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/onetoone/bidirectional/MappedByNotAuditedOneToOneTest.java @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.envers.integration.onetoone.bidirectional; + +import org.hibernate.cfg.Configuration; +import org.hibernate.envers.Audited; +import org.hibernate.envers.NotAudited; +import org.hibernate.envers.boot.EnversMappingException; + +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.junit.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OneToOne; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; + +@Jira("https://hibernate.atlassian.net/browse/HHH-19897") +public class MappedByNotAuditedOneToOneTest { + @Test + public void testMappingException() { + final Configuration cfg = new Configuration().addAnnotatedClasses( ToOneParent.class, ToOneChild.class ); + ServiceRegistryUtil.applySettings( cfg.getStandardServiceRegistryBuilder() ); + final var enversMappingException = assertThrows( + "Expecting EnversMappingException to be thrown due to @NotAudited on owning side of bidirectional association", + EnversMappingException.class, + cfg::buildSessionFactory + ); + assertThat( enversMappingException ).hasMessageContainingAll( + "Could not resolve mapped by property [child] for association", + "parent" + ); + } + + @Entity(name = "ToOneEntity") + @Audited + static class ToOneParent { + @Id + private Long id; + + private String parentData; + + @OneToOne + @NotAudited + private ToOneChild child; + } + + @Entity(name = "ToOneChild") + @Audited + static class ToOneChild { + @Id + private Long id; + + private String childData; + + @OneToOne(mappedBy = "child") + private ToOneParent parent; + } +} From 5baf43ad70ef4cfdf38e532218ce7a22ddf54b36 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Wed, 29 Oct 2025 10:31:32 +0100 Subject: [PATCH 2/2] HHH-19897 Envers - check mapped-by side is audited for associations --- .../EnversMetadataBuildingContextImpl.java | 8 + .../spi/EnversMetadataBuildingContext.java | 3 + .../internal/ClassesAuditingData.java | 4 + .../internal/EntitiesConfigurator.java | 2 +- .../metadata/CollectionMappedByResolver.java | 143 +++++++++++------- ...JoinColumnCollectionMetadataGenerator.java | 30 ++-- ...iddleTableCollectionMetadataGenerator.java | 24 ++- .../ToOneRelationMetadataGenerator.java | 44 +++++- .../hashcode/ListHashcodeChangeTest.java | 2 - .../hashcode/SetHashcodeChangeTest.java | 2 - 10 files changed, 176 insertions(+), 86 deletions(-) diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversMetadataBuildingContextImpl.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversMetadataBuildingContextImpl.java index ac25e10e24ed..5f85da14c12f 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversMetadataBuildingContextImpl.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversMetadataBuildingContextImpl.java @@ -12,6 +12,7 @@ import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.envers.boot.spi.EnversMetadataBuildingContext; import org.hibernate.envers.configuration.Configuration; +import org.hibernate.envers.configuration.internal.ClassesAuditingData; import org.hibernate.envers.configuration.internal.MappingCollector; import org.hibernate.envers.configuration.internal.metadata.AuditEntityConfigurationRegistry; import org.hibernate.envers.configuration.internal.metadata.AuditEntityNameRegister; @@ -33,6 +34,7 @@ public class EnversMetadataBuildingContextImpl implements EnversMetadataBuilding private final ObjectNameNormalizer objectNameNormalizer; private final AuditEntityNameRegister auditEntityNameRegistry; private final AuditEntityConfigurationRegistry auditEntityConfigurationRegistry; + private final ClassesAuditingData classesAuditingData; public EnversMetadataBuildingContextImpl( Configuration configuration, @@ -45,6 +47,7 @@ public EnversMetadataBuildingContextImpl( this.mappingCollector = mappingCollector; this.auditEntityNameRegistry = new AuditEntityNameRegister(); this.auditEntityConfigurationRegistry = new AuditEntityConfigurationRegistry(); + this.classesAuditingData = new ClassesAuditingData(); this.objectNameNormalizer = new ObjectNameNormalizer(this); } @@ -118,4 +121,9 @@ public AuditEntityNameRegister getAuditEntityNameRegistry() { public AuditEntityConfigurationRegistry getAuditEntityConfigurationRegistry() { return auditEntityConfigurationRegistry; } + + @Override + public ClassesAuditingData getClassesAuditingData() { + return classesAuditingData; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/spi/EnversMetadataBuildingContext.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/spi/EnversMetadataBuildingContext.java index 77f2b776860a..201f5619785d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/boot/spi/EnversMetadataBuildingContext.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/spi/EnversMetadataBuildingContext.java @@ -6,6 +6,7 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.envers.configuration.Configuration; +import org.hibernate.envers.configuration.internal.ClassesAuditingData; import org.hibernate.envers.configuration.internal.MappingCollector; import org.hibernate.envers.configuration.internal.metadata.AuditEntityConfigurationRegistry; import org.hibernate.envers.configuration.internal.metadata.AuditEntityNameRegister; @@ -32,4 +33,6 @@ public interface EnversMetadataBuildingContext extends MetadataBuildingContext { AuditEntityNameRegister getAuditEntityNameRegistry(); AuditEntityConfigurationRegistry getAuditEntityConfigurationRegistry(); + + ClassesAuditingData getClassesAuditingData(); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/ClassesAuditingData.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/ClassesAuditingData.java index 0d8a057b2d27..796ea84f9d66 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/ClassesAuditingData.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/ClassesAuditingData.java @@ -61,6 +61,10 @@ public Collection getAllClassAuditedData() { return persistentClassToAuditingData.values(); } + public ClassAuditingData getClassAuditingData(String entityName) { + return entityNameToAuditingData.get( entityName ); + } + /** * After all meta-data is read, updates calculated fields. This includes: *
    diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/EntitiesConfigurator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/EntitiesConfigurator.java index de7a713eb3ee..fb92871652be 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/EntitiesConfigurator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/EntitiesConfigurator.java @@ -38,7 +38,7 @@ public EntitiesConfigurations configure(EnversMetadataBuildingContext metadataBu final Iterator classes = GraphTopologicalSort.sort( new PersistentClassGraphDefiner( metadata ) ) .iterator(); - final ClassesAuditingData classesAuditingData = new ClassesAuditingData(); + final ClassesAuditingData classesAuditingData = metadataBuildingContext.getClassesAuditingData(); // Reading metadata from annotations final AnnotationsMetadataReader reader = new AnnotationsMetadataReader( metadataBuildingContext ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/CollectionMappedByResolver.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/CollectionMappedByResolver.java index 20eb74a6df16..31711780631d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/CollectionMappedByResolver.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/CollectionMappedByResolver.java @@ -4,26 +4,27 @@ */ package org.hibernate.envers.configuration.internal.metadata; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Locale; -import java.util.Objects; - import org.hibernate.envers.boot.EnversMappingException; +import org.hibernate.envers.boot.spi.EnversMetadataBuildingContext; +import org.hibernate.envers.configuration.internal.metadata.reader.AuditedPropertiesHolder; +import org.hibernate.envers.configuration.internal.metadata.reader.ClassAuditingData; +import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData; import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData; import org.hibernate.envers.internal.EnversMessageLogger; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Component; import org.hibernate.mapping.KeyValue; -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.jboss.logging.Logger; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + /** * Helper class that provides a way to resolve the {@code mappedBy} attribute for collections. * @@ -37,18 +38,39 @@ public class CollectionMappedByResolver { CollectionMappedByResolver.class.getName() ); - public static String resolveMappedBy(Collection collection, PropertyAuditingData propertyAuditingData) { - final PersistentClass referencedClass = getReferenceCollectionClass( collection ); + public static String resolveMappedBy( + String entityName, + Collection collection, + PropertyAuditingData propertyAuditingData, + String referencedEntityName, + EnversMetadataBuildingContext buildingContext) { + final var referencedClass = getReferencedClass( referencedEntityName, buildingContext ); final ResolverContext resolverContext = new ResolverContext( collection, propertyAuditingData ); - return getMappedBy( referencedClass, resolverContext ); + return getMappedBy( entityName, referencedClass, resolverContext, buildingContext ); } - public static String resolveMappedBy(Table collectionTable, PersistentClass referencedClass, PropertyAuditingData propertyAuditingData) { - return getMappedBy( referencedClass, new ResolverContext( collectionTable, propertyAuditingData ) ); + public static String resolveMappedBy( + String entityName, + Table collectionTable, + PropertyAuditingData propertyAuditingData, + String referencedEntityName, + EnversMetadataBuildingContext buildingContext) { + final var referencedClass = getReferencedClass( referencedEntityName, buildingContext ); + return getMappedBy( + entityName, + referencedClass, + new ResolverContext( collectionTable, propertyAuditingData ), + buildingContext + ); } - public static boolean isMappedByKey(Collection collection, String mappedBy) { - final PersistentClass referencedClass = getReferenceCollectionClass( collection ); + public static boolean isMappedByKey( + Collection collection, + String mappedBy, + String referencedEntityName, + EnversMetadataBuildingContext buildingContext) { + final PersistentClass referencedClass = getReferencedClass( referencedEntityName, + buildingContext ).getPersistentClass(); if ( referencedClass != null ) { final String keyMappedBy = searchMappedByKey( referencedClass, collection ); return mappedBy.equals( keyMappedBy ); @@ -56,7 +78,11 @@ public static boolean isMappedByKey(Collection collection, String mappedBy) { return false; } - private static String getMappedBy(PersistentClass referencedClass, ResolverContext resolverContext) { + private static String getMappedBy( + String entityName, + ClassAuditingData referencedClass, + ResolverContext resolverContext, + EnversMetadataBuildingContext buildingContext) { // If there's an @AuditMappedBy specified, returning it directly. final String auditMappedBy = resolverContext.propertyAuditingData.getAuditMappedBy(); if ( auditMappedBy != null ) { @@ -70,13 +96,15 @@ private static String getMappedBy(PersistentClass referencedClass, ResolverConte LOG.debugf( "Going to search the mapped by attribute for %s in superclasses of entity: %s", resolverContext.propertyAuditingData.getName(), - referencedClass.getClassName() + referencedClass.getEntityName() ); - PersistentClass tempClass = referencedClass; - while ( mappedBy == null && tempClass.getSuperclass() != null ) { - LOG.debugf( "Searching in superclass: %s", tempClass.getSuperclass().getClassName() ); - mappedBy = searchMappedBy( tempClass.getSuperclass(), resolverContext ); + PersistentClass tempClass = referencedClass.getPersistentClass().getSuperclass(); + while ( mappedBy == null && tempClass != null ) { + final var superclassName = tempClass.getEntityName(); + LOG.debugf( "Searching in superclass: %s", superclassName ); + final var auditingData = buildingContext.getClassesAuditingData().getClassAuditingData( superclassName ); + mappedBy = searchMappedBy( auditingData, resolverContext ); tempClass = tempClass.getSuperclass(); } } @@ -84,10 +112,12 @@ private static String getMappedBy(PersistentClass referencedClass, ResolverConte if ( mappedBy == null ) { throw new EnversMappingException( String.format( - Locale.ENGLISH, - "Unable to read mapped by attribute for %s in %s!", + Locale.ROOT, + "Could not resolve mapped by property for association [%s.%s] in the referenced entity [%s]," + + " please ensure that the association is audited on both sides.", + entityName, resolverContext.propertyAuditingData.getName(), - referencedClass.getClassName() + referencedClass.getEntityName() ) ); } @@ -95,49 +125,64 @@ private static String getMappedBy(PersistentClass referencedClass, ResolverConte return mappedBy; } - private static String searchMappedBy(PersistentClass persistentClass, ResolverContext resolverContext) { + private static String searchMappedBy(ClassAuditingData referencedClass, ResolverContext resolverContext) { if ( resolverContext.getCollection() != null ) { - return searchMappedBy( persistentClass, resolverContext.getCollection() ); + return searchMappedBy( referencedClass, resolverContext.getCollection() ); } - return searchMappedBy( persistentClass, resolverContext.getTable() ); + return searchMappedBy( referencedClass, resolverContext.getTable() ); } - private static String searchMappedBy(PersistentClass referencedClass, Collection collectionValue) { - final List assocClassProps = referencedClass.getProperties(); + private static String searchMappedBy(ClassAuditingData referencedClass, Collection collectionValue) { + final var persistentClass = referencedClass.getPersistentClass(); + final List assocClassProps = referencedClass.getPersistentClass().getProperties(); for ( Property property : assocClassProps ) { final List assocClassSelectables = property.getValue().getSelectables(); final List collectionKeySelectables = collectionValue.getKey().getSelectables(); if ( Objects.equals( assocClassSelectables, collectionKeySelectables ) ) { - return property.getName(); + final var propertyName = property.getName(); + // We need to check if the property is audited as well + return referencedClass.contains( propertyName ) ? propertyName : null; } } // HHH-7625 // Support ToOne relations with mappedBy that point to an @IdClass key property. - return searchMappedByKey( referencedClass, collectionValue ); + return searchMappedByKey( persistentClass, collectionValue ); } - private static String searchMappedBy(PersistentClass referencedClass, Table collectionTable) { - return searchMappedBy( referencedClass.getProperties(), collectionTable ); + private static String searchMappedBy(ClassAuditingData referencedClass, Table collectionTable) { + return searchMappedBy( referencedClass, referencedClass.getPersistentClass().getProperties(), collectionTable ); } - private static String searchMappedBy(List properties, Table collectionTable) { + private static String searchMappedBy(AuditedPropertiesHolder propertiesHolder, List properties, Table collectionTable) { for ( Property property : properties ) { if ( property.getValue() instanceof Collection ) { // The equality is intentional. We want to find a collection property with the same collection table. //noinspection ObjectEquality - if ( ( (Collection) property.getValue() ).getCollectionTable() == collectionTable ) { - return property.getName(); + if ( ((Collection) property.getValue()).getCollectionTable() == collectionTable ) { + final var propertyName = property.getName(); + // We need to check if the property is audited as well + return propertiesHolder.contains( propertyName ) ? propertyName : null; } } - else if ( property.getValue() instanceof Component ) { + else if ( property.getValue() instanceof Component component ) { // HHH-12240 // Should we find an embeddable, we should traverse it as well to see if the collection table // happens to be an attribute inside the embeddable rather than directly on the entity. - final Component component = (Component) property.getValue(); - - final String mappedBy = searchMappedBy( component.getProperties(), collectionTable ); - if ( mappedBy != null ) { - return property.getName() + "_" + mappedBy; + final var componentName = property.getName(); + final var componentData = propertiesHolder.getPropertyAuditingData( componentName ); + if ( componentData == null ) { + // If the component is not audited, no need to check sub-properties + return null; + } + else { + final String mappedBy = searchMappedBy( + (ComponentAuditingData) componentData, + component.getProperties(), + collectionTable + ); + if ( mappedBy != null ) { + return property.getName() + "_" + mappedBy; + } } } } @@ -161,18 +206,8 @@ private static String searchMappedByKey(PersistentClass referencedClass, Collect return null; } - private static 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 bidirectional relation with @JoinTable on the owning @ManyToOne side. - final ManyToOne manyToOneValue = (ManyToOne) collectionValue.getElement(); - referencedClass = manyToOneValue.getMetadata().getEntityBinding( manyToOneValue.getReferencedEntityName() ); - } - return referencedClass; + private static ClassAuditingData getReferencedClass(String className, EnversMetadataBuildingContext buildingContext) { + return buildingContext.getClassesAuditingData().getClassAuditingData( className ); } private static class ResolverContext { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/JoinColumnCollectionMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/JoinColumnCollectionMetadataGenerator.java index 004204e82f05..3c7827686c6b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/JoinColumnCollectionMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/JoinColumnCollectionMetadataGenerator.java @@ -54,19 +54,27 @@ public JoinColumnCollectionMetadataGenerator( @Override public void addCollection(CollectionMetadataContext context) { + final var referencingEntityName = context.getReferencingEntityName(); LOG.debugf( "Adding audit mapping for property %s.%s: one-to-many collection, using a join column on the referenced entity", - context.getReferencingEntityName(), + referencingEntityName, context.getPropertyName() ); final Collection collection = context.getCollection(); final PropertyAuditingData propertyAuditingData = context.getPropertyAuditingData(); - final String mappedBy = CollectionMappedByResolver.resolveMappedBy( collection, propertyAuditingData ); + final var referencedEntityName = context.getReferencedEntityName(); + final String mappedBy = CollectionMappedByResolver.resolveMappedBy( + referencingEntityName, + collection, + propertyAuditingData, + referencedEntityName, + getMetadataBuildingContext() + ); final IdMappingData referencedIdMapping = getReferencedIdMappingData( - context.getReferencingEntityName(), - context.getReferencedEntityName(), + referencingEntityName, + referencedEntityName, propertyAuditingData, false ); @@ -78,7 +86,7 @@ public void addCollection(CollectionMetadataContext context) { final MiddleIdData referencingIdData = createMiddleIdData( referencingIdMapping, mappedBy + "_", - context.getReferencingEntityName() + referencingEntityName ); // And for the referenced side. The prefixed mapper won't be used (as this collection isn't persisted @@ -86,7 +94,7 @@ public void addCollection(CollectionMetadataContext context) { final MiddleIdData referencedIdData = createMiddleIdData( referencedIdMapping, null, - context.getReferencedEntityName() + referencedEntityName ); // Generating the element mapping. @@ -102,18 +110,18 @@ public void addCollection(CollectionMetadataContext context) { final RelationQueryGenerator queryGenerator = new OneAuditEntityQueryGenerator( getMetadataBuildingContext().getConfiguration(), referencingIdData, - context.getReferencedEntityName(), + referencedEntityName, referencedIdData, context.getCollection().getElement() instanceof ComponentType, mappedBy, - CollectionMappedByResolver.isMappedByKey( collection, mappedBy ), + CollectionMappedByResolver.isMappedByKey( collection, mappedBy, referencedEntityName, getMetadataBuildingContext() ), getOrderByCollectionRole( collection, collection.getOrderBy() ) ); // Creating common mapper data. final CommonCollectionMapperData commonCollectionMapperData = createCommonCollectionMapperData( context, - context.getReferencedEntityName(), + referencedEntityName, referencingIdData, queryGenerator ); @@ -126,7 +134,7 @@ public void addCollection(CollectionMetadataContext context) { final String auditMappedBy = getAddOneToManyAttachedAuditMappedBy( context ); fakeBidirectionalRelationMapper = getBidirectionalRelationMapper( - context.getReferencingEntityName(), + referencingEntityName, referencingIdMapping, auditMappedBy ); @@ -153,7 +161,7 @@ public void addCollection(CollectionMetadataContext context) { referencingEntityConfiguration.addToManyNotOwningRelation( context.getPropertyName(), mappedBy, - context.getReferencedEntityName(), + referencedEntityName, referencingIdData.getPrefixedMapper(), fakeBidirectionalRelationMapper, fakeBidirectionalRelationIndexMapper, diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MiddleTableCollectionMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MiddleTableCollectionMetadataGenerator.java index 29506046884c..fa6ef6a53714 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MiddleTableCollectionMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MiddleTableCollectionMetadataGenerator.java @@ -21,7 +21,6 @@ import org.hibernate.envers.internal.tools.StringTools; import org.hibernate.mapping.Collection; import org.hibernate.mapping.OneToMany; -import org.hibernate.mapping.PersistentClass; import org.jboss.logging.Logger; @@ -50,9 +49,10 @@ public MiddleTableCollectionMetadataGenerator( @Override public void addCollection(CollectionMetadataContext context) { + final var referencingEntityName = context.getReferencingEntityName(); LOG.debugf( "Adding audit mapping for property %s.%s: collection with a join table", - context.getReferencingEntityName(), + referencingEntityName, context.getPropertyName() ); @@ -93,6 +93,7 @@ public void addCollection(CollectionMetadataContext context) { // Getting the id-mapping data of the referencing entity (the entity that "owns" this collection). final EntityConfiguration referencingEntityConfiguration = context.getReferencingEntityConfiguration(); final IdMappingData referencingIdMapping = referencingEntityConfiguration.getIdMappingData(); + final var referencedEntityName = context.getReferencedEntityName(); // Only valid for an inverse relation; null otherwise. String mappedBy; @@ -104,25 +105,26 @@ public void addCollection(CollectionMetadataContext context) { if ( context.getCollection().isInverse() ) { // If the relation is inverse, then referencedEntityName is not null. mappedBy = CollectionMappedByResolver.resolveMappedBy( + referencingEntityName, context.getCollection().getCollectionTable(), - getReferencedEntityMapping( context ), - context.getPropertyAuditingData() + context.getPropertyAuditingData(), + referencedEntityName, + getMetadataBuildingContext() ); referencingPrefixRelated = mappedBy + "_"; - referencedPrefix = StringTools.getLastComponent( context.getReferencedEntityName() ); + referencedPrefix = StringTools.getLastComponent( referencedEntityName ); } else { - mappedBy = null; - referencingPrefixRelated = StringTools.getLastComponent( context.getReferencingEntityName() ) + "_"; - referencedPrefix = context.getReferencedEntityName() == null ? "element" : context.getPropertyName(); + referencingPrefixRelated = StringTools.getLastComponent( referencingEntityName ) + "_"; + referencedPrefix = referencedEntityName == null ? "element" : context.getPropertyName(); } // Storing the id data of the referencing entity: original mapper, prefixed mapper and entity name. final MiddleIdData referencingIdData = createMiddleIdData( referencingIdMapping, referencingPrefixRelated, - context.getReferencingEntityName() + referencingEntityName ); // Creating a query generator builder, to which additional id data will be added, in case this collection @@ -255,10 +257,6 @@ private boolean isRevisionTypeInId(CollectionMetadataContext context) { return isEmbeddableElementType( context ) || isLobMapElementType( context ); } - private PersistentClass getReferencedEntityMapping(CollectionMetadataContext context) { - return getMetadataBuildingContext().getMetadataCollector().getEntityBinding( context.getReferencedEntityName() ); - } - private void storeMiddleEntityRelationInformation( CollectionMetadataContext context, String mappedBy, diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/ToOneRelationMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/ToOneRelationMetadataGenerator.java index 955a7aa1bc7c..a06b3fba099b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/ToOneRelationMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/ToOneRelationMetadataGenerator.java @@ -4,10 +4,14 @@ */ package org.hibernate.envers.configuration.internal.metadata; +import java.util.Locale; + import org.hibernate.envers.boot.EnversMappingException; import org.hibernate.envers.boot.model.AttributeContainer; import org.hibernate.envers.boot.spi.EnversMetadataBuildingContext; import org.hibernate.envers.RelationTargetNotFoundAction; +import org.hibernate.envers.configuration.internal.metadata.reader.AuditedPropertiesHolder; +import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData; import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData; import org.hibernate.envers.internal.entities.EntityConfiguration; import org.hibernate.envers.internal.entities.IdMappingData; @@ -115,15 +119,24 @@ public void addOneToOneNotOwning( throw new EnversMappingException( "An audited relation to a non-audited entity " + entityName + "!" ); } + final var referencedEntityName = propertyValue.getReferencedEntityName(); + final var propertyName = propertyAuditingData.getName(); + checkMappedByAudited( + entityName, + propertyName, + referencedEntityName, + owningReferencePropertyName, + getMetadataBuildingContext().getClassesAuditingData().getClassAuditingData( referencedEntityName ) + ); + final String lastPropertyPrefix = MappingTools.createToOneRelationPrefix( owningReferencePropertyName ); - final String referencedEntityName = propertyValue.getReferencedEntityName(); // Generating the id mapper for the relation final IdMapper ownedIdMapper = ownedIdMapping.getIdMapper().prefixMappedProperties( lastPropertyPrefix ); // Storing information about this relation - getAuditedEntityConfiguration( entityName ).addToOneNotOwningRelation( - propertyAuditingData.getName(), + configuration.addToOneNotOwningRelation( + propertyName, owningReferencePropertyName, referencedEntityName, ownedIdMapper, @@ -144,6 +157,31 @@ public void addOneToOneNotOwning( ); } + private static void checkMappedByAudited( + String entityName, + String associationName, + String referencedEntityName, + String referencedPropertyName, + AuditedPropertiesHolder propertiesHolder) { + final var split = referencedPropertyName.split( "\\.", 2 ); + final var auditingData = propertiesHolder.getPropertyAuditingData( split[0] ); + if ( auditingData == null ) { + throw new EnversMappingException( String.format( + Locale.ROOT, + "Could not resolve mapped by property [%s] for association [%s.%s] in the referenced entity [%s]," + + " please ensure that the association is audited on both sides.", + referencedPropertyName, + entityName, + associationName, + referencedEntityName + ) ); + } + else if ( split.length > 1 ) { + // mapped by is a nested component property + checkMappedByAudited( entityName, associationName, referencedEntityName, split[1], (ComponentAuditingData) auditingData ); + } + } + void addOneToOnePrimaryKeyJoinColumn( PropertyAuditingData propertyAuditingData, Value value, diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/hashcode/ListHashcodeChangeTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/hashcode/ListHashcodeChangeTest.java index 276444640313..ec87b6c77eec 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/hashcode/ListHashcodeChangeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/hashcode/ListHashcodeChangeTest.java @@ -11,7 +11,6 @@ import org.hibernate.envers.AuditReader; import org.hibernate.envers.Audited; -import org.hibernate.envers.NotAudited; import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase; import org.hibernate.orm.test.envers.Priority; @@ -256,7 +255,6 @@ public static class Book { @JoinTable(name = "author_book", joinColumns = @JoinColumn(name = "book_id"), inverseJoinColumns = @JoinColumn(name="author_id",nullable = false)) - @NotAudited private Author author; public Integer getId() { diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/hashcode/SetHashcodeChangeTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/hashcode/SetHashcodeChangeTest.java index 3568f43f472f..c2492e513e14 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/hashcode/SetHashcodeChangeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/hashcode/SetHashcodeChangeTest.java @@ -12,7 +12,6 @@ import org.hibernate.envers.AuditReader; import org.hibernate.envers.Audited; -import org.hibernate.envers.NotAudited; import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase; import org.hibernate.orm.test.envers.Priority; @@ -256,7 +255,6 @@ public static class Book { @ManyToOne(fetch = FetchType.LAZY) @JoinTable(name = "author_book", joinColumns = @JoinColumn(name = "book_id"), inverseJoinColumns = @JoinColumn(name="author_id",nullable = false)) - @NotAudited private Author author; public Integer getId() {