Skip to content

Commit 466a7b9

Browse files
mp911demipo256
authored andcommitted
Split projections document fragment into multiple subfragments.
Closes spring-projects#3144
1 parent 959bdfb commit 466a7b9

File tree

4 files changed

+276
-253
lines changed

4 files changed

+276
-253
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
ifndef::projection-collection[]
2+
:projection-collection: Collection
3+
endif::[]
4+
5+
[[projections.dtos]]
6+
= Class-based Projections (DTOs)
7+
8+
Another way of defining projections is by using value type DTOs (Data Transfer Objects) that hold properties for the fields that are supposed to be retrieved.
9+
These DTO types can be used in exactly the same way projection interfaces are used, except that no proxying happens and no nested projections can be applied.
10+
11+
If the store optimizes the query execution by limiting the fields to be loaded, the fields to be loaded are determined from the parameter names of the constructor that is exposed.
12+
13+
The following example shows a projecting DTO:
14+
15+
.A projecting DTO
16+
[source,java]
17+
----
18+
record NamesOnly(String firstname, String lastname) {
19+
}
20+
----
21+
22+
Java Records are ideal to define DTO types since they adhere to value semantics:
23+
All fields are `private final` and ``equals(…)``/``hashCode()``/``toString()`` methods are created automatically.
24+
Alternatively, you can use any class that defines the properties you want to project.
25+
26+
[[projection.dynamic]]
27+
== Dynamic Projections
28+
29+
So far, we have used the projection type as the return type or element type of a collection.
30+
However, you might want to select the type to be used at invocation time (which makes it dynamic).
31+
To apply dynamic projections, use a query method such as the one shown in the following example:
32+
33+
.A repository using a dynamic projection parameter
34+
[source,java,subs="+attributes"]
35+
----
36+
interface PersonRepository extends Repository<Person, UUID> {
37+
38+
<T> {projection-collection}<T> findByLastname(String lastname, Class<T> type);
39+
}
40+
----
41+
42+
This way, the method can be used to obtain the aggregates as is or with a projection applied, as shown in the following example:
43+
44+
.Using a repository with dynamic projections
45+
[source,java,subs="+attributes"]
46+
----
47+
void someMethod(PersonRepository people) {
48+
49+
{projection-collection}<Person> aggregates =
50+
people.findByLastname("Matthews", Person.class);
51+
52+
{projection-collection}<NamesOnly> aggregates =
53+
people.findByLastname("Matthews", NamesOnly.class);
54+
}
55+
----
56+
57+
NOTE: Query parameters of type `Class` are inspected whether they qualify as dynamic projection parameter.
58+
If the actual return type of the query equals the generic parameter type of the `Class` parameter, then the matching `Class` parameter is not available for usage within the query or SpEL expressions.
59+
If you want to use a `Class` parameter as query argument then make sure to use a different generic parameter, for example `Class<?>`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
ifndef::projection-collection[]
2+
:projection-collection: Collection
3+
endif::[]
4+
5+
[[projections.interfaces]]
6+
= Interface-based Projections
7+
8+
The easiest way to limit the result of the queries to only the name attributes is by declaring an interface that exposes accessor methods for the properties to be read, as shown in the following example:
9+
10+
.A projection interface to retrieve a subset of attributes
11+
[source,java]
12+
----
13+
interface NamesOnly {
14+
15+
String getFirstname();
16+
String getLastname();
17+
}
18+
----
19+
20+
The important bit here is that the properties defined here exactly match properties in the aggregate root.
21+
Doing so lets a query method be added as follows:
22+
23+
.A repository using an interface based projection with a query method
24+
[source,java,subs="+attributes"]
25+
----
26+
interface PersonRepository extends Repository<Person, UUID> {
27+
28+
{projection-collection}<NamesOnly> findByLastname(String lastname);
29+
}
30+
----
31+
32+
The query execution engine creates proxy instances of that interface at runtime for each element returned and forwards calls to the exposed methods to the target object.
33+
34+
NOTE: Declaring a method in your `Repository` that overrides a base method (e.g. declared in `CrudRepository`, a store-specific repository interface, or the `Simple…Repository`) results in a call to the base method regardless of the declared return type.
35+
Make sure to use a compatible return type as base methods cannot be used for projections.
36+
Some store modules support `@Query` annotations to turn an overridden base method into a query method that then can be used to return projections.
37+
38+
[[projections.interfaces.nested]]
39+
Projections can be used recursively.
40+
If you want to include some of the `Address` information as well, create a projection interface for that and return that interface from the declaration of `getAddress()`, as shown in the following example:
41+
42+
.A projection interface to retrieve a subset of attributes
43+
[source,java]
44+
----
45+
interface PersonSummary {
46+
47+
String getFirstname();
48+
String getLastname();
49+
AddressSummary getAddress();
50+
51+
interface AddressSummary {
52+
String getCity();
53+
}
54+
}
55+
----
56+
57+
On method invocation, the `address` property of the target instance is obtained and wrapped into a projecting proxy in turn.
58+
59+
[[projections.interfaces.closed]]
60+
== Closed Projections
61+
62+
A projection interface whose accessor methods all match properties of the target aggregate is considered to be a closed projection.
63+
The following example (which we used earlier in this chapter, too) is a closed projection:
64+
65+
.A closed projection
66+
[source,java]
67+
----
68+
interface NamesOnly {
69+
70+
String getFirstname();
71+
String getLastname();
72+
}
73+
----
74+
75+
If you use a closed projection, Spring Data can optimize the query execution, because we know about all the attributes that are needed to back the projection proxy.
76+
For more details on that, see the module-specific part of the reference documentation.
77+
78+
[[projections.interfaces.open]]
79+
== Open Projections
80+
81+
Accessor methods in projection interfaces can also be used to compute new values by using the `@Value` annotation, as shown in the following example:
82+
83+
[[projections.interfaces.open.simple]]
84+
.An Open Projection
85+
[source,java]
86+
----
87+
interface NamesOnly {
88+
89+
@Value("#{target.firstname + ' ' + target.lastname}")
90+
String getFullName();
91+
92+
}
93+
----
94+
95+
The aggregate root backing the projection is available in the `target` variable.
96+
A projection interface using `@Value` is an open projection.
97+
Spring Data cannot apply query execution optimizations in this case, because the SpEL expression could use any attribute of the aggregate root.
98+
99+
The expressions used in `@Value` should not be too complex -- you want to avoid programming in `String` variables.
100+
For very simple expressions, one option might be to resort to default methods (introduced in Java 8), as shown in the following example:
101+
102+
[[projections.interfaces.open.default]]
103+
.A projection interface using a default method for custom logic
104+
[source,java]
105+
----
106+
interface NamesOnly {
107+
108+
String getFirstname();
109+
String getLastname();
110+
111+
default String getFullName() {
112+
return getFirstname().concat(" ").concat(getLastname());
113+
}
114+
}
115+
----
116+
117+
This approach requires you to be able to implement logic purely based on the other accessor methods exposed on the projection interface.
118+
A second, more flexible, option is to implement the custom logic in a Spring bean and then invoke that from the SpEL expression, as shown in the following example:
119+
120+
[[projections.interfaces.open.bean-reference]]
121+
.Sample Person object
122+
[source,java]
123+
----
124+
@Component
125+
class MyBean {
126+
127+
String getFullName(Person person) {
128+
129+
}
130+
}
131+
132+
interface NamesOnly {
133+
134+
@Value("#{@myBean.getFullName(target)}")
135+
String getFullName();
136+
137+
}
138+
----
139+
140+
Notice how the SpEL expression refers to `myBean` and invokes the `getFullName(…)` method and forwards the projection target as a method parameter.
141+
Methods backed by SpEL expression evaluation can also use method parameters, which can then be referred to from the expression.
142+
The method parameters are available through an `Object` array named `args`.
143+
The following example shows how to get a method parameter from the `args` array:
144+
145+
.Sample Person object
146+
[source,java]
147+
----
148+
interface NamesOnly {
149+
150+
@Value("#{args[0] + ' ' + target.firstname + '!'}")
151+
String getSalutation(String prefix);
152+
}
153+
----
154+
155+
Again, for more complex expressions, you should use a Spring bean and let the expression invoke a method, as described <<projections.interfaces.open.bean-reference,earlier>>.
156+
157+
[[projections.interfaces.nullable-wrappers]]
158+
== Nullable Wrappers
159+
160+
Getters in projection interfaces can make use of nullable wrappers for improved null-safety.
161+
Currently supported wrapper types are:
162+
163+
* `java.util.Optional`
164+
* `com.google.common.base.Optional`
165+
* `scala.Option`
166+
* `io.vavr.control.Option`
167+
168+
.A projection interface using nullable wrappers
169+
[source,java]
170+
----
171+
interface NamesOnly {
172+
173+
Optional<String> getFirstname();
174+
}
175+
----
176+
177+
If the underlying projection value is not `null`, then values are returned using the present-representation of the wrapper type.
178+
In case the backing value is `null`, then the getter method returns the empty representation of the used wrapper type.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
ifndef::projection-collection[]
2+
:projection-collection: Collection
3+
endif::[]
4+
5+
Spring Data query methods usually return one or multiple instances of the aggregate root managed by the repository.
6+
However, it might sometimes be desirable to create projections based on certain attributes of those types.
7+
Spring Data allows modeling dedicated return types, to more selectively retrieve partial views of the managed aggregates.
8+
9+
Imagine a repository and aggregate root type such as the following example:
10+
11+
.A sample aggregate and repository
12+
[source,java,subs="+attributes"]
13+
----
14+
class Person {
15+
16+
@Id UUID id;
17+
String firstname, lastname;
18+
Address address;
19+
20+
static class Address {
21+
String zipCode, city, street;
22+
}
23+
}
24+
25+
interface PersonRepository extends Repository<Person, UUID> {
26+
27+
{projection-collection}<Person> findByLastname(String lastname);
28+
}
29+
----
30+
31+
Now imagine that we want to retrieve the person's name attributes only.
32+
What means does Spring Data offer to achieve this?
33+
The rest of this chapter answers that question.
34+
35+
NOTE: Projection types are types residing outside the entity's type hierarchy.
36+
Superclasses and interfaces implemented by the entity are inside the type hierarchy hence returning a supertype (or implemented interface) returns an instance of the fully materialized entity.

0 commit comments

Comments
 (0)