From 1afd6f2fb6c3de4e3e2f1a9af13e262fee944dd0 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 9 May 2018 16:20:58 +0200 Subject: [PATCH] #330 - Added projection sample for how to avoid dedicated SQL result set mappings. Upgraded to Ingalls snapshots as the sampler needs some tweaks that have not made it into a released version yet. --- jpa/jpa21/pom.xml | 4 ++ .../CustomResultSetMappingsConfiguration.java | 3 +- .../jpa/resultsetmappings/Subscription.java | 53 ++++++++++--------- .../SubscriptionProjection.java | 26 +++++++++ .../SubscriptionRepository.java | 35 ++++++++---- .../SubscriptionSummary.java | 5 +- ...ubscriptionRepositoryIntegrationTests.java | 49 ++++++++++------- 7 files changed, 114 insertions(+), 61 deletions(-) create mode 100644 jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionProjection.java diff --git a/jpa/jpa21/pom.xml b/jpa/jpa21/pom.xml index 3a93e7ec..050506d3 100644 --- a/jpa/jpa21/pom.xml +++ b/jpa/jpa21/pom.xml @@ -11,6 +11,10 @@ spring-data-jpa-jpa21 Spring Data JPA - JPA 2.1 specific features + + Ingalls-BUILD-SNAPSHOT + + org.hibernate diff --git a/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/CustomResultSetMappingsConfiguration.java b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/CustomResultSetMappingsConfiguration.java index dbc7363a..03cbf4b5 100644 --- a/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/CustomResultSetMappingsConfiguration.java +++ b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/CustomResultSetMappingsConfiguration.java @@ -21,5 +21,4 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; * @author Thomas Darimont */ @SpringBootApplication -class CustomResultSetMappingsConfiguration { -} +class CustomResultSetMappingsConfiguration {} diff --git a/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/Subscription.java b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/Subscription.java index d635b5cb..39e05574 100644 --- a/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/Subscription.java +++ b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/Subscription.java @@ -15,6 +15,8 @@ */ package example.springdata.jpa.resultsetmappings; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -23,39 +25,40 @@ import javax.persistence.ConstructorResult; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.NamedNativeQueries; import javax.persistence.NamedNativeQuery; import javax.persistence.SqlResultSetMapping; /** * @author Thomas Darimont + * @author Oliver Gierke */ -@Entity -@NoArgsConstructor +@NamedNativeQueries({ + + // A query using a dedicated SQL result set mapping (see below) + @NamedNativeQuery(name = "Subscription.findAllSubscriptionSummaries", // + query = "select product_name as productName, count(user_id) as subscriptions from subscription group by product_name order by productName", // + resultSetMapping = "subscriptionSummary"), + + // A query using simple projections + @NamedNativeQuery(name = "Subscription.findAllSubscriptionProjections", // + query = "select product_name as product, count(user_id) as usageCount from subscription group by product_name order by product") }) + @SqlResultSetMapping( // - name="subscriptionSummary", // - classes = @ConstructorResult( - targetClass = SubscriptionSummary.class, // - columns={ - @ColumnResult(name="productName", type=String.class), // - @ColumnResult(name="subscriptions", type=long.class) - })) -@NamedNativeQuery( - name="Subscription.findAllSubscriptionSummaries", // - query="select product_name as productName, count(user_id) as subscriptions from subscription group by product_name order by productName", // - resultSetMapping = "subscriptionSummary") + name = "subscriptionSummary", // + classes = @ConstructorResult(targetClass = SubscriptionSummary.class, // + columns = { // + @ColumnResult(name = "productName", type = String.class), // + @ColumnResult(name = "subscriptions", type = long.class) // + })) + +@Entity @Data +@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) +@AllArgsConstructor public class Subscription { - @Id - @GeneratedValue - Long id; - - String productName; - - long userId; - - public Subscription(String productName, long userId) { - this.productName = productName; - this.userId = userId; - } + private final @Id @GeneratedValue Long id = null; + private String productName; + private long userId; } diff --git a/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionProjection.java b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionProjection.java new file mode 100644 index 00000000..00977042 --- /dev/null +++ b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionProjection.java @@ -0,0 +1,26 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.jpa.resultsetmappings; + +/** + * @author Oliver Gierke + */ +interface SubscriptionProjection { + + String getProduct(); + + long getUsageCount(); +} diff --git a/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionRepository.java b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionRepository.java index 2cb86ce3..3a96c5a1 100644 --- a/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionRepository.java +++ b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionRepository.java @@ -15,22 +15,35 @@ */ package example.springdata.jpa.resultsetmappings; -import org.springframework.data.repository.CrudRepository; - import java.util.List; +import javax.persistence.SqlResultSetMapping; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; + /** * @author Thomas Darimont */ public interface SubscriptionRepository extends CrudRepository { - /** - * Returns an aggregated {@link SubscriptionSummary} by Product. - *

