diff --git a/.github/hibernate-github-bot.yml b/.github/hibernate-github-bot.yml new file mode 100644 index 0000000000..9d9ad649de --- /dev/null +++ b/.github/hibernate-github-bot.yml @@ -0,0 +1,3 @@ +--- +jira: + projectKey: "HHH" diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java index 0045a0b6ea..bae154c829 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java @@ -252,7 +252,7 @@ public class AnnotationMetadataSourceProcessorImpl implements MetadataSourceProc } private List orderAndFillHierarchy(List original) { - List copy = new ArrayList<>( original ); + List copy = new ArrayList<>( original.size() ); insertMappedSuperclasses( original, copy ); // order the hierarchy @@ -266,16 +266,28 @@ public class AnnotationMetadataSourceProcessorImpl implements MetadataSourceProc } private void insertMappedSuperclasses(List original, List copy) { + final boolean debug = log.isDebugEnabled(); for ( XClass clazz : original ) { - XClass superClass = clazz.getSuperclass(); - while ( superClass != null - && !reflectionManager.equals( superClass, Object.class ) - && !copy.contains( superClass ) ) { - if ( superClass.isAnnotationPresent( Entity.class ) - || superClass.isAnnotationPresent( javax.persistence.MappedSuperclass.class ) ) { - copy.add( superClass ); + if ( clazz.isAnnotationPresent( javax.persistence.MappedSuperclass.class ) ) { + if ( debug ) { + log.debugf( + "Skipping explicit MappedSuperclass %s, the class will be discovered analyzing the implementing class", + clazz + ); + } + } + else { + copy.add( clazz ); + XClass superClass = clazz.getSuperclass(); + while ( superClass != null + && !reflectionManager.equals( superClass, Object.class ) + && !copy.contains( superClass ) ) { + if ( superClass.isAnnotationPresent( Entity.class ) + || superClass.isAnnotationPresent( javax.persistence.MappedSuperclass.class ) ) { + copy.add( superClass ); + } + superClass = superClass.getSuperclass(); } - superClass = superClass.getSuperclass(); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 5b5c20317f..6d82702245 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -1584,8 +1584,7 @@ public final class AnnotationBinder { MetadataBuildingContext context) { int idPropertyCounter = 0; - Collection properties = propertyContainer.getProperties(); - for ( XProperty p : properties ) { + for ( XProperty p : propertyContainer.propertyIterator() ) { final int currentIdPropertyCounter = addProperty( propertyContainer, p, diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/PropertyContainer.java b/hibernate-core/src/main/java/org/hibernate/cfg/PropertyContainer.java index 537293d6ae..73e4d03dad 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/PropertyContainer.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/PropertyContainer.java @@ -9,6 +9,7 @@ package org.hibernate.cfg; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -35,6 +36,7 @@ import org.hibernate.boot.jaxb.SourceType; import org.hibernate.cfg.annotations.HCANNHelper; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.jboss.logging.Logger; @@ -63,7 +65,7 @@ class PropertyContainer { */ private final AccessType classLevelAccessType; - private final TreeMap persistentAttributeMap; + private final List persistentAttributes; PropertyContainer(XClass clazz, XClass entityAtStake, AccessType defaultClassLevelAccessType) { this.xClass = clazz; @@ -83,7 +85,6 @@ class PropertyContainer { : defaultClassLevelAccessType; assert classLevelAccessType == AccessType.FIELD || classLevelAccessType == AccessType.PROPERTY; - this.persistentAttributeMap = new TreeMap<>(); final List fields = xClass.getDeclaredProperties( AccessType.FIELD.getType() ); final List getters = xClass.getDeclaredProperties( AccessType.PROPERTY.getType() ); @@ -92,18 +93,23 @@ class PropertyContainer { final Map persistentAttributesFromGetters = new HashMap<>(); + final TreeMap localAttributeMap = new TreeMap<>(); collectPersistentAttributesUsingLocalAccessType( - persistentAttributeMap, + xClass, + localAttributeMap, persistentAttributesFromGetters, fields, getters ); collectPersistentAttributesUsingClassLevelAccessType( - persistentAttributeMap, + xClass, + classLevelAccessType, + localAttributeMap, persistentAttributesFromGetters, fields, getters ); + this.persistentAttributes = verifyAndInitializePersistentAttributes( xClass, localAttributeMap ); } private void preFilter(List fields, List getters) { @@ -124,7 +130,8 @@ class PropertyContainer { } } - private void collectPersistentAttributesUsingLocalAccessType( + private static void collectPersistentAttributesUsingLocalAccessType( + XClass xClass, TreeMap persistentAttributeMap, Map persistentAttributesFromGetters, List fields, @@ -176,7 +183,9 @@ class PropertyContainer { } } - private void collectPersistentAttributesUsingClassLevelAccessType( + private static void collectPersistentAttributesUsingClassLevelAccessType( + XClass xClass, + AccessType classLevelAccessType, TreeMap persistentAttributeMap, Map persistentAttributesFromGetters, List fields, @@ -229,20 +238,30 @@ class PropertyContainer { return classLevelAccessType; } + /** + * @deprecated Use the {@link #propertyIterator()} method instead. + */ + @Deprecated public Collection getProperties() { - assertTypesAreResolvable(); - return Collections.unmodifiableCollection( persistentAttributeMap.values() ); + return Collections.unmodifiableCollection( this.persistentAttributes ); } - private void assertTypesAreResolvable() { - for ( XProperty xProperty : persistentAttributeMap.values() ) { + public Iterable propertyIterator() { + return persistentAttributes; + } + + private static List verifyAndInitializePersistentAttributes(XClass xClass, Map localAttributeMap) { + ArrayList output = new ArrayList( localAttributeMap.size() ); + for ( XProperty xProperty : localAttributeMap.values() ) { if ( !xProperty.isTypeResolved() && !discoverTypeWithoutReflection( xProperty ) ) { String msg = "Property " + StringHelper.qualify( xClass.getName(), xProperty.getName() ) + " has an unbound type and no explicit target entity. Resolve this Generic usage issue" + " or set an explicit target attribute (eg @OneToMany(target=) or use an explicit @Type"; throw new AnnotationException( msg ); } + output.add( xProperty ); } + return CollectionHelper.toSmallList( output ); } // // private void considerExplicitFieldAndPropertyAccess() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/mapping/MappedSuperclassWithGenericsTest.java b/hibernate-core/src/test/java/org/hibernate/test/mapping/MappedSuperclassWithGenericsTest.java new file mode 100644 index 0000000000..fe49221bce --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/mapping/MappedSuperclassWithGenericsTest.java @@ -0,0 +1,90 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.mapping; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.MappedSuperclass; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-14499") +public class MappedSuperclassWithGenericsTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + IntermediateAbstractMapped.class, + BaseEntity.class, + AbstractGenericMappedSuperType.class, + }; + } + + @Test + public void testIt() { + + } + + @MappedSuperclass + public static abstract class AbstractGenericMappedSuperType { + + private T whateverType; + + } + + @MappedSuperclass + @IdClass(PK.class) + public static abstract class IntermediateAbstractMapped extends AbstractGenericMappedSuperType { + + @Id + private String keyOne; + @Id + private String keyTwo; + @Id + private String keyThree; + } + + @SuppressWarnings("UnusedDeclaration") + public static class PK implements Serializable { + + private String keyOne; + private String keyTwo; + private String keyThree; + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + PK pk = (PK) o; + return Objects.equals( keyOne, pk.keyOne ) && + Objects.equals( keyTwo, pk.keyTwo ) && + Objects.equals( keyThree, pk.keyThree ); + } + + @Override + public int hashCode() { + return Objects.hash( keyOne, keyTwo, keyThree ); + } + } + + @Entity(name = "BaseEntity") + public static class BaseEntity extends IntermediateAbstractMapped { + + String aString; + + } + +}