1 /*
2 * Copyright 2008-2020 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.provider;
17
18 import static org.springframework.data.jpa.provider.JpaClassUtils.*;
19 import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*;
20
21 import java.util.Collections;
22 import java.util.NoSuchElementException;
23
24 import javax.persistence.EntityManager;
25 import javax.persistence.Query;
26 import javax.persistence.metamodel.Metamodel;
27
28 import org.eclipse.persistence.jpa.JpaQuery;
29 import org.eclipse.persistence.queries.ScrollableCursor;
30 import org.hibernate.ScrollMode;
31 import org.hibernate.ScrollableResults;
32 import org.hibernate.proxy.HibernateProxy;
33 import org.springframework.data.util.CloseableIterator;
34 import org.springframework.lang.Nullable;
35 import org.springframework.transaction.support.TransactionSynchronizationManager;
36 import org.springframework.util.Assert;
37 import org.springframework.util.ConcurrentReferenceHashMap;
38
39 /**
40 * Enumeration representing persistence providers to be used.
41 *
42 * @author Oliver Gierke
43 * @author Thomas Darimont
44 * @author Mark Paluch
45 * @author Jens Schauder
46 */
47 public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
48
49 /**
50 * Hibernate persistence provider.
51 * <p>
52 * Since Hibernate 4.3 the location of the HibernateEntityManager moved to the org.hibernate.jpa package. In order to
53 * support both locations we interpret both classnames as a Hibernate {@code PersistenceProvider}.
54 *
55 * @see <a href="https://jira.spring.io/browse/DATAJPA-444">DATAJPA-444</a>
56 */
57 HIBERNATE(//
58 Collections.singletonList(HIBERNATE_ENTITY_MANAGER_INTERFACE), //
59 Collections.singletonList(HIBERNATE_JPA_METAMODEL_TYPE)) {
60
61 @Override
62 public String extractQueryString(Query query) {
63 return HibernateUtils.getHibernateQuery(query);
64 }
65
66 /**
67 * Return custom placeholder ({@code *}) as Hibernate does create invalid queries for count queries for objects with
68 * compound keys.
69 *
70 * @see <a href="https://hibernate.atlassian.net/browse/HHH-4044">HHH-4044</a>
71 * @see <a href="https://hibernate.atlassian.net/browse/HHH-3096">HHH-3096</a>
72 */
73 @Override
74 public String getCountQueryPlaceholder() {
75 return "*";
76 }
77
78 /*
79 * (non-Javadoc)
80 * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#isProxy(java.lang.Object)
81 */
82 @Override
83 public boolean shouldUseAccessorFor(Object entity) {
84 return entity instanceof HibernateProxy;
85 }
86
87 /*
88 * (non-Javadoc)
89 * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#getIdentifierFrom(java.lang.Object)
90 */
91 @Override
92 public Object getIdentifierFrom(Object entity) {
93 return ((HibernateProxy) entity).getHibernateLazyInitializer().getIdentifier();
94 }
95
96 /*
97 * (non-Javadoc)
98 * @see org.springframework.data.jpa.provider.PersistenceProvider#executeQueryWithResultStream(javax.persistence.Query)
99 */
100 @Override
101 public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
102 return new HibernateScrollableResultsIterator(jpaQuery);
103 }
104 },
105
106 /**
107 * EclipseLink persistence provider.
108 */
109 ECLIPSELINK(Collections.singleton(ECLIPSELINK_ENTITY_MANAGER_INTERFACE),
110 Collections.singleton(ECLIPSELINK_JPA_METAMODEL_TYPE)) {
111
112 @Override
113 public String extractQueryString(Query query) {
114 return ((JpaQuery<?>) query).getDatabaseQuery().getJPQLString();
115 }
116
117 /*
118 * (non-Javadoc)
119 * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#isProxy(java.lang.Object)
120 */
121 @Override
122 public boolean shouldUseAccessorFor(Object entity) {
123 return false;
124 }
125
126 /*
127 * (non-Javadoc)
128 * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#getIdentifierFrom(java.lang.Object)
129 */
130 @Nullable
131 @Override
132 public Object getIdentifierFrom(Object entity) {
133 return null;
134 }
135
136 /*
137 * (non-Javadoc)
138 * @see org.springframework.data.jpa.provider.PersistenceProvider#executeQueryWithResultStream(javax.persistence.Query)
139 */
140 @Override
141 public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
142 return new EclipseLinkScrollableResultsIterator<>(jpaQuery);
143 }
144 },
145
146 /**
147 * Unknown special provider. Use standard JPA.
148 */
149 GENERIC_JPA(Collections.singleton(GENERIC_JPA_ENTITY_MANAGER_INTERFACE), Collections.emptySet()) {
150
151 /*
152 * (non-Javadoc)
153 * @see org.springframework.data.jpa.repository.query.QueryExtractor#extractQueryString(javax.persistence.Query)
154 */
155 @Nullable
156 @Override
157 public String extractQueryString(Query query) {
158 return null;
159 }
160
161 /*
162 * (non-Javadoc)
163 * @see org.springframework.data.jpa.repository.support.PersistenceProvider#canExtractQuery()
164 */
165 @Override
166 public boolean canExtractQuery() {
167 return false;
168 }
169
170 /*
171 * (non-Javadoc)
172 * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#isProxy(java.lang.Object)
173 */
174 @Override
175 public boolean shouldUseAccessorFor(Object entity) {
176 return false;
177 }
178
179 /*
180 * (non-Javadoc)
181 * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#getIdentifierFrom(java.lang.Object)
182 */
183 @Nullable
184 @Override
185 public Object getIdentifierFrom(Object entity) {
186 return null;
187 }
188 };
189
190 static ConcurrentReferenceHashMap<Class<?>, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<>();
191 private final Iterable<String> entityManagerClassNames;
192 private final Iterable<String> metamodelClassNames;
193 /**
194 * Creates a new {@link PersistenceProvider}.
195 *
196 * @param entityManagerClassNames the names of the provider specific {@link EntityManager} implementations. Must not
197 * be {@literal null} or empty.
198 * @param metamodelClassNames must not be {@literal null}.
199 */
200 PersistenceProvider(Iterable<String> entityManagerClassNames, Iterable<String> metamodelClassNames) {
201
202 this.entityManagerClassNames = entityManagerClassNames;
203 this.metamodelClassNames = metamodelClassNames;
204 }
205
206 /**
207 * Caches the given {@link PersistenceProvider} for the given source type.
208 *
209 * @param type must not be {@literal null}.
210 * @param provider must not be {@literal null}.
211 * @return the {@code PersistenceProvider} passed in as an argument. Guaranteed to be not {@code null}.
212 */
213 private static PersistenceProvider cacheAndReturn(Class<?> type, PersistenceProvider provider) {
214 CACHE.put(type, provider);
215 return provider;
216 }
217
218 /**
219 * Determines the {@link PersistenceProvider} from the given {@link EntityManager}. If no special one can be
220 * determined {@link #GENERIC_JPA} will be returned.
221 *
222 * @param em must not be {@literal null}.
223 * @return will never be {@literal null}.
224 */
225 public static PersistenceProvider fromEntityManager(EntityManager em) {
226
227 Assert.notNull(em, "EntityManager must not be null!");
228
229 Class<?> entityManagerType = em.getDelegate().getClass();
230 PersistenceProvider cachedProvider = CACHE.get(entityManagerType);
231
232 if (cachedProvider != null) {
233 return cachedProvider;
234 }
235
236 for (PersistenceProvider provider : values()) {
237 for (String entityManagerClassName : provider.entityManagerClassNames) {
238 if (isEntityManagerOfType(em, entityManagerClassName)) {
239 return cacheAndReturn(entityManagerType, provider);
240 }
241 }
242 }
243
244 return cacheAndReturn(entityManagerType, GENERIC_JPA);
245 }
246
247 /**
248 * Determines the {@link PersistenceProvider} from the given {@link Metamodel}. If no special one can be determined
249 * {@link #GENERIC_JPA} will be returned.
250 *
251 * @param metamodel must not be {@literal null}.
252 * @return will never be {@literal null}.
253 */
254 public static PersistenceProvider fromMetamodel(Metamodel metamodel) {
255
256 Assert.notNull(metamodel, "Metamodel must not be null!");
257
258 Class<? extends Metamodel> metamodelType = metamodel.getClass();
259 PersistenceProvider cachedProvider = CACHE.get(metamodelType);
260
261 if (cachedProvider != null) {
262 return cachedProvider;
263 }
264
265 for (PersistenceProvider provider : values()) {
266 for (String metamodelClassName : provider.metamodelClassNames) {
267 if (isMetamodelOfType(metamodel, metamodelClassName)) {
268 return cacheAndReturn(metamodelType, provider);
269 }
270 }
271 }
272
273 return cacheAndReturn(metamodelType, GENERIC_JPA);
274 }
275
276 /**
277 * Returns the placeholder to be used for simple count queries. Default implementation returns {@code x}.
278 *
279 * @return a placeholder for count queries. Guaranteed to be not {@code null}.
280 */
281 public String getCountQueryPlaceholder() {
282 return "x";
283 }
284
285 /*
286 * (non-Javadoc)
287 * @see org.springframework.data.jpa.repository.query.QueryExtractor#canExtractQuery()
288 */
289 @Override
290 public boolean canExtractQuery() {
291 return true;
292 }
293
294 /**
295 * Holds the PersistenceProvider specific interface names.
296 *
297 * @author Thomas Darimont
298 * @author Jens Schauder
299 */
300 interface Constants {
301
302 String GENERIC_JPA_ENTITY_MANAGER_INTERFACE = "javax.persistence.EntityManager";
303 String ECLIPSELINK_ENTITY_MANAGER_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManager";
304 // needed as Spring only exposes that interface via the EM proxy
305 String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.jpa.HibernateEntityManager";
306
307 String HIBERNATE_JPA_METAMODEL_TYPE = "org.hibernate.metamodel.internal.MetamodelImpl";
308 String ECLIPSELINK_JPA_METAMODEL_TYPE = "org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl";
309 }
310
311 public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
312 throw new UnsupportedOperationException(
313 "Streaming results is not implement for this PersistenceProvider: " + name());
314 }
315
316 /**
317 * {@link CloseableIterator} for Hibernate.
318 *
319 * @author Thomas Darimont
320 * @author Oliver Gierke
321 * @since 1.8
322 */
323 private static class HibernateScrollableResultsIterator implements CloseableIterator<Object> {
324
325 private final @Nullable ScrollableResults scrollableResults;
326
327 /**
328 * Creates a new {@link HibernateScrollableResultsIterator} for the given {@link Query}.
329 *
330 * @param jpaQuery must not be {@literal null}.
331 */
332 HibernateScrollableResultsIterator(Query jpaQuery) {
333
334 org.hibernate.query.Query<?> query = jpaQuery.unwrap(org.hibernate.query.Query.class);
335 this.scrollableResults = query.setReadOnly(TransactionSynchronizationManager.isCurrentTransactionReadOnly())//
336 .scroll(ScrollMode.FORWARD_ONLY);
337 }
338
339 /*
340 * (non-Javadoc)
341 * @see java.util.Iterator#next()
342 */
343 @Override
344 public Object next() {
345
346 if (scrollableResults == null) {
347 throw new NoSuchElementException("No ScrollableResults");
348 }
349
350 // Cast needed for Hibernate 6 compatibility
351 Object[] row = (Object[]) scrollableResults.get();
352
353 return row.length == 1 ? row[0] : row;
354 }
355
356 /*
357 * (non-Javadoc)
358 * @see java.util.Iterator#hasNext()
359 */
360 @Override
361 public boolean hasNext() {
362 return scrollableResults != null && scrollableResults.next();
363 }
364
365 /*
366 * (non-Javadoc)
367 * @see org.springframework.data.util.CloseableIterator#close()
368 */
369 @Override
370 public void close() {
371
372 if (scrollableResults != null) {
373 scrollableResults.close();
374 }
375 }
376 }
377
378 /**
379 * {@link CloseableIterator} for EclipseLink.
380 *
381 * @author Thomas Darimont
382 * @author Oliver Gierke
383 * @param <T>
384 * @since 1.8
385 */
386 @SuppressWarnings("unchecked")
387 private static class EclipseLinkScrollableResultsIterator<T> implements CloseableIterator<T> {
388
389 private final @Nullable ScrollableCursor scrollableCursor;
390
391 /**
392 * Creates a new {@link EclipseLinkScrollableResultsIterator} for the given JPA {@link Query}.
393 *
394 * @param jpaQuery must not be {@literal null}.
395 */
396 EclipseLinkScrollableResultsIterator(Query jpaQuery) {
397
398 jpaQuery.setHint("eclipselink.cursor.scrollable", true);
399
400 this.scrollableCursor = (ScrollableCursor) jpaQuery.getSingleResult();
401 }
402
403 /*
404 * (non-Javadoc)
405 * @see java.util.Iterator#hasNext()
406 */
407 @Override
408 public boolean hasNext() {
409 return scrollableCursor != null && scrollableCursor.hasNext();
410 }
411
412 /*
413 * (non-Javadoc)
414 * @see java.util.Iterator#next()
415 */
416 @Override
417 public T next() {
418
419 if (scrollableCursor == null) {
420 throw new NoSuchElementException("No ScrollableCursor");
421 }
422
423 return (T) scrollableCursor.next();
424 }
425
426 /*
427 * (non-Javadoc)
428 * @see org.springframework.data.util.CloseableIterator#close()
429 */
430 @Override
431 public void close() {
432
433 if (scrollableCursor != null) {
434 scrollableCursor.close();
435 }
436 }
437 }
438 }
439