Skip to content

Commit b617eb0

Browse files
mp911dechristophstrobl
authored andcommitted
DATACMNS-830 - Add common documentation part for projections.
Original Pull Request: #158
1 parent eafc085 commit b617eb0

File tree

1 file changed

+163
-0
lines changed

1 file changed

+163
-0
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
[[projections]]
2+
= Projections
3+
4+
Spring Data Repositories usually return the domain model when using query methods. However, sometimes, you may need to alter the view of that model for various reasons. In this section, you will learn how to define projections to serve up simplified and reduced views of resources.
5+
6+
Look at the following domain model:
7+
8+
[source,java]
9+
----
10+
@Entity
11+
public class Person {
12+
13+
@Id @GeneratedValue
14+
private Long id;
15+
private String firstName, lastName;
16+
17+
@OneToOne
18+
private Address address;
19+
20+
}
21+
22+
@Entity
23+
public class Address {
24+
25+
@Id @GeneratedValue
26+
private Long id;
27+
private String street, state, country;
28+
29+
30+
}
31+
----
32+
33+
This `Person` has several attributes:
34+
35+
* `id` is the primary key
36+
* `firstName` and `lastName` are data attributes
37+
* `address` is a link to another domain object
38+
39+
Now assume we create a corresponding repository as follows:
40+
41+
[source,java]
42+
----
43+
interface PersonRepository extends CrudRepository<Person, Long> {
44+
45+
Person findPersonByFirstName(String firstName);
46+
}
47+
----
48+
49+
Spring Data will return this domain object including all of its attributes. There are two options just to retrieve the `address` attribute. One option is to define a repository for `Address` objects like this:
50+
51+
[source,java]
52+
----
53+
interface AddressRepository extends CrudRepository<Address, Long> {}
54+
----
55+
56+
In this situation, using `PersonRepository` will still return the whole `Person` object. Using `AddressRepository` will return just the `Address`.
57+
58+
However, what if you don't want to expose `address` details at all? You can offer the consumer of your repository service an alternative by defining one or more projections.
59+
60+
.Simple Projection
61+
====
62+
[source,java]
63+
----
64+
interface NoAddresses { <1>
65+
66+
String getFirstName(); <2>
67+
68+
String getLastName(); <3>
69+
}
70+
----
71+
This projection has the following details:
72+
73+
<1> It's a plain Java interface making it declarative.
74+
<2> It exports the `firstName`.
75+
<3> It exports the `lastName`.
76+
====
77+
78+
The `NoAddresses` projection only has getters for `firstName` and `lastName` meaning that it will not serve up any address information. The query method definition returns in this case `NoAdresses` instead of `Person`.
79+
80+
[source,java]
81+
----
82+
interface PersonRepository extends CrudRepository<Person, Long> {
83+
84+
NoAddresses findByFirstName(String firstName);
85+
}
86+
----
87+
88+
Projections declare a contract between the underlying type and the method signatures related to the exposed properties. Hence it's required to name getter methods according to the property name of the underlying type. If the underlying property is named `firstName`, then the getter method must be named `getFirstName` otherwise Spring Data is not able to look up the source property. This type of projection is also called _closed projection_. Closed projections expose a subset of properties hence they can be used to optimize the query in a way to reduce the selected fields from the data store. The other type is, as you might imagine, an _open projection_.
89+
90+
[[projections.remodelling-data]]
91+
== Remodelling data
92+
93+
So far, you have seen how projections can be used to reduce the information that is presented to the user. Projections can be used to adjust the exposed data model. You can add virtual properties to your projection. Look at the following projection interface:
94+
95+
.Renaming a property
96+
====
97+
[source,java]
98+
----
99+
interface RenamedProperty { <1>
100+
101+
String getFirstName(); <2>
102+
103+
@Value("#{target.lastName}")
104+
String getName(); <3>
105+
}
106+
----
107+
This projection has the following details:
108+
109+
<1> It's a plain Java interface making it declarative.
110+
<2> It exports the `firstName`.
111+
<3> It exports the `name` property. Since this property is virtual it requires `@Value("#{target.lastName}")` to specify the property source.
112+
====
113+
114+
The backing domain model does not have this property so we need to tell Spring Data from where this property is obtained.
115+
Virtual properties are the place where `@Value` comes into play. The `name` getter is annotated with `@Value` to use SpEL expressions pointing to the backing property `lastName`. You may have noticed `lastName` is prefixed with `target.` which is the variable name pointing to the backing object. Using `@Value` on methods allows defining where and how the value is obtained.
116+
117+
Some applications require the full name of a person. Concatenating strings with `String.format("%s %s", person.getFirstName(), person.getLastName())` would be one possibility but this piece of code needs to be called in every place the full name is required. Virtual properties on projections leverage the need for repeating that code all over.
118+
119+
[source,java]
120+
----
121+
interface FullNameAndCountry {
122+
123+
@Value("#{target.firstName} #{target.lastName}")
124+
String getFullName();
125+
126+
@Value("#{target.address.country}")
127+
String getCountry();
128+
}
129+
----
130+
131+
In fact, `@Value` gives full access to the target object and its nested properties. SpEL expressions are extremly powerful as the definition is always applied to the projection method. Let's take SpEL expressions in projections to the next level.
132+
133+
134+
Imagine you had the following domain model definition:
135+
136+
[source,java]
137+
----
138+
@Entity
139+
public class User {
140+
141+
@Id @GeneratedValue
142+
private Long id;
143+
private String name;
144+
145+
private String password;
146+
147+
}
148+
----
149+
150+
IMPORTANT: This example may seem a bit contrived, but it's possible with a richer domain model and many projections, to accidentally leak such details. Since Spring Data cannot discern the sensitivity of such data, it is up to the developers to avoid such situations. Storing a password as plain-text is discouraged. You really shouldn't do this. For this example, you could also replace `password` with anything else that is secret.
151+
152+
In some cases, you might keep the `password` as secret as possible and not expose it more than it should be. The solution is to create a projection using `@Value` together with a SpEL expression.
153+
154+
[source,java]
155+
----
156+
interface PasswordProjection {
157+
@Value("#{(target.password == null || target.password.empty) ? null : '******'}")
158+
String getPassword();
159+
}
160+
----
161+
162+
The expression checks whether the password is `null` or empty and returns `null` in this case, otherwise six asterisks to indicate a password was set.
163+

0 commit comments

Comments
 (0)