Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0-GH-4085-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data JPA Parent</name>
Expand Down Expand Up @@ -39,7 +39,7 @@
<mysql-connector-java>9.5.0</mysql-connector-java>
<postgresql>42.7.8</postgresql>
<oracle>23.26.0.0.0</oracle>
<springdata.commons>4.1.0-SNAPSHOT</springdata.commons>
<springdata.commons>4.1.0-GH-3400-SNAPSHOT</springdata.commons>
<vavr>0.10.3</vavr>

<hibernate.groupId>org.hibernate</hibernate.groupId>
Expand Down
4 changes: 2 additions & 2 deletions spring-data-envers/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0-GH-4085-SNAPSHOT</version>

<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0-GH-4085-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-jpa-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0-GH-4085-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jpa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0-GH-4085-SNAPSHOT</version>

<name>Spring Data JPA</name>
<description>Spring Data module for JPA repositories.</description>
Expand All @@ -16,7 +16,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0-GH-4085-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
/*
* Copyright 2025 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
*
* https://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 org.springframework.data.jpa.criteria;

import jakarta.persistence.criteria.CollectionJoin;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Fetch;
import jakarta.persistence.criteria.From;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.ListJoin;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.criteria.SetJoin;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.data.core.PropertyReference;
import org.springframework.data.core.TypedPropertyPath;
import org.springframework.data.jpa.repository.query.QueryUtils;

/**
* Utility methods to resolve JPA Criteria API objects using Spring Data's type-safe property references. These helper
* methods obtain Criteria API objects using {@link TypedPropertyPath} and {@link PropertyReference} to resolve
* {@link Expression property expressions} and {@link Join joins}.
* <p>
* The class is intended for concise, type-aware criteria construction through a type-safe DSL where property references
* are preferred over string-based navigation.
* <p>
* Example:
*
* <pre class="code">
* Root<User> root = criteriaQuery.from(User.class);
*
* Expression<User> expr = Expressions.get(root, User::getManager);
*
* Join<User, Address> join = Expressions.join(root, JoinType.INNER, j -&gt; j.join(User::getAddress));
* </pre>
*
* @author Mark Paluch
* @since 4.1
* @see PropertyReference
* @see TypedPropertyPath
*/
public abstract class Expressions {

/**
* Create an {@link Expression} for the given property path.
* <p>
* The resulting expression can be used in predicates. Expression resolution navigates joins as necessary.
*
* @param from the root or join to start from.
* @param property property path to navigate.
* @return the expression.
*/
static <T, P> Expression<P> get(From<?, T> from, TypedPropertyPath<T, P> property) {
return QueryUtils.toExpressionRecursively(from, property, false);
}

/**
* Create a {@link Selection} for the given property path.
* <p>
* The resulting object can be used in the selection, joined paths consider outer joins as needed.
*
* @param from the root or join to start from.
* @param property property path to navigate.
* @return the selection.
*/
static <T, P> Selection<P> select(From<?, T> from, TypedPropertyPath<T, P> property) {
return QueryUtils.toExpressionRecursively(from, property, true);
}

/**
* Create a list of {@link Selection selection objects} for the given property paths.
* <p>
* The resulting objects can be used in the selection, joined paths consider outer joins as needed.
*
* @param from the root or join to start from.
* @param properties property path to navigate.
* @return the selection.
*/
@SafeVarargs
static <T> List<Selection<?>> select(From<?, T> from, TypedPropertyPath<T, ?>... properties) {
return Arrays.stream(properties).map(it -> get(from, it)).collect(Collectors.toUnmodifiableList());
}

/**
* Create a {@link Join} using the given {@link PropertyReference property}.
*
* @param from the root or join to start from.
* @param property property reference to navigate.
* @return the resolved join.
* @see From#join(String)
*/
static <T, P> Join<T, P> join(From<?, T> from, PropertyReference<T, P> property) {
return from.join(property.getName());
}

/**
* Create a {@link Join} considering {@link JoinType} using the given joiner function allowing to express joins using
* property references.
*
* @param from the root or join to start from.
* @param joinType the join type.
* @param function joiner function.
* @return the resolved join.
* @see From#join(String, JoinType)
*/
static <T, J extends Join<?, ?>> J join(From<?, T> from, JoinType joinType, Function<Joiner<T>, J> function) {
return function.apply(new TypedJoiner<>(from, joinType));
}

/**
* Create a {@link Fetch fetch join} using the given {@link PropertyReference property}.
*
* @param from the root or join to start from.
* @param property property reference to navigate.
* @return the resolved fetch.
* @see From#fetch(String)
*/
static <T, P> Fetch<T, P> fetch(From<?, T> from, PropertyReference<T, P> property) {
return from.fetch(property.getName());
}

/**
* Create a {@link Fetch fetch join} considering {@link JoinType} using the given fetcher function allowing to express
* fetches using property references.
*
* @param from the root or join to start from.
* @param joinType the join type.
* @param function fetcher function.
* @return the resolved fetch.
* @see From#fetch(String, JoinType)
*/
static <T, F extends Fetch<?, ?>> F fetch(From<?, T> from, JoinType joinType, Function<Fetcher<T>, F> function) {
return function.apply(new TypedFetcher<>(from, joinType));
}

private Expressions() {}

/**
* Strategy interface used by {@link Expressions#join} to obtain joins using property references.
* <p>
* Implementations adapt a {@link jakarta.persistence.criteria.From} and expose typed join methods for singular and
* collection-valued attributes as well as map-valued attributes. The methods accept
* {@link org.springframework.data.core.PropertyReference} instances to avoid string-based attribute navigation.
*/
interface Joiner<T> {

/**
* Create a join for the given property.
*
* @param property the property to join.
* @see From#join
*/
<P> Join<T, P> join(PropertyReference<T, P> property);

/**
* Create a collection join for the given property.
*
* @param property the property to join.
* @see From#joinCollection
*/
<P> CollectionJoin<T, P> joinCollection(PropertyReference<T, P> property);

/**
* Create a list join for the given property.
*
* @param property the property to join.
* @see From#joinList
*/
<P> ListJoin<T, P> joinList(PropertyReference<T, P> property);

/**
* Create a set join for the given property.
*
* @param property the property to join.
* @see From#joinSet
*/
<P> SetJoin<T, P> joinSet(PropertyReference<T, P> property);

/**
* Create a map join for the given property.
*
* @param property the property to join.
* @see From#joinMap
*/
<K, V, P extends Map<K, V>> MapJoin<T, K, V> joinMap(PropertyReference<T, P> property);

}

record TypedJoiner<T>(From<?, T> from, JoinType type) implements Joiner<T> {

public <P> Join<T, P> join(PropertyReference<T, P> property) {
return from.join(property.getName(), type);
}

public <P> CollectionJoin<T, P> joinCollection(PropertyReference<T, P> property) {
return from.joinCollection(property.getName(), type);
}

public <P> ListJoin<T, P> joinList(PropertyReference<T, P> property) {
return from.joinList(property.getName(), type);
}

public <P> SetJoin<T, P> joinSet(PropertyReference<T, P> property) {
return from.joinSet(property.getName(), type);
}

public <K, V, P extends Map<K, V>> MapJoin<T, K, V> joinMap(PropertyReference<T, P> property) {
return from.joinMap(property.getName(), type);
}

}

/**
* Strategy interface used by {@link Expressions#fetch} to obtain fetch joins using property references.
* <p>
* Implementations adapt a {@link jakarta.persistence.criteria.From} and expose typed fetch methods accepting
* {@link org.springframework.data.core.PropertyReference} instances to avoid string-based attribute navigation.
*/
interface Fetcher<T> {

/**
* Create a fetch join for the given property.
*
* @param property the property to join.
* @see From#fetch
*/
<P> Fetch<T, P> fetch(PropertyReference<T, P> property);

}

record TypedFetcher<T>(From<?, T> from, JoinType type) implements Fetcher<T> {

@Override
public <P> Fetch<T, P> fetch(PropertyReference<T, P> property) {
return from.fetch(property.getName(), type);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2025 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
*
* https://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.
*/

/**
* JPA Criteria Query support.
*/
@org.jspecify.annotations.NullMarked
package org.springframework.data.jpa.criteria;
Loading
Loading