Skip to content

Commit eae03a3

Browse files
committed
Introduce Expressions utility for type-safe Criteria Query Expression resolution.
1 parent ec00abd commit eae03a3

File tree

3 files changed

+434
-0
lines changed

3 files changed

+434
-0
lines changed
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.criteria;
17+
18+
import jakarta.persistence.criteria.CollectionJoin;
19+
import jakarta.persistence.criteria.Expression;
20+
import jakarta.persistence.criteria.Fetch;
21+
import jakarta.persistence.criteria.From;
22+
import jakarta.persistence.criteria.Join;
23+
import jakarta.persistence.criteria.JoinType;
24+
import jakarta.persistence.criteria.ListJoin;
25+
import jakarta.persistence.criteria.MapJoin;
26+
import jakarta.persistence.criteria.Selection;
27+
import jakarta.persistence.criteria.SetJoin;
28+
29+
import java.util.Arrays;
30+
import java.util.List;
31+
import java.util.Map;
32+
import java.util.function.Function;
33+
import java.util.stream.Collectors;
34+
35+
import org.springframework.data.core.PropertyReference;
36+
import org.springframework.data.core.TypedPropertyPath;
37+
import org.springframework.data.jpa.repository.query.QueryUtils;
38+
39+
/**
40+
* Utility methods to resolve JPA Criteria API objects using Spring Data's type-safe property references. These helper
41+
* methods obtain Criteria API objects using {@link TypedPropertyPath} and {@link PropertyReference} to resolve
42+
* {@link Expression property expressions} and {@link Join joins}.
43+
* <p>
44+
* The class is intended for concise, type-aware criteria construction through a type-safe DSL where property references
45+
* are preferred over string-based navigation.
46+
* <p>
47+
* Example:
48+
*
49+
* <pre class="code">
50+
* Root<User> root = criteriaQuery.from(User.class);
51+
*
52+
* Expression<User> expr = Expressions.get(root, User::getManager);
53+
*
54+
* Join<User, Address> join = Expressions.join(root, JoinType.INNER, j -&gt; j.join(User::getAddress));
55+
* </pre>
56+
*
57+
* @author Mark Paluch
58+
* @since 4.1
59+
* @see PropertyReference
60+
* @see TypedPropertyPath
61+
*/
62+
public abstract class Expressions {
63+
64+
/**
65+
* Create an {@link Expression} for the given property path.
66+
* <p>
67+
* The resulting expression can be used in predicates. Expression resolution navigates joins as necessary.
68+
*
69+
* @param from the root or join to start from.
70+
* @param property property path to navigate.
71+
* @return the expression.
72+
*/
73+
static <T, P> Expression<P> get(From<?, T> from, TypedPropertyPath<T, P> property) {
74+
return QueryUtils.toExpressionRecursively(from, property, false);
75+
}
76+
77+
/**
78+
* Create a {@link Selection} for the given property path.
79+
* <p>
80+
* The resulting object can be used in the selection, joined paths consider outer joins as needed.
81+
*
82+
* @param from the root or join to start from.
83+
* @param property property path to navigate.
84+
* @return the selection.
85+
*/
86+
static <T, P> Selection<P> select(From<?, T> from, TypedPropertyPath<T, P> property) {
87+
return QueryUtils.toExpressionRecursively(from, property, true);
88+
}
89+
90+
/**
91+
* Create a list of {@link Selection selection objects} for the given property paths.
92+
* <p>
93+
* The resulting objects can be used in the selection, joined paths consider outer joins as needed.
94+
*
95+
* @param from the root or join to start from.
96+
* @param properties property path to navigate.
97+
* @return the selection.
98+
*/
99+
@SafeVarargs
100+
static <T> List<Selection<?>> select(From<?, T> from, TypedPropertyPath<T, ?>... properties) {
101+
return Arrays.stream(properties).map(it -> get(from, it)).collect(Collectors.toUnmodifiableList());
102+
}
103+
104+
/**
105+
* Create a {@link Join} using the given {@link PropertyReference property}.
106+
*
107+
* @param from the root or join to start from.
108+
* @param property property reference to navigate.
109+
* @return the resolved join.
110+
* @see From#join(String)
111+
*/
112+
static <T, P> Join<T, P> join(From<?, T> from, PropertyReference<T, P> property) {
113+
return from.join(property.getName());
114+
}
115+
116+
/**
117+
* Create a {@link Join} considering {@link JoinType} using the given joiner function allowing to express joins using
118+
* property references.
119+
*
120+
* @param from the root or join to start from.
121+
* @param joinType the join type.
122+
* @param function joiner function.
123+
* @return the resolved join.
124+
* @see From#join(String, JoinType)
125+
*/
126+
static <T, J extends Join<?, ?>> J join(From<?, T> from, JoinType joinType, Function<Joiner<T>, J> function) {
127+
return function.apply(new TypedJoiner<>(from, joinType));
128+
}
129+
130+
/**
131+
* Create a {@link Fetch fetch join} using the given {@link PropertyReference property}.
132+
*
133+
* @param from the root or join to start from.
134+
* @param property property reference to navigate.
135+
* @return the resolved fetch.
136+
* @see From#fetch(String)
137+
*/
138+
static <T, P> Fetch<T, P> fetch(From<?, T> from, PropertyReference<T, P> property) {
139+
return from.fetch(property.getName());
140+
}
141+
142+
/**
143+
* Create a {@link Fetch fetch join} considering {@link JoinType} using the given fetcher function allowing to express
144+
* fetches using property references.
145+
*
146+
* @param from the root or join to start from.
147+
* @param joinType the join type.
148+
* @param function fetcher function.
149+
* @return the resolved fetch.
150+
* @see From#fetch(String, JoinType)
151+
*/
152+
static <T, F extends Fetch<?, ?>> F fetch(From<?, T> from, JoinType joinType, Function<Fetcher<T>, F> function) {
153+
return function.apply(new TypedFetcher<>(from, joinType));
154+
}
155+
156+
private Expressions() {}
157+
158+
/**
159+
* Strategy interface used by {@link Expressions#join} to obtain joins using property references.
160+
* <p>
161+
* Implementations adapt a {@link jakarta.persistence.criteria.From} and expose typed join methods for singular and
162+
* collection-valued attributes as well as map-valued attributes. The methods accept
163+
* {@link org.springframework.data.core.PropertyReference} instances to avoid string-based attribute navigation.
164+
*/
165+
interface Joiner<T> {
166+
167+
/**
168+
* Create a join for the given property.
169+
*
170+
* @param property the property to join.
171+
* @see From#join
172+
*/
173+
<P> Join<T, P> join(PropertyReference<T, P> property);
174+
175+
/**
176+
* Create a collection join for the given property.
177+
*
178+
* @param property the property to join.
179+
* @see From#joinCollection
180+
*/
181+
<P> CollectionJoin<T, P> joinCollection(PropertyReference<T, P> property);
182+
183+
/**
184+
* Create a list join for the given property.
185+
*
186+
* @param property the property to join.
187+
* @see From#joinList
188+
*/
189+
<P> ListJoin<T, P> joinList(PropertyReference<T, P> property);
190+
191+
/**
192+
* Create a set join for the given property.
193+
*
194+
* @param property the property to join.
195+
* @see From#joinSet
196+
*/
197+
<P> SetJoin<T, P> joinSet(PropertyReference<T, P> property);
198+
199+
/**
200+
* Create a map join for the given property.
201+
*
202+
* @param property the property to join.
203+
* @see From#joinMap
204+
*/
205+
<K, V, P extends Map<K, V>> MapJoin<T, K, V> joinMap(PropertyReference<T, P> property);
206+
207+
}
208+
209+
record TypedJoiner<T>(From<?, T> from, JoinType type) implements Joiner<T> {
210+
211+
public <P> Join<T, P> join(PropertyReference<T, P> property) {
212+
return from.join(property.getName(), type);
213+
}
214+
215+
public <P> CollectionJoin<T, P> joinCollection(PropertyReference<T, P> property) {
216+
return from.joinCollection(property.getName(), type);
217+
}
218+
219+
public <P> ListJoin<T, P> joinList(PropertyReference<T, P> property) {
220+
return from.joinList(property.getName(), type);
221+
}
222+
223+
public <P> SetJoin<T, P> joinSet(PropertyReference<T, P> property) {
224+
return from.joinSet(property.getName(), type);
225+
}
226+
227+
public <K, V, P extends Map<K, V>> MapJoin<T, K, V> joinMap(PropertyReference<T, P> property) {
228+
return from.joinMap(property.getName(), type);
229+
}
230+
231+
}
232+
233+
/**
234+
* Strategy interface used by {@link Expressions#fetch} to obtain fetch joins using property references.
235+
* <p>
236+
* Implementations adapt a {@link jakarta.persistence.criteria.From} and expose typed fetch methods accepting
237+
* {@link org.springframework.data.core.PropertyReference} instances to avoid string-based attribute navigation.
238+
*/
239+
interface Fetcher<T> {
240+
241+
/**
242+
* Create a fetch join for the given property.
243+
*
244+
* @param property the property to join.
245+
* @see From#fetch
246+
*/
247+
<P> Fetch<T, P> fetch(PropertyReference<T, P> property);
248+
249+
}
250+
251+
record TypedFetcher<T>(From<?, T> from, JoinType type) implements Fetcher<T> {
252+
253+
@Override
254+
public <P> Fetch<T, P> fetch(PropertyReference<T, P> property) {
255+
return from.fetch(property.getName(), type);
256+
}
257+
258+
}
259+
260+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* JPA Criteria Query support.
19+
*/
20+
@org.jspecify.annotations.NullMarked
21+
package org.springframework.data.jpa.criteria;

0 commit comments

Comments
 (0)