/*
* Copyright 2008-2020 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.provider;
import static org.springframework.data.jpa.provider.JpaClassUtils.*;
import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*;
import java.util.Collections;
import java.util.NoSuchElementException;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.metamodel.Metamodel;
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.lang.Nullable;
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, Thomas Darimont, Mark Paluch, Jens Schauder
/**
* Enumeration representing persistence providers to be used.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
* @author Jens Schauder
*/
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
Hibernate persistence provider.
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 PersistenceProvider
.
See Also:
/**
* 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(//
Collections.singletonList(HIBERNATE_ENTITY_MANAGER_INTERFACE), //
Collections.singletonList(HIBERNATE_JPA_METAMODEL_TYPE)) {
@Override
public String extractQueryString(Query query) {
return HibernateUtils.getHibernateQuery(query);
}
Return custom placeholder (*
) as Hibernate does create invalid queries for count queries for objects with compound keys. See Also:
/**
* 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#executeQueryWithResultStream(javax.persistence.Query)
*/
@Override
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
return new HibernateScrollableResultsIterator(jpaQuery);
}
},
EclipseLink persistence provider.
/**
* EclipseLink persistence provider.
*/
ECLIPSELINK(Collections.singleton(ECLIPSELINK_ENTITY_MANAGER_INTERFACE),
Collections.singleton(ECLIPSELINK_JPA_METAMODEL_TYPE)) {
@Override
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)
*/
@Nullable
@Override
public Object getIdentifierFrom(Object entity) {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.provider.PersistenceProvider#executeQueryWithResultStream(javax.persistence.Query)
*/
@Override
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
return new EclipseLinkScrollableResultsIterator<>(jpaQuery);
}
},
Unknown special provider. Use standard JPA.
/**
* Unknown special provider. Use standard JPA.
*/
GENERIC_JPA(Collections.singleton(GENERIC_JPA_ENTITY_MANAGER_INTERFACE), Collections.emptySet()) {
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.QueryExtractor#extractQueryString(javax.persistence.Query)
*/
@Nullable
@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)
*/
@Nullable
@Override
public Object getIdentifierFrom(Object entity) {
return null;
}
};
static ConcurrentReferenceHashMap<Class<?>, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<>();
private final Iterable<String> entityManagerClassNames;
private final Iterable<String> metamodelClassNames;
Creates a new PersistenceProvider
. Params: - entityManagerClassNames – the names of the provider specific
EntityManager
implementations. Must not be null or empty. - metamodelClassNames – must not be null.
/**
* Creates a new {@link PersistenceProvider}.
*
* @param entityManagerClassNames the names of the provider specific {@link EntityManager} implementations. Must not
* be {@literal null} or empty.
* @param metamodelClassNames must not be {@literal null}.
*/
PersistenceProvider(Iterable<String> entityManagerClassNames, Iterable<String> metamodelClassNames) {
this.entityManagerClassNames = entityManagerClassNames;
this.metamodelClassNames = metamodelClassNames;
}
Caches the given PersistenceProvider
for the given source type. Params: - type – must not be null.
- provider – must not be null.
Returns: the PersistenceProvider
passed in as an argument. Guaranteed to be not null
.
/**
* 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 the {@code PersistenceProvider} passed in as an argument. Guaranteed to be not {@code null}.
*/
private static PersistenceProvider cacheAndReturn(Class<?> type, PersistenceProvider provider) {
CACHE.put(type, provider);
return provider;
}
Determines the PersistenceProvider
from the given EntityManager
. If no special one can be determined GENERIC_JPA
will be returned. Params: - em – must not be null.
Returns: will never be null.
/**
* 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 PersistenceProvider
from the given Metamodel
. If no special one can be determined GENERIC_JPA
will be returned. Params: - metamodel – must not be null.
Returns: will never be null.
/**
* 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);
}
Returns the placeholder to be used for simple count queries. Default implementation returns x
. Returns: a placeholder for count queries. Guaranteed to be not null
.
/**
* Returns the placeholder to be used for simple count queries. Default implementation returns {@code x}.
*
* @return a placeholder for count queries. Guaranteed to be not {@code null}.
*/
public String getCountQueryPlaceholder() {
return "x";
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.QueryExtractor#canExtractQuery()
*/
@Override
public boolean canExtractQuery() {
return true;
}
Holds the PersistenceProvider specific interface names.
Author: Thomas Darimont, Jens Schauder
/**
* Holds the PersistenceProvider specific interface names.
*
* @author Thomas Darimont
* @author Jens Schauder
*/
interface Constants {
String GENERIC_JPA_ENTITY_MANAGER_INTERFACE = "javax.persistence.EntityManager";
String ECLIPSELINK_ENTITY_MANAGER_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManager";
// 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";
}
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
throw new UnsupportedOperationException(
"Streaming results is not implement for this PersistenceProvider: " + name());
}
CloseableIterator
for Hibernate. Author: Thomas Darimont, Oliver Gierke Since: 1.8
/**
* {@link CloseableIterator} for Hibernate.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.8
*/
private static class HibernateScrollableResultsIterator implements CloseableIterator<Object> {
private final @Nullable ScrollableResults scrollableResults;
Creates a new HibernateScrollableResultsIterator
for the given Query
. Params: - jpaQuery – must not be null.
/**
* Creates a new {@link HibernateScrollableResultsIterator} for the given {@link Query}.
*
* @param jpaQuery must not be {@literal null}.
*/
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() {
if (scrollableResults == null) {
throw new NoSuchElementException("No ScrollableResults");
}
// Cast needed for Hibernate 6 compatibility
Object[] row = (Object[]) scrollableResults.get();
return row.length == 1 ? row[0] : row;
}
/*
* (non-Javadoc)
* @see java.util.Iterator#hasNext()
*/
@Override
public boolean hasNext() {
return scrollableResults != null && scrollableResults.next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.CloseableIterator#close()
*/
@Override
public void close() {
if (scrollableResults != null) {
scrollableResults.close();
}
}
}
CloseableIterator
for EclipseLink. Author: Thomas Darimont, Oliver Gierke Type parameters: - <T> –
Since: 1.8
/**
* {@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 @Nullable ScrollableCursor scrollableCursor;
Creates a new EclipseLinkScrollableResultsIterator
for the given JPA Query
. Params: - jpaQuery – must not be null.
/**
* Creates a new {@link EclipseLinkScrollableResultsIterator} for the given JPA {@link Query}.
*
* @param jpaQuery must not be {@literal null}.
*/
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 && scrollableCursor.hasNext();
}
/*
* (non-Javadoc)
* @see java.util.Iterator#next()
*/
@Override
public T next() {
if (scrollableCursor == null) {
throw new NoSuchElementException("No ScrollableCursor");
}
return (T) scrollableCursor.next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.CloseableIterator#close()
*/
@Override
public void close() {
if (scrollableCursor != null) {
scrollableCursor.close();
}
}
}
}