diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractJavaType.java index be82cbf4341c..cf7d1abbf998 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractJavaType.java @@ -48,7 +48,7 @@ protected AbstractJavaType(Type type) { protected AbstractJavaType(Type type, MutabilityPlan mutabilityPlan) { this.type = type; this.mutabilityPlan = mutabilityPlan; - this.comparator = Comparable.class.isAssignableFrom( getJavaTypeClass() ) + this.comparator = type != null && Comparable.class.isAssignableFrom( getJavaTypeClass() ) ? (Comparator) ComparableComparator.INSTANCE : null; } diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/CompositeIdClassTest.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/CompositeIdClassTest.java new file mode 100644 index 000000000000..b2f8d27f9ccd --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/CompositeIdClassTest.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.data.idclass; + +import org.hibernate.processor.test.data.idclass.MyEntity.MyEntityId; +import org.hibernate.processor.test.util.CompilationTest; +import org.hibernate.processor.test.util.WithClasses; +import org.junit.Test; + +import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor; +import static org.hibernate.processor.test.util.TestUtil.assertPresenceOfMethodInMetamodelFor; +import static org.hibernate.processor.test.util.TestUtil.getMetaModelSourceAsString; + +public class CompositeIdClassTest extends CompilationTest { + @Test + @WithClasses({ + MyRepository.class, + MyEntity.class, + }) + public void test() { + System.out.println( getMetaModelSourceAsString( MyEntity.class ) ); + System.out.println( getMetaModelSourceAsString( MyEntity.class, true ) ); + System.out.println( getMetaModelSourceAsString( MyRepository.class ) ); + assertMetamodelClassGeneratedFor( MyEntity.class ); + assertMetamodelClassGeneratedFor( MyEntity.class, true ); + assertMetamodelClassGeneratedFor( MyRepository.class ); + assertPresenceOfMethodInMetamodelFor( + MyRepository.class, + "findById", + MyEntityId.class + ); + } +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/MyEntity.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/MyEntity.java new file mode 100644 index 000000000000..3ab45712b44b --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/MyEntity.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.data.idclass; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; + +@Entity +@IdClass(MyEntity.MyEntityId.class) +public class MyEntity { + + @Id + Integer topicId; + + @Id + String userId; + + String status; + + public record MyEntityId(Integer topicId, String userId) { + + } +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/MyRepository.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/MyRepository.java new file mode 100644 index 000000000000..b9e662be896b --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/MyRepository.java @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.data.idclass; + +import jakarta.data.repository.Query; +import jakarta.data.repository.Repository; +import org.hibernate.processor.test.data.idclass.MyEntity.MyEntityId; + +@Repository +public interface MyRepository { + + @Query("from MyEntity where id=:id") + MyEntity findById(MyEntityId id); +} diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockEntityPersister.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockEntityPersister.java index db5319aa12f7..ce833b256185 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockEntityPersister.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockEntityPersister.java @@ -102,6 +102,10 @@ public final Type getPropertyType(String propertyPath) { result = getSubclassPropertyType(propertyPath); } + if ("id".equals( propertyPath )) { + result = identifierType(); + } + if (result!=null) { propertyTypesByName.put(propertyPath, result); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java index fa733a79d68b..5ff6fe307c39 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java @@ -166,7 +166,7 @@ private static Element dereference(AccessType defaultAccessType, Element symbol, private Type propertyType(Element member, String entityName, String path, AccessType defaultAccessType) { final TypeMirror memberType = memberType(member); if (isEmbeddedProperty(member)) { - return component.make(asElement(memberType), entityName, path, defaultAccessType, this); + return componentType( entityName, path, defaultAccessType, memberType ); } else if (isToOneAssociation(member)) { return new ManyToOneType(getTypeConfiguration(), getToOneTargetEntity(member)); @@ -186,6 +186,10 @@ else if (isEnumProperty(member)) { } } + private Component componentType(String entityName, String path, AccessType defaultAccessType, TypeMirror memberType) { + return component.make( asElement( memberType ), entityName, path, defaultAccessType, this ); + } + @SuppressWarnings({"rawtypes", "unchecked"}) private static BasicType enumType(Element member, TypeMirror memberType) { final Class enumClass = Enum.class; // because we can't load the real enum class! @@ -414,6 +418,12 @@ public String identifierPropertyName() { @Override public Type identifierType() { + if (hasAnnotation( type, "IdClass" )) { + final TypeMirror annotationMember = (TypeMirror)getAnnotationMember( getAnnotation( type, "IdClass" ), "value" ); + if (annotationMember != null) { + return factory.componentType( getEntityName(), EntityIdentifierMapping.ID_ROLE_NAME, defaultAccessType, annotationMember ); + } + } for (Element element : type.getEnclosedElements()) { if ( hasAnnotation(element, "Id")|| hasAnnotation(element, "EmbeddedId") ) { return factory.propertyType(element, getEntityName(), EntityIdentifierMapping.ID_ROLE_NAME, defaultAccessType); @@ -490,7 +500,8 @@ String qualifyName(String jpaEntityName) { boolean isAttributeDefined(String entityName, String fieldName) { final TypeElement entityClass = findEntityClass(entityName); return entityClass != null - && findPropertyByPath(entityClass, fieldName, getDefaultAccessType(entityClass)) != null; + && (findPropertyByPath(entityClass, fieldName, getDefaultAccessType(entityClass)) != null + || "id".equals( fieldName ) && hasAnnotation( entityClass, "IdClass" )); } public TypeElement findEntityClass(String entityName) { diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/idclass/CompositeIdClassTest.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/idclass/CompositeIdClassTest.java new file mode 100644 index 000000000000..c7878a4eb29f --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/idclass/CompositeIdClassTest.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.idclass; + +import jakarta.persistence.EntityManager; +import org.hibernate.processor.test.idclass.MyEntity.MyEntityId; +import org.hibernate.processor.test.util.CompilationTest; +import org.hibernate.processor.test.util.WithClasses; +import org.junit.Test; + +import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor; +import static org.hibernate.processor.test.util.TestUtil.assertPresenceOfMethodInMetamodelFor; +import static org.hibernate.processor.test.util.TestUtil.assertPresenceOfNameFieldInMetamodelFor; +import static org.hibernate.processor.test.util.TestUtil.getMetaModelSourceAsString; + +public class CompositeIdClassTest extends CompilationTest { + @Test + @WithClasses(MyEntity.class) + public void test() { + System.out.println( getMetaModelSourceAsString( MyEntity.class ) ); + assertMetamodelClassGeneratedFor( MyEntity.class ); + assertPresenceOfNameFieldInMetamodelFor( + MyEntity.class, + "QUERY_FIND_BY_ID", + "Missing named query attribute." + ); + assertPresenceOfMethodInMetamodelFor( + MyEntity.class, + "findById", + EntityManager.class, + MyEntityId.class + ); + } +} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/idclass/MyEntity.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/idclass/MyEntity.java new file mode 100644 index 000000000000..7f0f8e9cd284 --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/idclass/MyEntity.java @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.idclass; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.NamedQuery; +import org.hibernate.annotations.processing.CheckHQL; + +@Entity +@IdClass(MyEntity.MyEntityId.class) +@CheckHQL +@NamedQuery(name = "#findById", query = "from MyEntity e where e.id=:id") +public class MyEntity { + + @Id + Integer topicId; + + @Id + String userId; + + String status; + + public record MyEntityId(Integer topicId, String userId) { + + } +}