diff --git a/docs/src/main/asciidoc/hibernate-orm-panache.adoc b/docs/src/main/asciidoc/hibernate-orm-panache.adoc index 866f1b187cda64..6dca32c473061f 100644 --- a/docs/src/main/asciidoc/hibernate-orm-panache.adoc +++ b/docs/src/main/asciidoc/hibernate-orm-panache.adoc @@ -939,6 +939,36 @@ PanacheQuery query = Dog.findAll().project(DogDto.class); ---- <1> The `ownerName` DTO constructor's parameter will be loaded from the `owner.name` HQL property. +In case you want to project nested class like Person in Dog entity you can use `@NestedProjectedClass` annotation on projected class. + +[source,java] +---- +@RegisterForReflection +public class DogDto2 { + public String name; + public PersonDto2 owner; + + public DogDto2(String name, PersonDto2 owner) { + this.name = name; + this.owner = owner; + } + + @NestedProjectedClass // <1> + public static class PersonDto2 { + public String name; + + public PersonDto2(String name) { + this.name = name; + } + } +} + +PanacheQuery query = Dog.findAll().project(DogDto2.class); +---- + +<1> This annotation can be used when you want to project `@Embedded` entity or `@ManyToOne`, `@OneToOne` relation. +It does not support `@OneToMany` or `@ManyToMany` relation. + It is also possible to specify a HQL query with a select clause. In this case, the projection class must have a constructor matching the values returned by the select clause: diff --git a/docs/src/main/asciidoc/hibernate-reactive-panache.adoc b/docs/src/main/asciidoc/hibernate-reactive-panache.adoc index 00f5818c0b9923..7d426040376cdd 100644 --- a/docs/src/main/asciidoc/hibernate-reactive-panache.adoc +++ b/docs/src/main/asciidoc/hibernate-reactive-panache.adoc @@ -690,6 +690,37 @@ PanacheQuery query = Dog.findAll().project(DogDto.class); ---- <1> The `ownerName` DTO constructor's parameter will be loaded from the `owner.name` HQL property. +In case you want to project nested class like Person in Dog entity you can use `@NestedProjectedClass` annotation on projected class. + +[source,java] +---- + +@RegisterForReflection +public class DogDto2 { + public String name; + public PersonDto2 owner; + + public DogDto2(String name, PersonDto2 owner) { + this.name = name; + this.owner = owner; + } + + @NestedProjectedClass // <1> + public static class PersonDto2 { + public String name; + + public PersonDto2(String name) { + this.name = name; + } + } +} + +PanacheQuery query = Dog.findAll().project(DogDto2.class); +---- + +<1> This annotation can be used when you want to project `@Embedded` entity or `@ManyToOne`, `@OneToOne` relation. +It does not support `@OneToMany` or `@ManyToMany` relation. + It is also possible to specify a HQL query with a select clause. In this case, the projection class must have a constructor matching the values returned by the select clause: diff --git a/extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/ProjectedClass.java b/extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/NestedProjectedClass.java similarity index 90% rename from extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/ProjectedClass.java rename to extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/NestedProjectedClass.java index 3eb03b19caed9e..a797b44bc4173d 100644 --- a/extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/ProjectedClass.java +++ b/extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/NestedProjectedClass.java @@ -11,5 +11,5 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -public @interface ProjectedClass { +public @interface NestedProjectedClass { } diff --git a/extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/runtime/CommonPanacheQueryImpl.java b/extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/runtime/CommonPanacheQueryImpl.java index 037803430d2ad2..6643f64e5a6b49 100644 --- a/extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/runtime/CommonPanacheQueryImpl.java +++ b/extensions/panache/hibernate-orm-panache-common/runtime/src/main/java/io/quarkus/hibernate/orm/panache/common/runtime/CommonPanacheQueryImpl.java @@ -17,7 +17,7 @@ import org.hibernate.Filter; import org.hibernate.Session; -import io.quarkus.hibernate.orm.panache.common.ProjectedClass; +import io.quarkus.hibernate.orm.panache.common.NestedProjectedClass; import io.quarkus.hibernate.orm.panache.common.ProjectedFieldName; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Range; @@ -172,7 +172,7 @@ private String getParameterName(Class parentType, String parentParameter, Par // For nested classes, add parent parameter in parameterName parameterName = (parentParameter == null) ? parameterName : parentParameter.concat(".").concat(parameterName); // Test if the parameter is a nested Class that should be projected too. - if (parameter.getType().isAnnotationPresent(ProjectedClass.class)) { + if (parameter.getType().isAnnotationPresent(NestedProjectedClass.class)) { Class nestedType = parameter.getType(); return getParameterFromClass(nestedType, parameterName).toString(); } else { diff --git a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/ProjectedClass.java b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/NestedProjectedClass.java similarity index 90% rename from extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/ProjectedClass.java rename to extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/NestedProjectedClass.java index 7371ea7e1bd491..f1917f2e3bad26 100644 --- a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/ProjectedClass.java +++ b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/NestedProjectedClass.java @@ -11,5 +11,5 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -public @interface ProjectedClass { +public @interface NestedProjectedClass { } diff --git a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/CommonPanacheQueryImpl.java b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/CommonPanacheQueryImpl.java index 5e80a7e0c6e7ad..7ececab540d911 100644 --- a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/CommonPanacheQueryImpl.java +++ b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/CommonPanacheQueryImpl.java @@ -18,7 +18,7 @@ import org.hibernate.Filter; import org.hibernate.reactive.mutiny.Mutiny; -import io.quarkus.hibernate.reactive.panache.common.ProjectedClass; +import io.quarkus.hibernate.reactive.panache.common.NestedProjectedClass; import io.quarkus.hibernate.reactive.panache.common.ProjectedFieldName; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Range; @@ -162,7 +162,7 @@ private String getParameterName(Class parentType, String parentParameter, Par // For nested classes, add parent parameter in parameterName parameterName = (parentParameter == null) ? parameterName : parentParameter.concat(".").concat(parameterName); // Test if the parameter is a nested Class that should be projected too. - if (parameter.getType().isAnnotationPresent(ProjectedClass.class)) { + if (parameter.getType().isAnnotationPresent(NestedProjectedClass.class)) { Class nestedType = parameter.getType(); return getParameterFromClass(nestedType, parameterName).toString(); } else { diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/DogDto2.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/DogDto2.java new file mode 100644 index 00000000000000..22d772fd165d4a --- /dev/null +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/DogDto2.java @@ -0,0 +1,24 @@ +package io.quarkus.it.panache; + +import io.quarkus.hibernate.orm.panache.common.NestedProjectedClass; +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection +public class DogDto2 { + public String name; + public PersonDto2 owner; + + public DogDto2(String name, PersonDto2 owner) { + this.name = name; + this.owner = owner; + } + + @NestedProjectedClass + public static class PersonDto2 { + public String name; + + public PersonDto2(String name) { + this.name = name; + } + } +} diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/PersonDTO.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/PersonDTO.java index 7f114570554cbc..81b9507a20d6b6 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/PersonDTO.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/PersonDTO.java @@ -1,11 +1,10 @@ package io.quarkus.it.panache; -import io.quarkus.hibernate.orm.panache.common.ProjectedClass; +import io.quarkus.hibernate.orm.panache.common.NestedProjectedClass; import io.quarkus.hibernate.orm.panache.common.ProjectedFieldName; import io.quarkus.runtime.annotations.RegisterForReflection; @RegisterForReflection -@ProjectedClass public class PersonDTO extends PersonName { public final AddressDTO address; @@ -26,7 +25,7 @@ public PersonDTO(String uniqueName, String name, AddressDTO address, AddressDTO this.directHeight = directHeight; } - @ProjectedClass + @NestedProjectedClass public static class AddressDTO implements Comparable { // Simple filed with automatic mapping in constructor @@ -53,7 +52,7 @@ public String getStreet3() { } } - @ProjectedClass + @NestedProjectedClass public static class DescriptionDTO { private final String description; @@ -78,7 +77,7 @@ public String getGeneratedDescription() { } } - @ProjectedClass + @NestedProjectedClass public static class EmbeddedDescriptionDTO { @ProjectedFieldName("embeddedDescription") public final String value; diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java index b5b32ac397f53a..9927b86191d46c 100644 --- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java @@ -315,4 +315,22 @@ void testNestedEntityProjection() { Assertions.assertEquals("Height: 170, weight: 75", personDTO.description.getGeneratedDescription()); Assertions.assertEquals("embedded", personDTO.description.description2.value); } + + @Test + @Transactional + void testDogDto2Projection() { + Person hum = new Person(); + hum.name = "hum"; + Dog kit = new Dog("kit", "bulldog"); + hum.dogs.add(kit); + kit.owner = hum; + hum.persist(); + + DogDto2 dogDto2 = Dog.find(" name = ?1", "kit") + .project(DogDto2.class) + .firstResult(); + hum.delete(); + Assertions.assertEquals("kit", dogDto2.name); + Assertions.assertEquals("hum", dogDto2.owner.name); + } } diff --git a/integration-tests/hibernate-reactive-panache/pom.xml b/integration-tests/hibernate-reactive-panache/pom.xml index f006db319ae2a1..1a1d3c612eb6ee 100644 --- a/integration-tests/hibernate-reactive-panache/pom.xml +++ b/integration-tests/hibernate-reactive-panache/pom.xml @@ -195,13 +195,13 @@ maven-surefire-plugin - true + false maven-failsafe-plugin - true + false diff --git a/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/DogDto2.java b/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/DogDto2.java new file mode 100644 index 00000000000000..81f863b2453aa8 --- /dev/null +++ b/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/DogDto2.java @@ -0,0 +1,24 @@ +package io.quarkus.it.panache.reactive; + +import io.quarkus.hibernate.reactive.panache.common.NestedProjectedClass; +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection +public class DogDto2 { + public String name; + public PersonDto2 owner; + + public DogDto2(String name, PersonDto2 owner) { + this.name = name; + this.owner = owner; + } + + @NestedProjectedClass + public static class PersonDto2 { + public String name; + + public PersonDto2(String name) { + this.name = name; + } + } +} diff --git a/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/PersonDTO.java b/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/PersonDTO.java index 1a0a2ccab5f257..ffe38806cfcd39 100644 --- a/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/PersonDTO.java +++ b/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/PersonDTO.java @@ -1,11 +1,10 @@ package io.quarkus.it.panache.reactive; -import io.quarkus.hibernate.reactive.panache.common.ProjectedClass; +import io.quarkus.hibernate.reactive.panache.common.NestedProjectedClass; import io.quarkus.hibernate.reactive.panache.common.ProjectedFieldName; import io.quarkus.runtime.annotations.RegisterForReflection; @RegisterForReflection -@ProjectedClass public class PersonDTO extends PersonName { public final AddressDTO address; @@ -26,7 +25,7 @@ public PersonDTO(String uniqueName, String name, AddressDTO address, AddressDTO this.directHeight = directHeight; } - @ProjectedClass + @NestedProjectedClass public static class AddressDTO implements Comparable { // Simple field with automatic mapping in constructor @@ -53,7 +52,7 @@ public String getStreet3() { } } - @ProjectedClass + @NestedProjectedClass public static class DescriptionDTO { private final String description; @@ -78,7 +77,7 @@ public String getGeneratedDescription() { } } - @ProjectedClass + @NestedProjectedClass public static class EmbeddedDescriptionDTO { @ProjectedFieldName("embeddedDescription") public final String value; diff --git a/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/PanacheFunctionalityTest.java b/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/PanacheFunctionalityTest.java index 33e355054f1d8d..c3bbc1e142edc8 100644 --- a/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/PanacheFunctionalityTest.java +++ b/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/PanacheFunctionalityTest.java @@ -349,4 +349,24 @@ void testNestedEntityProjection(UniAsserter asserter) { }); asserter.execute(() -> person.delete()); } + + @Test + @TestReactiveTransaction + void testDogDto2Projection(UniAsserter asserter) { + Person hum = new Person(); + hum.name = "hum"; + Dog kit = new Dog("kit", "bulldog"); + hum.dogs.add(kit); + kit.owner = hum; + asserter.execute(() -> hum.persist()); + asserter.assertThat( + () -> Dog.find(" name = ?1", "kit") + .project(DogDto2.class) + .firstResult(), + dogDto2 -> { + Assertions.assertEquals("kit", dogDto2.name); + Assertions.assertEquals("hum", dogDto2.owner.name); + }); + asserter.execute(() -> hum.delete()); + } }