diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/GraalVMFeatures.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/GraalVMFeatures.java index d0d2e124b8f80..b4f4ae6caa1bf 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/GraalVMFeatures.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/GraalVMFeatures.java @@ -28,4 +28,13 @@ ReflectiveClassBuildItem registerGeneratorClassesForReflections() { .build(); } + // Workaround for https://hibernate.atlassian.net/browse/HHH-16809 + // See https://github.com/hibernate/hibernate-orm/pull/6815#issuecomment-1662197545 + @BuildStep + ReflectiveClassBuildItem registerJdbcArrayTypesForReflection() { + return ReflectiveClassBuildItem + .builder(HibernateOrmTypes.JDBC_JAVA_TYPES.stream().map(d -> d.toString() + "[]").toArray(String[]::new)) + .build(); + } + } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index ed82729ff903b..a2117baab9210 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -461,10 +461,10 @@ public void preGenAnnotationProxies(List per // but there are plans to make deep changes to XML mapping in ORM (to rely on Jandex directly), // so let's not waste our time on optimizations that won't be relevant in a few months. List annotationClassNames = new ArrayList<>(); - for (DotName name : HibernateOrmAnnotations.JPA_MAPPING_ANNOTATIONS) { + for (DotName name : HibernateOrmTypes.JPA_MAPPING_ANNOTATIONS) { annotationClassNames.add(name.toString()); } - for (DotName name : HibernateOrmAnnotations.HIBERNATE_MAPPING_ANNOTATIONS) { + for (DotName name : HibernateOrmTypes.HIBERNATE_MAPPING_ANNOTATIONS) { annotationClassNames.add(name.toString()); } reflective.produce(ReflectiveClassBuildItem.builder(annotationClassNames.toArray(new String[0])) @@ -766,7 +766,7 @@ public void registerInjectServiceMethodsForReflection(CombinedIndexBuildItem ind Set classes = new HashSet<>(); // Built-in service classes; can't rely on Jandex as Hibernate ORM is not indexed by default. - HibernateOrmAnnotations.ANNOTATED_WITH_INJECT_SERVICE.stream() + HibernateOrmTypes.ANNOTATED_WITH_INJECT_SERVICE.stream() .map(DotName::toString) .forEach(classes::add); diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmAnnotations.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmTypes.java similarity index 91% rename from extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmAnnotations.java rename to extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmTypes.java index 384b8239753c3..3b8a7f8e9e0ab 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmAnnotations.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmTypes.java @@ -4,9 +4,9 @@ import org.jboss.jandex.DotName; -public final class HibernateOrmAnnotations { +public final class HibernateOrmTypes { - private HibernateOrmAnnotations() { + private HibernateOrmTypes() { } public static final List PACKAGE_ANNOTATIONS = List.of( @@ -316,4 +316,41 @@ private HibernateOrmAnnotations() { DotName.createSimple("jakarta.persistence.PreRemove"), DotName.createSimple("jakarta.persistence.PreUpdate")); + public static final List JDBC_JAVA_TYPES = List.of( + DotName.createSimple("java.lang.Boolean"), + DotName.createSimple("java.lang.Byte"), + DotName.createSimple("java.lang.Character"), + DotName.createSimple("java.lang.Class"), + DotName.createSimple("java.lang.Double"), + DotName.createSimple("java.lang.Float"), + DotName.createSimple("java.lang.Integer"), + DotName.createSimple("java.lang.Long"), + DotName.createSimple("java.lang.Object"), + DotName.createSimple("java.lang.Short"), + DotName.createSimple("java.lang.String"), + DotName.createSimple("java.math.BigDecimal"), + DotName.createSimple("java.math.BigInteger"), + DotName.createSimple("java.net.InetAddress"), + DotName.createSimple("java.net.URL"), + DotName.createSimple("java.sql.Blob"), + DotName.createSimple("java.sql.Clob"), + DotName.createSimple("java.sql.NClob"), + DotName.createSimple("java.time.Duration"), + DotName.createSimple("java.time.Instant"), + DotName.createSimple("java.time.LocalDate"), + DotName.createSimple("java.time.LocalDateTime"), + DotName.createSimple("java.time.LocalTime"), + DotName.createSimple("java.time.OffsetDateTime"), + DotName.createSimple("java.time.OffsetTime"), + DotName.createSimple("java.time.Year"), + DotName.createSimple("java.time.ZoneId"), + DotName.createSimple("java.time.ZoneOffset"), + DotName.createSimple("java.time.ZonedDateTime"), + DotName.createSimple("java.util.Calendar"), + DotName.createSimple("java.util.Currency"), + DotName.createSimple("java.util.Date"), + DotName.createSimple("java.util.Locale"), + DotName.createSimple("java.util.Map$Entry"), + DotName.createSimple("java.util.TimeZone"), + DotName.createSimple("java.util.UUID")); } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java index 729da9756567e..84edc7d837e66 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java @@ -85,7 +85,7 @@ public final class JpaJandexScavenger { public JpaModelBuildItem discoverModelAndRegisterForReflection() { Collector collector = new Collector(); - for (DotName packageAnnotation : HibernateOrmAnnotations.PACKAGE_ANNOTATIONS) { + for (DotName packageAnnotation : HibernateOrmTypes.PACKAGE_ANNOTATIONS) { enlistJPAModelAnnotatedPackages(collector, packageAnnotation); } enlistJPAModelClasses(collector, ClassNames.JPA_ENTITY); @@ -95,7 +95,7 @@ public JpaModelBuildItem discoverModelAndRegisterForReflection() { enlistEmbeddedsAndElementCollections(collector); enlistPotentialCdiBeanClasses(collector, ClassNames.CONVERTER); - for (DotName annotation : HibernateOrmAnnotations.JPA_LISTENER_ANNOTATIONS) { + for (DotName annotation : HibernateOrmTypes.JPA_LISTENER_ANNOTATIONS) { enlistPotentialCdiBeanClasses(collector, annotation); } diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmAnnotationsTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmTypesTest.java similarity index 78% rename from extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmAnnotationsTest.java rename to extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmTypesTest.java index 397b417c66da6..992cb3d7b33a2 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmAnnotationsTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmTypesTest.java @@ -8,6 +8,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Modifier; import java.net.URL; import java.util.Arrays; import java.util.HashSet; @@ -24,17 +25,22 @@ import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.Type; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import io.quarkus.deployment.index.IndexWrapper; import io.quarkus.deployment.index.IndexingUtil; +import io.quarkus.deployment.index.PersistentClassIndex; +import io.quarkus.deployment.util.JandexUtil; import io.quarkus.hibernate.orm.deployment.ClassNames; -import io.quarkus.hibernate.orm.deployment.HibernateOrmAnnotations; +import io.quarkus.hibernate.orm.deployment.HibernateOrmTypes; /** - * Test that hardcoded lists of Hibernate ORM annotations stay up-to-date. + * Test that hardcoded lists of Hibernate ORM types stay up-to-date. */ -public class HibernateOrmAnnotationsTest { +public class HibernateOrmTypesTest { private static final DotName RETENTION = DotName.createSimple(Retention.class.getName()); private static final DotName TARGET = DotName.createSimple(Target.class.getName()); @@ -53,7 +59,7 @@ public void testNoMissingJpaAnnotation() { Set jpaMappingAnnotations = findRuntimeAnnotations(jpaIndex); jpaMappingAnnotations.removeIf(name -> name.toString().startsWith("jakarta.persistence.metamodel.")); - assertThat(HibernateOrmAnnotations.JPA_MAPPING_ANNOTATIONS) + assertThat(HibernateOrmTypes.JPA_MAPPING_ANNOTATIONS) .containsExactlyInAnyOrderElementsOf(jpaMappingAnnotations); } @@ -65,7 +71,7 @@ public void testNoMissingJpaListenerAnnotation() { .filter(name -> listenerAnnotationNamePattern.matcher(name.toString()).matches()) .collect(Collectors.toSet()); - assertThat(HibernateOrmAnnotations.JPA_LISTENER_ANNOTATIONS) + assertThat(HibernateOrmTypes.JPA_LISTENER_ANNOTATIONS) .containsExactlyInAnyOrderElementsOf(jpaMappingAnnotations); } @@ -76,7 +82,7 @@ public void testNoMissingHibernateAnnotation() { hibernateMappingAnnotations.removeIf(name -> name.toString().contains(".spi.")); ignoreInternalAnnotations(hibernateMappingAnnotations); - assertThat(HibernateOrmAnnotations.HIBERNATE_MAPPING_ANNOTATIONS) + assertThat(HibernateOrmTypes.HIBERNATE_MAPPING_ANNOTATIONS) .containsExactlyInAnyOrderElementsOf(hibernateMappingAnnotations); } @@ -93,7 +99,7 @@ public void testNoMissingPackageLevelAnnotation() { packageLevelHibernateAnnotations.removeIf(name -> name.toString().contains(".internal.")); ignoreInternalAnnotations(packageLevelHibernateAnnotations); - assertThat(HibernateOrmAnnotations.PACKAGE_ANNOTATIONS) + assertThat(HibernateOrmTypes.PACKAGE_ANNOTATIONS) .containsExactlyInAnyOrderElementsOf(packageLevelHibernateAnnotations); } @@ -102,10 +108,33 @@ public void testNoMissingInjectServiceAnnotatedClass() { Set injectServiceAnnotatedClasses = findClassesWithMethodsAnnotatedWith(hibernateIndex, ClassNames.INJECT_SERVICE); - assertThat(HibernateOrmAnnotations.ANNOTATED_WITH_INJECT_SERVICE) + assertThat(HibernateOrmTypes.ANNOTATED_WITH_INJECT_SERVICE) .containsExactlyInAnyOrderElementsOf(injectServiceAnnotatedClasses); } + @Test + public void testNoMissingJdbcJavaTypeClass() { + Set jdbcJavaTypeNames = new TreeSet<>(); + DotName basicJavaTypeName = DotName.createSimple("org.hibernate.type.descriptor.java.BasicJavaType"); + IndexView hibernateAndJdkIndex = new IndexWrapper(hibernateIndex, Thread.currentThread().getContextClassLoader(), + new PersistentClassIndex()); + + for (ClassInfo basicJavaTypeImplInfo : hibernateIndex.getAllKnownImplementors(basicJavaTypeName)) { + if (Modifier.isAbstract(basicJavaTypeImplInfo.flags())) { + continue; + } + List typeParams = JandexUtil.resolveTypeParameters(basicJavaTypeImplInfo.name(), basicJavaTypeName, + hibernateAndJdkIndex); + Type jdbcJavaType = typeParams.get(0); + if (jdbcJavaType.kind() == Type.Kind.CLASS) { + jdbcJavaTypeNames.add(jdbcJavaType.name()); + } + } + + assertThat(HibernateOrmTypes.JDBC_JAVA_TYPES) + .containsExactlyInAnyOrderElementsOf(jdbcJavaTypeNames); + } + private Set findRuntimeAnnotations(Index index) { Set annotations = new HashSet<>(); for (AnnotationInstance retentionAnnotation : index.getAnnotations(RETENTION)) { diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java index 0bc9b7d02b6e7..ac23341e763f4 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java @@ -57,6 +57,8 @@ private static void doStuffWithHibernate(EntityManagerFactory entityManagerFacto deleteAllPerson(entityManagerFactory); + // Try an entity using a UUID + verifyUUIDEntity(entityManagerFactory); } private static void verifyJPANamedQuery(final EntityManagerFactory emf) { @@ -144,6 +146,27 @@ private static String randomName() { return UUID.randomUUID().toString(); } + private static void verifyUUIDEntity(final EntityManagerFactory emf) { + EntityManager em = emf.createEntityManager(); + EntityTransaction transaction = em.getTransaction(); + transaction.begin(); + MyUUIDEntity myEntity = new MyUUIDEntity(); + myEntity.setName("George"); + em.persist(myEntity); + transaction.commit(); + em.close(); + + em = emf.createEntityManager(); + transaction = em.getTransaction(); + transaction.begin(); + myEntity = em.find(MyUUIDEntity.class, myEntity.getId()); + if (myEntity == null || !"George".equals(myEntity.getName())) { + throw new RuntimeException("Incorrect loaded MyUUIDEntity " + myEntity); + } + transaction.commit(); + em.close(); + } + private void reportException(String errorMessage, final Exception e, final HttpServletResponse resp) throws IOException { final PrintWriter writer = resp.getWriter(); if (errorMessage != null) { diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/MyUUIDEntity.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/MyUUIDEntity.java new file mode 100644 index 0000000000000..23997f53e5f09 --- /dev/null +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/MyUUIDEntity.java @@ -0,0 +1,41 @@ +package io.quarkus.it.jpa.postgresql; + +import java.util.UUID; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity(name = "uuidentity") +public class MyUUIDEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + private String name; + + public MyUUIDEntity() { + } + + public MyUUIDEntity(UUID id, String name) { + this.id = id; + this.name = name; + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +}