diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index c00e84414..65f0d3729 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -33,6 +33,7 @@ import javax.persistence.TypedQuery; import org.springframework.core.convert.converter.Converter; import org.springframework.data.jpa.provider.HibernateUtils; +import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.query.JpaQueryExecution.CollectionExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution; @@ -64,6 +65,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { private final JpaQueryMethod method; private final EntityManager em; private final JpaMetamodel metamodel; + private final PersistenceProvider provider; /** * Creates a new {@link AbstractJpaQuery} from the given {@link JpaQueryMethod}. @@ -79,6 +81,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { this.method = method; this.em = em; this.metamodel = new JpaMetamodel(em.getMetamodel()); + this.provider = PersistenceProvider.fromEntityManager(em); } /* @@ -235,6 +238,10 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { */ protected Class getTypeToRead() { + if (PersistenceProvider.ECLIPSELINK.equals(provider)) { + return null; + } + ResultProcessor resultFactory = getQueryMethod().getResultProcessor(); ReturnedType returnedType = resultFactory.getReturnedType(); diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index 172fb86b0..fdd7766d2 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2017 the original author or authors. + * Copyright 2008-2018 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. @@ -17,25 +17,32 @@ package org.springframework.data.jpa.repository.support; import static org.springframework.data.querydsl.QueryDslUtils.*; +import lombok.extern.slf4j.Slf4j; + import java.io.Serializable; import javax.persistence.EntityManager; +import javax.persistence.Tuple; import org.springframework.beans.factory.BeanFactory; import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.query.AbstractJpaQuery; import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy; +import org.springframework.data.jpa.repository.query.JpaQueryMethod; +import org.springframework.data.jpa.util.JpaMetamodel; import org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.QueryCreationListener; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; +import org.springframework.data.repository.query.ReturnedType; import org.springframework.util.Assert; /** @@ -65,6 +72,10 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(); addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor); + + if (extractor.equals(PersistenceProvider.ECLIPSELINK)) { + addQueryCreationListener(new EclipseLinkProjectionQueryCreationListener(entityManager)); + } } /* @@ -172,4 +183,57 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { return (JpaEntityInformation) JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager); } + + /** + * Query creation listener that informs EclipseLink users that they have to be extra careful when defining repository + * query methods using projections as we have to rely on the declaration order of the accessors in projection + * interfaces matching the order in columns. Alias-based mapping doesn't work with EclipseLink as it doesn't support + * {@link Tuple} based queries yet. + * + * @author Oliver Gierke + * @since 2.0.5 + * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141 + */ + @Slf4j + private static class EclipseLinkProjectionQueryCreationListener implements QueryCreationListener { + + private static final String ECLIPSELINK_PROJECTIONS = "Usage of Spring Data projections detected on persistence provider EclipseLink. Make sure the following query methods declare result columns in exactly the order the accessors are declared in the projecting interface or the order of parameters for DTOs:"; + + private final JpaMetamodel metamodel; + + private boolean warningLogged = false; + + /** + * Creates a new {@link EclipseLinkProjectionQueryCreationListener} for the given {@link EntityManager}. + * + * @param em must not be {@literal null}. + */ + public EclipseLinkProjectionQueryCreationListener(EntityManager em) { + + Assert.notNull(em, "EntityManager must not be null!"); + + this.metamodel = new JpaMetamodel(em.getMetamodel()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.QueryCreationListener#onCreation(org.springframework.data.repository.query.RepositoryQuery) + */ + @Override + public void onCreation(AbstractJpaQuery query) { + + JpaQueryMethod queryMethod = query.getQueryMethod(); + ReturnedType type = queryMethod.getResultProcessor().getReturnedType(); + + if (type.isProjecting() && !metamodel.isJpaManaged(type.getReturnedType())) { + + if (!warningLogged) { + log.info(ECLIPSELINK_PROJECTIONS); + this.warningLogged = true; + } + + log.info(" - {}", queryMethod); + } + } + } } diff --git a/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java index b6f38e79c..2a9cbcfec 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java @@ -54,6 +54,7 @@ public class AbstractStringBasedJpaQueryIntegrationTests { public void createsNormalQueryForJpaManagedReturnTypes() throws Exception { EntityManager mock = mock(EntityManager.class); + when(mock.getDelegate()).thenReturn(mock); when(mock.getEntityManagerFactory()).thenReturn(em.getEntityManagerFactory()); when(mock.getMetamodel()).thenReturn(em.getMetamodel()); diff --git a/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java index f6ec4fe8b..829b75e63 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java @@ -66,6 +66,7 @@ public class NamedQueryUnitTests { when(em.getMetamodel()).thenReturn(metamodel); when(em.getEntityManagerFactory()).thenReturn(emf); + when(em.getDelegate()).thenReturn(em); when(emf.createEntityManager()).thenReturn(em); } diff --git a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java index d9a0eb7a6..2b2f4437b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java @@ -90,6 +90,7 @@ public class SimpleJpaQueryUnitTests { when(em.createQuery(anyString())).thenReturn(query); when(em.createQuery(anyString(), eq(Long.class))).thenReturn(typedQuery); when(em.getEntityManagerFactory()).thenReturn(emf); + when(em.getDelegate()).thenReturn(em); when(emf.createEntityManager()).thenReturn(em); when(metadata.getDomainType()).thenReturn((Class) User.class); when(metadata.getReturnedDomainClass(Mockito.any(Method.class))).thenReturn((Class) User.class);