Skip to content

Commit b8c94bc

Browse files
committed
Add support for Value Expressions, Stream, Named and Modifying Queries.
See #3830
1 parent 496963f commit b8c94bc

20 files changed

+905
-194
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
*/
1616
package org.springframework.data.jpa.provider;
1717

18+
import org.hibernate.query.NativeQuery;
1819
import org.hibernate.query.Query;
1920
import org.hibernate.query.spi.SqmQuery;
21+
import org.hibernate.query.sql.spi.NamedNativeQueryMemento;
22+
import org.hibernate.query.sqm.spi.NamedSqmQueryMemento;
2023
import org.jspecify.annotations.Nullable;
2124

2225
/**
@@ -44,7 +47,6 @@ private HibernateUtils() {}
4447
public @Nullable static String getHibernateQuery(Object query) {
4548

4649
try {
47-
4850
// Try the new Hibernate implementation first
4951
if (query instanceof SqmQuery sqmQuery) {
5052

@@ -57,6 +59,22 @@ private HibernateUtils() {}
5759
return sqmQuery.getSqmStatement().toHqlString();
5860
}
5961

62+
// Try the new Hibernate implementation first
63+
if (query instanceof NamedSqmQueryMemento<?> sqmQuery) {
64+
65+
String hql = sqmQuery.getHqlString();
66+
67+
if (!hql.equals("<criteria>")) {
68+
return hql;
69+
}
70+
71+
return sqmQuery.getSqmStatement().toHqlString();
72+
}
73+
74+
if (query instanceof NamedNativeQueryMemento<?> nativeQuery) {
75+
return nativeQuery.getSqlString();
76+
}
77+
6078
// Couple of cases in which this still breaks, see HHH-15389
6179
} catch (RuntimeException o_O) {}
6280

@@ -67,4 +85,28 @@ private HibernateUtils() {}
6785
throw new IllegalArgumentException("Don't know how to extract the query string from " + query);
6886
}
6987
}
88+
89+
public static boolean isNativeQuery(Object query) {
90+
91+
// Try the new Hibernate implementation first
92+
if (query instanceof SqmQuery) {
93+
return false;
94+
}
95+
96+
if (query instanceof NativeQuery<?>) {
97+
return true;
98+
}
99+
100+
// Try the new Hibernate implementation first
101+
if (query instanceof NamedSqmQueryMemento<?>) {
102+
103+
return false;
104+
}
105+
106+
if (query instanceof NamedNativeQueryMemento<?>) {
107+
return true;
108+
}
109+
110+
return false;
111+
}
70112
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
import jakarta.persistence.EntityManager;
1919
import jakarta.persistence.metamodel.Metamodel;
2020

21-
import org.springframework.util.Assert;
22-
2321
import org.jspecify.annotations.Nullable;
22+
23+
import org.springframework.util.Assert;
2424
import org.springframework.util.ClassUtils;
2525

2626
/**
@@ -59,7 +59,7 @@ public static boolean isMetamodelOfType(Metamodel metamodel, String type) {
5959
return isOfType(metamodel, type, metamodel.getClass().getClassLoader());
6060
}
6161

62-
private static boolean isOfType(Object source, String typeName, @Nullable ClassLoader classLoader) {
62+
public static boolean isOfType(Object source, String typeName, @Nullable ClassLoader classLoader) {
6363

6464
Assert.notNull(source, "Source instance must not be null");
6565
Assert.hasText(typeName, "Target type name must not be null or empty");

spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*;
2020

2121
import jakarta.persistence.EntityManager;
22+
import jakarta.persistence.EntityManagerFactory;
2223
import jakarta.persistence.Query;
2324
import jakarta.persistence.metamodel.IdentifiableType;
2425
import jakarta.persistence.metamodel.Metamodel;
@@ -65,14 +66,20 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
6566
* @see <a href="https://github.com/spring-projects/spring-data-jpa/issues/846">DATAJPA-444</a>
6667
*/
6768
HIBERNATE(//
69+
Collections.singletonList(HIBERNATE_ENTITY_MANAGER_FACTORY_INTERFACE), //
6870
Collections.singletonList(HIBERNATE_ENTITY_MANAGER_INTERFACE), //
6971
Collections.singletonList(HIBERNATE_JPA_METAMODEL_TYPE)) {
7072

7173
@Override
72-
public @Nullable String extractQueryString(Query query) {
74+
public @Nullable String extractQueryString(Object query) {
7375
return HibernateUtils.getHibernateQuery(query);
7476
}
7577

78+
@Override
79+
public boolean isNativeQuery(Object query) {
80+
return HibernateUtils.isNativeQuery(query);
81+
}
82+
7683
/**
7784
* Return custom placeholder ({@code *}) as Hibernate does create invalid queries for count queries for objects with
7885
* compound keys.
@@ -115,14 +122,20 @@ public String getCommentHintKey() {
115122
/**
116123
* EclipseLink persistence provider.
117124
*/
118-
ECLIPSELINK(Collections.singleton(ECLIPSELINK_ENTITY_MANAGER_INTERFACE),
125+
ECLIPSELINK(List.of(ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE1, ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE2),
126+
Collections.singleton(ECLIPSELINK_ENTITY_MANAGER_INTERFACE),
119127
Collections.singleton(ECLIPSELINK_JPA_METAMODEL_TYPE)) {
120128

121129
@Override
122-
public String extractQueryString(Query query) {
130+
public String extractQueryString(Object query) {
123131
return ((JpaQuery<?>) query).getDatabaseQuery().getJPQLString();
124132
}
125133

134+
@Override
135+
public boolean isNativeQuery(Object query) {
136+
return false;
137+
}
138+
126139
@Override
127140
public boolean shouldUseAccessorFor(Object entity) {
128141
return false;
@@ -152,13 +165,19 @@ public String getCommentHintValue(String comment) {
152165
/**
153166
* Unknown special provider. Use standard JPA.
154167
*/
155-
GENERIC_JPA(Collections.singleton(GENERIC_JPA_ENTITY_MANAGER_INTERFACE), Collections.emptySet()) {
168+
GENERIC_JPA(Collections.singleton(GENERIC_JPA_ENTITY_MANAGER_INTERFACE),
169+
Collections.singleton(GENERIC_JPA_ENTITY_MANAGER_INTERFACE), Collections.emptySet()) {
156170

157171
@Override
158-
public @Nullable String extractQueryString(Query query) {
172+
public @Nullable String extractQueryString(Object query) {
159173
return null;
160174
}
161175

176+
@Override
177+
public boolean isNativeQuery(Object query) {
178+
return false;
179+
}
180+
162181
@Override
163182
public boolean canExtractQuery() {
164183
return false;
@@ -196,6 +215,7 @@ public boolean shouldUseAccessorFor(Object entity) {
196215
private static final Collection<PersistenceProvider> ALL = List.of(HIBERNATE, ECLIPSELINK, GENERIC_JPA);
197216

198217
private static final ConcurrentReferenceHashMap<Class<?>, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<>();
218+
private final Iterable<String> entityManagerFactoryClassNames;
199219
private final Iterable<String> entityManagerClassNames;
200220
private final Iterable<String> metamodelClassNames;
201221

@@ -204,24 +224,38 @@ public boolean shouldUseAccessorFor(Object entity) {
204224
/**
205225
* Creates a new {@link PersistenceProvider}.
206226
*
227+
* @param entityManagerFactoryClassNames the names of the provider specific
228+
* {@link jakarta.persistence.EntityManagerFactory} implementations. Must not be {@literal null} or empty.
207229
* @param entityManagerClassNames the names of the provider specific {@link EntityManager} implementations. Must not
208230
* be {@literal null} or empty.
209231
* @param metamodelClassNames must not be {@literal null}.
210232
*/
211-
PersistenceProvider(Iterable<String> entityManagerClassNames, Iterable<String> metamodelClassNames) {
233+
PersistenceProvider(Iterable<String> entityManagerFactoryClassNames, Iterable<String> entityManagerClassNames,
234+
Iterable<String> metamodelClassNames) {
212235

236+
this.entityManagerFactoryClassNames = entityManagerFactoryClassNames;
213237
this.entityManagerClassNames = entityManagerClassNames;
214238
this.metamodelClassNames = metamodelClassNames;
215239

216240
boolean present = false;
217-
for (String entityManagerClassName : entityManagerClassNames) {
241+
for (String emfClassName : entityManagerFactoryClassNames) {
218242

219-
if (ClassUtils.isPresent(entityManagerClassName, PersistenceProvider.class.getClassLoader())) {
243+
if (ClassUtils.isPresent(emfClassName, PersistenceProvider.class.getClassLoader())) {
220244
present = true;
221245
break;
222246
}
223247
}
224248

249+
if (!present) {
250+
for (String entityManagerClassName : entityManagerClassNames) {
251+
252+
if (ClassUtils.isPresent(entityManagerClassName, PersistenceProvider.class.getClassLoader())) {
253+
present = true;
254+
break;
255+
}
256+
}
257+
}
258+
225259
this.present = present;
226260
}
227261

@@ -266,6 +300,36 @@ public static PersistenceProvider fromEntityManager(EntityManager em) {
266300
return cacheAndReturn(entityManagerType, GENERIC_JPA);
267301
}
268302

303+
/**
304+
* Determines the {@link PersistenceProvider} from the given {@link EntityManager}. If no special one can be
305+
* determined {@link #GENERIC_JPA} will be returned.
306+
*
307+
* @param emf must not be {@literal null}.
308+
* @return will never be {@literal null}.
309+
*/
310+
public static PersistenceProvider fromEntityManagerFactory(EntityManagerFactory emf) {
311+
312+
Assert.notNull(emf, "EntityManager must not be null");
313+
314+
Class<?> entityManagerType = emf.getPersistenceUnitUtil().getClass();
315+
PersistenceProvider cachedProvider = CACHE.get(entityManagerType);
316+
317+
if (cachedProvider != null) {
318+
return cachedProvider;
319+
}
320+
321+
for (PersistenceProvider provider : ALL) {
322+
for (String emfClassName : provider.entityManagerFactoryClassNames) {
323+
if (isOfType(emf.getPersistenceUnitUtil(), emfClassName,
324+
emf.getPersistenceUnitUtil().getClass().getClassLoader())) {
325+
return cacheAndReturn(entityManagerType, provider);
326+
}
327+
}
328+
}
329+
330+
return cacheAndReturn(entityManagerType, GENERIC_JPA);
331+
}
332+
269333
/**
270334
* Determines the {@link PersistenceProvider} from the given {@link Metamodel}. If no special one can be determined
271335
* {@link #GENERIC_JPA} will be returned.
@@ -350,9 +414,15 @@ public boolean isPresent() {
350414
*/
351415
interface Constants {
352416

417+
String GENERIC_JPA_ENTITY_MANAGER_FACTORY_INTERFACE = "jakarta.persistence.EntityManagerFactory";
353418
String GENERIC_JPA_ENTITY_MANAGER_INTERFACE = "jakarta.persistence.EntityManager";
419+
420+
String ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE1 = "org.eclipse.persistence.internal.jpa.EntityManagerFactoryDelegate";
421+
String ECLIPSELINK_ENTITY_MANAGER_FACTORY_INTERFACE2 = "org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl";
354422
String ECLIPSELINK_ENTITY_MANAGER_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManager";
423+
355424
// needed as Spring only exposes that interface via the EM proxy
425+
String HIBERNATE_ENTITY_MANAGER_FACTORY_INTERFACE = "org.hibernate.jpa.internal.PersistenceUnitUtilImpl";
356426
String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.engine.spi.SessionImplementor";
357427

358428
String HIBERNATE_JPA_METAMODEL_TYPE = "org.hibernate.metamodel.model.domain.JpaMetamodel";

spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryExtractor.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jpa.provider;
1717

1818
import jakarta.persistence.Query;
19+
import jakarta.persistence.TypedQueryReference;
1920

2021
import org.jspecify.annotations.Nullable;
2122

@@ -28,19 +29,31 @@
2829
public interface QueryExtractor {
2930

3031
/**
31-
* Reverse engineers the query string from the {@link Query} object. This requires provider specific API as JPA does
32-
* not provide access to the underlying query string as soon as one has created a {@link Query} instance of it.
32+
* Reverse engineers the query string from the {@link Query} or a {@link TypedQueryReference} object. This requires
33+
* provider specific API as JPA does not provide access to the underlying query string as soon as one has created a
34+
* {@link Query} instance of it.
3335
*
3436
* @param query
3537
* @return the query string representing the query or {@literal null} if resolving is not possible.
3638
*/
3739
@Nullable
38-
String extractQueryString(Query query);
40+
String extractQueryString(Object query);
41+
42+
/**
43+
* Reverse engineers the query native flag from a {@link Query} or native query as JPA does not provide access to the
44+
* underlying query string once a (named) query is constructed.
45+
*
46+
* @param query
47+
* @return {@literal true} if the query is a native one.
48+
* @since 4.0
49+
*/
50+
boolean isNativeQuery(Object query);
3951

4052
/**
4153
* Returns whether the extractor is able to extract the original query string from a given {@link Query}.
4254
*
4355
* @return
4456
*/
4557
boolean canExtractQuery();
58+
4659
}
Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,30 +36,31 @@
3636

3737
/**
3838
* @author Christoph Strobl
39+
* @since 4.0
3940
*/
40-
class AotMetaModel implements Metamodel {
41+
class AotMetamodel implements Metamodel {
4142

4243
private final String persistenceUnit;
4344
private final Set<Class<?>> managedTypes;
4445
private final Lazy<EntityManagerFactory> entityManagerFactory = Lazy.of(this::init);
4546
private final Lazy<Metamodel> metamodel = Lazy.of(() -> entityManagerFactory.get().getMetamodel());
4647
private final Lazy<EntityManager> entityManager = Lazy.of(() -> entityManagerFactory.get().createEntityManager());
4748

48-
public AotMetaModel(Set<Class<?>> managedTypes) {
49+
public AotMetamodel(Set<Class<?>> managedTypes) {
4950
this("dynamic-tests", managedTypes);
5051
}
5152

52-
private AotMetaModel(String persistenceUnit, Set<Class<?>> managedTypes) {
53+
private AotMetamodel(String persistenceUnit, Set<Class<?>> managedTypes) {
5354
this.persistenceUnit = persistenceUnit;
5455
this.managedTypes = managedTypes;
5556
}
5657

57-
public static AotMetaModel hibernateModel(Class<?>... types) {
58-
return new AotMetaModel(Set.of(types));
58+
public static AotMetamodel hibernateModel(Class<?>... types) {
59+
return new AotMetamodel(Set.of(types));
5960
}
6061

61-
public static AotMetaModel hibernateModel(String persistenceUnit, Class<?>... types) {
62-
return new AotMetaModel(persistenceUnit, Set.of(types));
62+
public static AotMetamodel hibernateModel(String persistenceUnit, Class<?>... types) {
63+
return new AotMetamodel(persistenceUnit, Set.of(types));
6364
}
6465

6566
public <X> EntityType<X> entity(Class<X> cls) {
@@ -95,6 +96,11 @@ public EntityManager entityManager() {
9596
return entityManager.get();
9697
}
9798

99+
// TODO: Capture an existing factory bean (e.g. EntityManagerFactoryInfo) to extract PersistenceInfo
100+
public EntityManagerFactory getEntityManagerFactory() {
101+
return entityManagerFactory.get();
102+
}
103+
98104
EntityManagerFactory init() {
99105

100106
MutablePersistenceUnitInfo persistenceUnitInfo = new MutablePersistenceUnitInfo() {
@@ -121,4 +127,5 @@ public List<String> getManagedClassNames() {
121127
}
122128
}, Map.of("hibernate.dialect", "org.hibernate.dialect.H2Dialect")).build();
123129
}
130+
124131
}

0 commit comments

Comments
 (0)