- * Note that this example uses a JPA 2.1 Constructor based {@link javax.persistence.SqlResultSetMapping} - * in combination with native query defined in {@link Subscription}. - * - * @return - */ - List findAllSubscriptionSummaries(); + /** + * Returns an aggregated {@link SubscriptionSummary} by Product. + *

+ * Note that this example uses a JPA 2.1 Constructor based {@link javax.persistence.SqlResultSetMapping} in + * combination with native query defined in {@link Subscription}. + * + * @return + */ + @Query(nativeQuery = true) + List findAllSubscriptionSummaries(); + + /** + * Returns an aggregated {@link SubscriptionProjection} using a named native query but does not require a dedicated + * {@link SqlResultSetMapping}. + * + * @return + */ + @Query(nativeQuery = true) + List findAllSubscriptionProjections(); } diff --git a/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionSummary.java b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionSummary.java index ef4880a1..d176adcd 100644 --- a/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionSummary.java +++ b/jpa/jpa21/src/main/java/example/springdata/jpa/resultsetmappings/SubscriptionSummary.java @@ -23,7 +23,6 @@ import lombok.Value; @Value public class SubscriptionSummary { - private final String product; - - private final Long usageCount; + String product; + Long usageCount; } diff --git a/jpa/jpa21/src/test/java/example/springdata/jpa/resultsetmappings/SubscriptionRepositoryIntegrationTests.java b/jpa/jpa21/src/test/java/example/springdata/jpa/resultsetmappings/SubscriptionRepositoryIntegrationTests.java index 33d6db48..85467d8b 100644 --- a/jpa/jpa21/src/test/java/example/springdata/jpa/resultsetmappings/SubscriptionRepositoryIntegrationTests.java +++ b/jpa/jpa21/src/test/java/example/springdata/jpa/resultsetmappings/SubscriptionRepositoryIntegrationTests.java @@ -15,9 +15,10 @@ */ package example.springdata.jpa.resultsetmappings; -import static java.util.Arrays.asList; +import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -25,8 +26,6 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - /** * @author Thomas Darimont */ @@ -35,26 +34,36 @@ import java.util.List; @Transactional public class SubscriptionRepositoryIntegrationTests { - private static final String SERVICE_1 = "Service 1"; - private static final String SERVICE_2 = "Service 2"; + private static final String SERVICE_1 = "Service 1"; + private static final String SERVICE_2 = "Service 2"; - @Autowired - SubscriptionRepository repository; + @Autowired SubscriptionRepository repository; - @Test - public void shouldReturnCorrectSubscriptionSummary() { + @Before + public void setUp() { - repository.save(new Subscription(SERVICE_1, 1)); - repository.save(new Subscription(SERVICE_1, 2)); - repository.save(new Subscription(SERVICE_1, 3)); - repository.save(new Subscription(SERVICE_2, 3)); - repository.save(new Subscription(SERVICE_2, 4)); + repository.save(new Subscription(SERVICE_1, 1)); + repository.save(new Subscription(SERVICE_1, 2)); + repository.save(new Subscription(SERVICE_1, 3)); + repository.save(new Subscription(SERVICE_2, 3)); + repository.save(new Subscription(SERVICE_2, 4)); + } - List subscriptionSummaries = repository.findAllSubscriptionSummaries(); + @Test + @SuppressWarnings("unchecked") + public void shouldReturnCorrectSubscriptionSummary() { - assertThat(subscriptionSummaries) // - .flatExtracting(s -> asList(s.getProduct(), s.getUsageCount())) - .contains(SERVICE_1, 3L, SERVICE_2, 2L); - } + assertThat(repository.findAllSubscriptionSummaries()) // + .flatExtracting(s -> asList(s.getProduct(), s.getUsageCount())) // + .contains(SERVICE_1, 3L, SERVICE_2, 2L); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldReturnCorrectSubscriptionProjection() { + + assertThat(repository.findAllSubscriptionProjections()) // + .flatExtracting(s -> asList(s.getProduct(), s.getUsageCount())) // + .contains(SERVICE_1, 3L, SERVICE_2, 2L); + } } -