#330 - Demonstrate usage of JPA 2.1 @SqlResultSetMapping.
Sometimes, e.g. for analytics, it is handy to be able to return a different entity result type from a Repository query method than the base Repository entity type or an interface based projection. This demonstrates how to use a custom @SqlResultSetMapping in combination with @ColumnResult introduced in JPA 2.1.
This commit is contained in:
committed by
Oliver Gierke
parent
9b2b10cc3d
commit
9ab1ae1fe6
@@ -51,4 +51,57 @@ public interface UserRepository extends CrudRepository<User, Long> {
|
||||
|
||||
Calling `UserRepository.plus1BackedByOtherNamedStoredProcedure(…)` will execute the stored procedure `plus1inout` using the meta-data declared on the `User` domain class.
|
||||
|
||||
`UserRepository.plus1inout(…)` will derive stored procedure metadata from the repository and default to positional parameter binding and expect a single output parameter of the backing stored procedure.
|
||||
`UserRepository.plus1inout(…)` will derive stored procedure metadata from the repository and default to positional parameter binding and expect a single output parameter of the backing stored procedure.
|
||||
|
||||
## Support for custom SqlResultSetMapping with ConstructorResult
|
||||
|
||||
Sometimes, e.g. for analytics, it is handy to be able to return a different entity result type from a Repository query method than the base Repository entity type or an interface based projection.
|
||||
|
||||
In those cases one can leverage JPAs `SqlResultSetMapping` feature to map the columns of the result of a query to different fields.
|
||||
|
||||
JPA 2.1 introduced the new `SqlResultSetMapping` type `ConstructorResult` which allows to map columns of a result set row to a constructor invocation
|
||||
which can be nicely used in combination with Value Objects.
|
||||
|
||||
This example shows how to define a custom `SqlResultSetMapping` for the result of an analytical native query that reports the usage summary for a set of Subscriptions.
|
||||
|
||||
`SqlResultSetMapping` definition on the Subscription entity class:
|
||||
```java
|
||||
@Entity
|
||||
@NoArgsConstructor
|
||||
@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"
|
||||
)
|
||||
@Data
|
||||
public class Subscription {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
`SubscriptionSummary` is modelled as a value object:
|
||||
```java
|
||||
@Value
|
||||
public class SubscriptionSummary {
|
||||
|
||||
private final String product;
|
||||
|
||||
private final Long usageCount;
|
||||
}
|
||||
```
|
||||
|
||||
The `SubscriptionRepository` declares the custom query method `findAllSubscriptionSummaries` which is backed by the named native query declared on the `Subscription` entity.
|
||||
```java
|
||||
interface SubscriptionRepository extends CrudRepository<Subscription,Long> {
|
||||
|
||||
List<SubscriptionSummary> findAllSubscriptionSummaries();
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
@SpringBootApplication
|
||||
class CustomResultSetMappingsConfiguration {
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.ColumnResult;
|
||||
import javax.persistence.ConstructorResult;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.NamedNativeQuery;
|
||||
import javax.persistence.SqlResultSetMapping;
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
@Entity
|
||||
@NoArgsConstructor
|
||||
@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")
|
||||
@Data
|
||||
public class Subscription {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
Long id;
|
||||
|
||||
String productName;
|
||||
|
||||
long userId;
|
||||
|
||||
public Subscription(String productName, long userId) {
|
||||
this.productName = productName;
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public interface SubscriptionRepository extends CrudRepository<Subscription, Long> {
|
||||
|
||||
/**
|
||||
* Returns an aggregated {@link SubscriptionSummary} by Product.
|
||||
* <p>
|
||||
* 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<SubscriptionSummary> findAllSubscriptionSummaries();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
@Value
|
||||
public class SubscriptionSummary {
|
||||
|
||||
private final String product;
|
||||
|
||||
private final Long usageCount;
|
||||
}
|
||||
@@ -5,3 +5,10 @@ BEGIN ATOMIC
|
||||
set res = arg + 1;
|
||||
END
|
||||
/;
|
||||
|
||||
DROP table subscription IF EXISTS
|
||||
/;
|
||||
CREATE TABLE subscription(id IDENTITY, product_name VARCHAR(255), user_id INT)
|
||||
/;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@DataJpaTest
|
||||
@Transactional
|
||||
public class SubscriptionRepositoryIntegrationTests {
|
||||
|
||||
private static final String SERVICE_1 = "Service 1";
|
||||
private static final String SERVICE_2 = "Service 2";
|
||||
|
||||
@Autowired
|
||||
SubscriptionRepository repository;
|
||||
|
||||
@Test
|
||||
public void shouldReturnCorrectSubscriptionSummary() {
|
||||
|
||||
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<SubscriptionSummary> subscriptionSummaries = repository.findAllSubscriptionSummaries();
|
||||
|
||||
assertThat(subscriptionSummaries) //
|
||||
.flatExtracting(s -> asList(s.getProduct(), s.getUsageCount()))
|
||||
.contains(SERVICE_1, 3L, SERVICE_2, 2L);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user