/* * Copyright 2008-2017 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 * * http://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.provider; import static org.springframework.data.jpa.provider.JpaClassUtils.*; import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.Query; import javax.persistence.metamodel.Metamodel; import org.apache.openjpa.enhance.PersistenceCapable; import org.apache.openjpa.persistence.OpenJPAPersistence; import org.apache.openjpa.persistence.OpenJPAQuery; import org.apache.openjpa.persistence.jdbc.FetchDirection; import org.apache.openjpa.persistence.jdbc.JDBCFetchPlan; import org.apache.openjpa.persistence.jdbc.LRSSizeAlgorithm; import org.apache.openjpa.persistence.jdbc.ResultSetType; import org.eclipse.persistence.jpa.JpaQuery; import org.eclipse.persistence.queries.ScrollableCursor; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.proxy.HibernateProxy; import org.springframework.data.util.CloseableIterator; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; /** * Enumeration representing persistence providers to be used. * * @author Oliver Gierke * @author Thomas Darimont */ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor { /** * Hibernate persistence provider. * <p> * Since Hibernate 4.3 the location of the HibernateEntityManager moved to the org.hibernate.jpa package. In order to * support both locations we interpret both classnames as a Hibernate {@code PersistenceProvider}. * * @see <a href="https://jira.spring.io/browse/DATAJPA-444">DATAJPA-444</a> */ HIBERNATE(// Arrays.asList(HIBERNATE_ENTITY_MANAGER_INTERFACE), // Arrays.asList(HIBERNATE_JPA_METAMODEL_TYPE)) { public String extractQueryString(Query query) { return HibernateUtils.getHibernateQuery(query); } /** * Return custom placeholder ({@code *}) as Hibernate does create invalid queries for count queries for objects with * compound keys. * * @see <a href="https://hibernate.atlassian.net/browse/HHH-4044">HHH-4044</a> * @see <a href="https://hibernate.atlassian.net/browse/HHH-3096">HHH-3096</a> */ @Override public String getCountQueryPlaceholder() { return "*"; } /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#isProxy(java.lang.Object) */ @Override public boolean shouldUseAccessorFor(Object entity) { return entity instanceof HibernateProxy; } /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#getIdentifierFrom(java.lang.Object) */ @Override public Object getIdentifierFrom(Object entity) { return ((HibernateProxy) entity).getHibernateLazyInitializer().getIdentifier(); } /* * (non-Javadoc) * @see org.springframework.data.jpa.provider.PersistenceProvider#potentiallyConvertEmptyCollection(java.util.Collection) */ @Override public <T> Collection<T> potentiallyConvertEmptyCollection(Collection<T> collection) { return collection == null || collection.isEmpty() ? null : collection; } /* * (non-Javadoc) * @see org.springframework.data.jpa.provider.PersistenceProvider#executeQueryWithResultStream(javax.persistence.Query) */ @Override public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) { return new HibernateScrollableResultsIterator(jpaQuery); } }, /** * EclipseLink persistence provider. */ ECLIPSELINK(Collections.singleton(ECLIPSELINK_ENTITY_MANAGER_INTERFACE), Collections.singleton(ECLIPSELINK_JPA_METAMODEL_TYPE)) { public String extractQueryString(Query query) { return ((JpaQuery<?>) query).getDatabaseQuery().getJPQLString(); } /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#isProxy(java.lang.Object) */ @Override public boolean shouldUseAccessorFor(Object entity) { return false; } /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#getIdentifierFrom(java.lang.Object) */ @Override public Object getIdentifierFrom(Object entity) { return null; } /* (non-Javadoc) * @see org.springframework.data.jpa.provider.PersistenceProvider#potentiallyConvertEmptyCollection(java.util.Collection) */ @Override public <T> Collection<T> potentiallyConvertEmptyCollection(Collection<T> collection) { return collection == null || collection.isEmpty() ? null : collection; } /* * (non-Javadoc) * @see org.springframework.data.jpa.provider.PersistenceProvider#executeQueryWithResultStream(javax.persistence.Query) */ @Override public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) { return new EclipseLinkScrollableResultsIterator<Object>(jpaQuery); } }, /** * OpenJpa persistence provider. */ OPEN_JPA(Collections.singleton(OPENJPA_ENTITY_MANAGER_INTERFACE), Collections.singleton(OPENJPA_JPA_METAMODEL_TYPE)) { /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.query.QueryExtractor#extractQueryString(javax.persistence.Query) */ @Override public String extractQueryString(Query query) { return ((OpenJPAQuery<?>) query).getQueryString(); } /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#isProxy(java.lang.Object) */ @Override public boolean shouldUseAccessorFor(Object entity) { return entity instanceof PersistenceCapable; } /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#getIdentifierFrom(java.lang.Object) */ @Override public Object getIdentifierFrom(Object entity) { return ((PersistenceCapable) entity).pcFetchObjectId(); } /* * (non-Javadoc) * @see org.springframework.data.jpa.provider.PersistenceProvider#executeQueryWithResultStream(javax.persistence.Query) */ @Override public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) { return new OpenJpaResultStreamingIterator<Object>(jpaQuery); } }, /** * Unknown special provider. Use standard JPA. */ GENERIC_JPA(Collections.singleton(GENERIC_JPA_ENTITY_MANAGER_INTERFACE), Collections.<String> emptySet()) { /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.query.QueryExtractor#extractQueryString(javax.persistence.Query) */ @Override public String extractQueryString(Query query) { return null; } /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.PersistenceProvider#canExtractQuery() */ @Override public boolean canExtractQuery() { return false; } /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#isProxy(java.lang.Object) */ @Override public boolean shouldUseAccessorFor(Object entity) { return false; } /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.ProxyIdAccessor#getIdentifierFrom(java.lang.Object) */ @Override public Object getIdentifierFrom(Object entity) { return null; } }; /** * Holds the PersistenceProvider specific interface names. * * @author Thomas Darimont */ static interface Constants { String GENERIC_JPA_ENTITY_MANAGER_INTERFACE = "javax.persistence.EntityManager"; String OPENJPA_ENTITY_MANAGER_INTERFACE = "org.apache.openjpa.persistence.OpenJPAEntityManager"; String ECLIPSELINK_ENTITY_MANAGER_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManager"; String HIBERNATE_SESSION_INTERFACE = "org.hibernate.Session"; // needed as Spring only exposes that interface via the EM proxy String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.jpa.HibernateEntityManager"; String HIBERNATE_JPA_METAMODEL_TYPE = "org.hibernate.metamodel.internal.MetamodelImpl"; String ECLIPSELINK_JPA_METAMODEL_TYPE = "org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl"; String OPENJPA_JPA_METAMODEL_TYPE = "org.apache.openjpa.persistence.meta.MetamodelImpl"; } private static ConcurrentReferenceHashMap<Class<?>, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<Class<?>, PersistenceProvider>(); private final Iterable<String> entityManagerClassNames; private final Iterable<String> metamodelClassNames; /** * Creates a new {@link PersistenceProvider}. * * @param entityManagerClassNames the names of the provider specific {@link EntityManager} implementations. Must not * be {@literal null} or empty. */ private PersistenceProvider(Iterable<String> entityManagerClassNames, Iterable<String> metamodelClassNames) { this.entityManagerClassNames = entityManagerClassNames; this.metamodelClassNames = metamodelClassNames; } /** * Determines the {@link PersistenceProvider} from the given {@link EntityManager}. If no special one can be * determined {@link #GENERIC_JPA} will be returned. * * @param em must not be {@literal null}. * @return will never be {@literal null}. */ public static PersistenceProvider fromEntityManager(EntityManager em) { Assert.notNull(em, "EntityManager must not be null!"); Class<?> entityManagerType = em.getDelegate().getClass(); PersistenceProvider cachedProvider = CACHE.get(entityManagerType); if (cachedProvider != null) { return cachedProvider; } for (PersistenceProvider provider : values()) { for (String entityManagerClassName : provider.entityManagerClassNames) { if (isEntityManagerOfType(em, entityManagerClassName)) { return cacheAndReturn(entityManagerType, provider); } } } return cacheAndReturn(entityManagerType, GENERIC_JPA); } /** * Determines the {@link PersistenceProvider} from the given {@link Metamodel}. If no special one can be determined * {@link #GENERIC_JPA} will be returned. * * @param metamodel must not be {@literal null}. * @return will never be {@literal null}. */ public static PersistenceProvider fromMetamodel(Metamodel metamodel) { Assert.notNull(metamodel, "Metamodel must not be null!"); Class<? extends Metamodel> metamodelType = metamodel.getClass(); PersistenceProvider cachedProvider = CACHE.get(metamodelType); if (cachedProvider != null) { return cachedProvider; } for (PersistenceProvider provider : values()) { for (String metamodelClassName : provider.metamodelClassNames) { if (isMetamodelOfType(metamodel, metamodelClassName)) { return cacheAndReturn(metamodelType, provider); } } } return cacheAndReturn(metamodelType, GENERIC_JPA); } /** * Caches the given {@link PersistenceProvider} for the given source type. * * @param type must not be {@literal null}. * @param provider must not be {@literal null}. * @return */ private static PersistenceProvider cacheAndReturn(Class<?> type, PersistenceProvider provider) { CACHE.put(type, provider); return provider; } /* * (non-Javadoc) * * @see * org.springframework.data.jpa.repository.query.QueryExtractor#canExtractQuery * () */ public boolean canExtractQuery() { return true; } /** * Returns the placeholder to be used for simple count queries. Default implementation returns {@code *}. * * @return */ public String getCountQueryPlaceholder() { return "x"; } /** * Potentially converts an empty collection to the appropriate representation of this {@link PersistenceProvider}, * since some JPA providers cannot correctly handle empty collections. * * @see <a href="https://jira.spring.io/browse/DATAJPA-606">DATAJPA-606</a> * @param collection * @return */ public <T> Collection<T> potentiallyConvertEmptyCollection(Collection<T> collection) { return collection; } public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) { throw new UnsupportedOperationException( "Streaming results is not implement for this PersistenceProvider: " + name()); } /** * {@link CloseableIterator} for Hibernate. * * @author Thomas Darimont * @author Oliver Gierke * @param <T> the domain type to return≠ * @since 1.8 */ private static class HibernateScrollableResultsIterator implements CloseableIterator<Object> { private final ScrollableResults scrollableResults; /** * Creates a new {@link HibernateScrollableResultsIterator} for the given {@link Query}. * * @param jpaQuery must not be {@literal null}. */ public HibernateScrollableResultsIterator(Query jpaQuery) { org.hibernate.query.Query<?> query = jpaQuery.unwrap(org.hibernate.query.Query.class); this.scrollableResults = query.setReadOnly(TransactionSynchronizationManager.isCurrentTransactionReadOnly())// .scroll(ScrollMode.FORWARD_ONLY); } /* * (non-Javadoc) * @see java.util.Iterator#next() */ @Override public Object next() { Object[] row = scrollableResults.get(); return row.length == 1 ? row[0] : row; } /* * (non-Javadoc) * @see java.util.Iterator#hasNext() */ @Override public boolean hasNext() { return scrollableResults == null ? false : scrollableResults.next(); } /* * (non-Javadoc) * @see org.springframework.data.util.CloseableIterator#close() */ @Override public void close() { if (scrollableResults != null) { scrollableResults.close(); } } } /** * {@link CloseableIterator} for EclipseLink. * * @author Thomas Darimont * @author Oliver Gierke * @param <T> * @since 1.8 */ @SuppressWarnings("unchecked") private static class EclipseLinkScrollableResultsIterator<T> implements CloseableIterator<T> { private final ScrollableCursor scrollableCursor; /** * Creates a new {@link EclipseLinkScrollableResultsIterator} for the given JPA {@link Query}. * * @param jpaQuery must not be {@literal null}. */ public EclipseLinkScrollableResultsIterator(Query jpaQuery) { jpaQuery.setHint("eclipselink.cursor.scrollable", true); this.scrollableCursor = (ScrollableCursor) jpaQuery.getSingleResult(); } /* * (non-Javadoc) * @see java.util.Iterator#hasNext() */ @Override public boolean hasNext() { return scrollableCursor == null ? false : scrollableCursor.hasNext(); } /* * (non-Javadoc) * @see java.util.Iterator#next() */ @Override public T next() { return (T) scrollableCursor.next(); } /* * (non-Javadoc) * @see org.springframework.data.util.CloseableIterator#close() */ @Override public void close() { if (scrollableCursor != null) { scrollableCursor.close(); } } } /** * {@link CloseableIterator} for OpenJpa. * * @author Thomas Darimont * @author Oliver Gierke * @param <T> the domain type to return * @since 1.8 */ private static class OpenJpaResultStreamingIterator<T> implements CloseableIterator<T> { private final Iterator<T> iterator; /** * Createsa new {@link OpenJpaResultStreamingIterator} for the given JPA {@link Query}. * * @param jpaQuery must not be {@literal null}. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public OpenJpaResultStreamingIterator(Query jpaQuery) { OpenJPAQuery kq = OpenJPAPersistence.cast(jpaQuery); JDBCFetchPlan fetch = (JDBCFetchPlan) kq.getFetchPlan(); fetch.setFetchBatchSize(20); fetch.setResultSetType(ResultSetType.SCROLL_SENSITIVE); fetch.setFetchDirection(FetchDirection.FORWARD); fetch.setLRSSizeAlgorithm(LRSSizeAlgorithm.LAST); List<T> resultList = kq.getResultList(); iterator = resultList.iterator(); } /* * (non-Javadoc) * @see java.util.Iterator#hasNext() */ @Override public boolean hasNext() { return iterator == null ? false : iterator.hasNext(); } /* * (non-Javadoc) * @see java.util.Iterator#next() */ @Override public T next() { return iterator.next(); } /* * (non-Javadoc) * @see org.springframework.data.util.CloseableIterator#close() */ @Override public void close() { if (iterator != null) { OpenJPAPersistence.close(iterator); } } } }