JpaAdapter.java
/*
* Copyright 2026-present 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.util;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.CriteriaSelect;
import jakarta.persistence.criteria.CriteriaUpdate;
import java.util.List;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Creates {@link Query queries} from an {@link EntityManager} in a way that stays binary-compatible across Jakarta
* Persistence API generations.
* <p>
* Jakarta Persistence 4 changes the return type of the untyped query factory methods, so code compiled against 3.2
* fails with a {@link NoSuchMethodError} when running against 4 and therefore we're falling back tom reflection.
*
* @author Oscar Fanchin
* @author Christoph Strobl
* @since 4.2
*/
public abstract class JpaAdapter {
private static final ReflectiveMethod CREATE_QUERY = ReflectiveMethod.get(EntityManager.class, "createQuery",
String.class);
private static final ReflectiveMethod CREATE_NAMED_QUERY = ReflectiveMethod.get(EntityManager.class,
"createNamedQuery", String.class);
private static final ReflectiveMethod CREATE_NATIVE_QUERY = ReflectiveMethod.get(EntityManager.class,
"createNativeQuery", String.class);
private static final ReflectiveMethod CREATE_NATIVE_QUERY_WITH_TYPE = ReflectiveMethod.get(EntityManager.class,
"createNativeQuery", String.class, Class.class);
private static final ReflectiveMethod CREATE_NATIVE_QUERY_WITH_MAPPING = ReflectiveMethod.get(EntityManager.class,
"createNativeQuery", String.class, String.class);
private static final ReflectiveMethod CREATE_UPDATE_QUERY = resolveCriteriaStatementMethod(CriteriaUpdate.class);
private static final ReflectiveMethod CREATE_DELETE_QUERY = resolveCriteriaStatementMethod(CriteriaDelete.class);
// Method references are resolved on first execution, so the direct calls below are never linked under JPA 4.
private static final boolean JPA_32 = JpaDetector.isJpa32Present() && !JpaDetector.isJpa4Present();
private static final List<ReflectiveMethod> REFLECTIVE_METHODS = List.of(CREATE_QUERY, CREATE_NAMED_QUERY,
CREATE_NATIVE_QUERY, CREATE_NATIVE_QUERY_WITH_TYPE, CREATE_NATIVE_QUERY_WITH_MAPPING, CREATE_UPDATE_QUERY,
CREATE_DELETE_QUERY);
private JpaAdapter() {}
public static List<ReflectiveMethod> getReflectiveMethods() {
return REFLECTIVE_METHODS;
}
/**
* Create an untyped query using the runtime Jakarta Persistence API.
*/
public static Query createQuery(EntityManager entityManager, String queryString) {
Assert.notNull(entityManager, "EntityManager must not be null");
return JPA_32 //
? entityManager.createQuery(queryString) //
: (Query) CREATE_QUERY.invoke(entityManager, queryString);
}
/**
* Create an untyped named query using the runtime Jakarta Persistence API.
*/
public static Query createNamedQuery(EntityManager entityManager, String queryName) {
Assert.notNull(entityManager, "EntityManager must not be null");
return JPA_32 //
? entityManager.createNamedQuery(queryName) //
: (Query) CREATE_NAMED_QUERY.invoke(entityManager, queryName);
}
/**
* Create an untyped native query using the runtime Jakarta Persistence API.
*/
public static Query createNativeQuery(EntityManager entityManager, String queryString) {
Assert.notNull(entityManager, "EntityManager must not be null");
return JPA_32 //
? entityManager.createNativeQuery(queryString) //
: (Query) CREATE_NATIVE_QUERY.invoke(entityManager, queryString);
}
/**
* Create a native query for the given result type using the runtime Jakarta Persistence API.
*/
public static Query createNativeQuery(EntityManager entityManager, String queryString, Class<?> resultClass) {
Assert.notNull(entityManager, "EntityManager must not be null");
return JPA_32 //
? entityManager.createNativeQuery(queryString, resultClass) //
: (Query) CREATE_NATIVE_QUERY_WITH_TYPE.invoke(entityManager, queryString, resultClass);
}
/**
* Create a native query for the given SQL result set mapping using the runtime Jakarta Persistence API.
*/
public static Query createNativeQuery(EntityManager entityManager, String queryString, String resultSetMapping) {
Assert.notNull(entityManager, "EntityManager must not be null");
return JPA_32 //
? entityManager.createNativeQuery(queryString, resultSetMapping) //
: (Query) CREATE_NATIVE_QUERY_WITH_MAPPING.invoke(entityManager, queryString, resultSetMapping);
}
/**
* Create a typed query through the {@link CriteriaSelect} overload common to Jakarta Persistence API generations.
*/
public static <T> TypedQuery<T> createQuery(EntityManager entityManager, CriteriaQuery<T> criteriaQuery) {
Assert.notNull(entityManager, "EntityManager must not be null");
Assert.notNull(criteriaQuery, "CriteriaQuery must not be null");
return entityManager.createQuery((CriteriaSelect<T>) criteriaQuery);
}
/**
* Create an update query using the runtime Jakarta Persistence API.
*/
@SuppressWarnings("removal")
public static Query createQuery(EntityManager entityManager, CriteriaUpdate<?> criteriaUpdate) {
Assert.notNull(entityManager, "EntityManager must not be null");
Assert.notNull(criteriaUpdate, "CriteriaUpdate must not be null");
return JPA_32 //
? entityManager.createQuery(criteriaUpdate) //
: (Query) CREATE_UPDATE_QUERY.invoke(entityManager, criteriaUpdate);
}
/**
* Create a delete query using the runtime Jakarta Persistence API.
*/
@SuppressWarnings("removal")
public static Query createQuery(EntityManager entityManager, CriteriaDelete<?> criteriaDelete) {
Assert.notNull(entityManager, "EntityManager must not be null");
Assert.notNull(criteriaDelete, "CriteriaDelete must not be null");
return JPA_32 //
? entityManager.createQuery(criteriaDelete) //
: (Query) CREATE_DELETE_QUERY.invoke(entityManager, criteriaDelete);
}
/**
* Resolve the factory method for criteria mutations.
* <p>
* JPA 4.0 deprecated {@code createQuery(CriteriaStatement)} so we prefer {@code createStatement(CriteriaStatement)}
* where possible.
*
* @param criteriaType the criteria type to resolve the method for.
* @return the most appropriate method to use.
*/
private static ReflectiveMethod resolveCriteriaStatementMethod(Class<?> criteriaType) {
ReflectionUtils.MethodFilter filter = method -> method.getParameterCount() == 1
&& method.getParameterTypes()[0].isAssignableFrom(criteriaType)
&& Query.class.isAssignableFrom(method.getReturnType());
ReflectiveMethod method = ReflectiveMethod.find(EntityManager.class, "createStatement", filter);
if (method == null) {
method = ReflectiveMethod.find(EntityManager.class, "createQuery", filter);
}
if (method == null) {
throw new IllegalStateException(
"Cannot resolve EntityManager.createStatement(%1$s) nor EntityManager.createQuery(%1$s)"
.formatted(criteriaType.getSimpleName()));
}
return method;
}
}