From f0855026cb8ad8af8343e4477780be2171dc628b Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 20 Jun 2019 13:39:10 +0200 Subject: [PATCH] DATAJPA-1562 - Made projections work with late Hibernate 4 versions. For Projections we check the Hibernate version to make sure it supports Tuples. This was only done based on Hibernate 5 versions although some Hibernate 4 versions do so as well. This is now taken in consideration for the version check. The versions we consider functional for tuples are determined by trial and error because no clear documentation when this was introduced to Hibernate could be found. Original pull request: #387. --- .../data/jpa/provider/HibernateUtils.java | 41 ++++++++++++++++++- .../repository/query/AbstractJpaQuery.java | 12 +++--- .../jpa/repository/query/NativeJpaQuery.java | 8 ++-- .../MetamodelIntegrationTests.java | 2 +- ...lipseLinkNamespaceUserRepositoryTests.java | 7 +++- .../OpenJpaNamespaceUserRepositoryTests.java | 6 +++ .../jpa/repository/UserRepositoryTests.java | 37 +++++++++-------- .../jpa/repository/sample/UserRepository.java | 4 ++ 8 files changed, 89 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java b/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java index efcbfdd35..ec3aa1665 100644 --- a/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java +++ b/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java @@ -27,7 +27,9 @@ import org.springframework.util.ReflectionUtils; /** * Utility functions to work with Hibernate. Mostly using reflection to make sure common functionality can be executed * against all the Hibernate version we support. - * + * + * @author Oliver Gierke + * @author Jens Schauder * @since 1.10.2 * @soundtrack Benny Greb - Soulfood (Live, https://www.youtube.com/watch?v=9_ErMa_CtSw) */ @@ -36,6 +38,11 @@ public abstract class HibernateUtils { private static final List TYPES = Arrays.asList("org.hibernate.jpa.HibernateQuery", "org.hibernate.ejb.HibernateQuery"); + + private static final Version HIBERNATE4_VERSION_SUPPORTING_TUPLES = new Version(4, 2, 21); + private static final Version HIBERNATE5_VERSION = new Version(5, 0, 0); + private static final Version HIBERNATE5_VERSION_SUPPORTING_TUPLES = new Version(5, 2, 11); + private static final Method GET_HIBERNATE_QUERY; private static final Class HIBERNATE_QUERY_INTERFACE; @@ -103,4 +110,36 @@ public abstract class HibernateUtils { public static boolean isVersionOrBetter(Version version) { return HIBERNATE_VERSION.isGreaterThanOrEqualTo(version); } + + /** + * Returns whether the currently used version of Hibernate is in the given interval of versions. + * + * @param lowerIncluding lower version bound to compare to. The lower version bound is inclusive. Must not be + * {@literal null}. + * @param upperExcluding upper version bound to compare to. The upper version bound is exclusive. Must not be + * {@literal null}. + * @return whence lowerIncluding <= Hibernate version < upperExcluding. + */ + private static boolean isVersionInInterval(Version lowerIncluding, Version upperExcluding) { + return HIBERNATE_VERSION.isGreaterThanOrEqualTo(lowerIncluding) && HIBERNATE_VERSION.isLessThan(upperExcluding); + } + + /** + * Returns wether the current version of Hibernate supports {@link javax.persistence.Tuple} as a return type for + * native queries. + */ + public static boolean supportsTuples() { + + return HibernateUtils.isVersionInInterval(HIBERNATE4_VERSION_SUPPORTING_TUPLES, HIBERNATE5_VERSION) + || HibernateUtils.isVersionOrBetter(HIBERNATE5_VERSION_SUPPORTING_TUPLES); + } + + /** + * Returns wether the current version of Hibernate supports {@link javax.persistence.Tuple} as a return type for + * native queries. + */ + public static boolean supportsTuplesForNativeQueries() { + + return HibernateUtils.isVersionOrBetter(HIBERNATE5_VERSION_SUPPORTING_TUPLES); + } } 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 cfe4e869f..348958a48 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 @@ -47,7 +47,6 @@ import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; -import org.springframework.data.util.Version; import org.springframework.util.Assert; /** @@ -57,11 +56,10 @@ import org.springframework.util.Assert; * @author Thomas Darimont * @author Mark Paluch * @author Nicolas Cirigliano + * @author Jens Schauder */ public abstract class AbstractJpaQuery implements RepositoryQuery { - protected static final Version HIBERNATE_VERSION_SUPPORTING_TUPLES = new Version(5, 2, 11); - private final JpaQueryMethod method; private final EntityManager em; private final JpaMetamodel metamodel; @@ -242,9 +240,11 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { return null; } - return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // - ? HibernateUtils.isVersionOrBetter(HIBERNATE_VERSION_SUPPORTING_TUPLES) ? Tuple.class : null // - : null; + return returnedType.isProjecting() // + && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // + && HibernateUtils.supportsTuples() // + ? Tuple.class // + : null; } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java index 4ebbbf6b0..0a0846d2b 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java @@ -81,8 +81,10 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { return result; } - return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // - ? HibernateUtils.isVersionOrBetter(HIBERNATE_VERSION_SUPPORTING_TUPLES) ? Tuple.class : null // - : result; + return returnedType.isProjecting() // + && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // + && HibernateUtils.supportsTuplesForNativeQueries() // + ? Tuple.class // + : result; } } diff --git a/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java b/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java index 742466816..7e2e49cc1 100644 --- a/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java @@ -102,7 +102,7 @@ public abstract class MetamodelIntegrationTests { assertThat(elements.get(0).getAlias(), is(nullValue())); } - @Test + @Test // DATAJPA-1273 @Transactional public void returnsAliasesInTuple() { diff --git a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java index ceb73698a..04e783e54 100644 --- a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java @@ -24,7 +24,6 @@ import javax.persistence.Query; import org.junit.Assume; import org.junit.Ignore; import org.junit.Test; - import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.data.util.Version; import org.springframework.test.context.ContextConfiguration; @@ -124,6 +123,12 @@ public class EclipseLinkNamespaceUserRepositoryTests extends NamespaceUserReposi @Test public void bindsNativeQueryResultsToProjectionByName() {} + /** + * TODO: Remove, once https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141 is fixed. + */ + @Override + public void findListOfMap() {} + /** * Ignores the test for EclipseLink 2.7.2. Reconsider once https://bugs.eclipse.org/bugs/show_bug.cgi?id=533240 is * fixed. diff --git a/src/test/java/org/springframework/data/jpa/repository/OpenJpaNamespaceUserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/OpenJpaNamespaceUserRepositoryTests.java index 40be24493..0d07b8003 100644 --- a/src/test/java/org/springframework/data/jpa/repository/OpenJpaNamespaceUserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/OpenJpaNamespaceUserRepositoryTests.java @@ -128,6 +128,12 @@ public class OpenJpaNamespaceUserRepositoryTests extends NamespaceUserRepository @Override public void bindsNativeQueryResultsToProjectionByName() {} + /** + * ignored since OpenJPA doesn't support tuples + */ + @Override + public void findListOfMap() {} + /** * ignored since OpenJPA doesn't support tuples */ diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index f4b253fc0..722c59cee 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -72,6 +72,7 @@ import org.springframework.data.jpa.domain.sample.Address; import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.SpecialUser; import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.provider.HibernateUtils; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.sample.SampleEvaluationContextExtension.SampleSecurityContextHolder; import org.springframework.data.jpa.repository.sample.UserRepository; @@ -101,8 +102,6 @@ import com.google.common.base.Optional; @Transactional public class UserRepositoryTests { - private static final Version HIBERNATE_VERSION_SUPPORTING_TUPLE_ON_NATIVE_QUERIES = new Version(5, 2, 11); - @PersistenceContext EntityManager em; // CUT @@ -2186,8 +2185,7 @@ public class UserRepositoryTests { @Test // DATAJPA-980 public void supportsProjectionsWithNativeQueries() { - Assume - .assumeTrue(getHibernateVersion().isGreaterThanOrEqualTo(HIBERNATE_VERSION_SUPPORTING_TUPLE_ON_NATIVE_QUERIES)); + Assume.assumeTrue(HibernateUtils.supportsTuplesForNativeQueries()); flushTestUsers(); @@ -2202,8 +2200,7 @@ public class UserRepositoryTests { @Test // DATAJPA-1248 public void supportsProjectionsWithNativeQueriesAndCamelCaseProperty() throws Exception { - Assume - .assumeTrue(getHibernateVersion().isGreaterThanOrEqualTo(HIBERNATE_VERSION_SUPPORTING_TUPLE_ON_NATIVE_QUERIES)); + Assume.assumeTrue(HibernateUtils.supportsTuplesForNativeQueries()); flushTestUsers(); User user = repository.findAll().get(0); @@ -2221,8 +2218,7 @@ public class UserRepositoryTests { @Test // DATAJPA-1273 public void bindsNativeQueryResultsToProjectionByName() { - Assume - .assumeTrue(getHibernateVersion().isGreaterThanOrEqualTo(HIBERNATE_VERSION_SUPPORTING_TUPLE_ON_NATIVE_QUERIES)); + Assume.assumeTrue(HibernateUtils.supportsTuples()); flushTestUsers(); @@ -2239,8 +2235,7 @@ public class UserRepositoryTests { @Test // DATAJPA-1301 public void returnsNullValueInMap() { - Assume - .assumeTrue(getHibernateVersion().isGreaterThanOrEqualTo(HIBERNATE_VERSION_SUPPORTING_TUPLE_ON_NATIVE_QUERIES)); + Assume.assumeTrue(HibernateUtils.supportsTuples()); firstUser.setLastname(null); flushTestUsers(); @@ -2257,6 +2252,22 @@ public class UserRepositoryTests { assertThat(map.get(new Object()), is(nullValue())); } + @Test // DATAJPA-1562 + public void findListOfMap() { + + Assume.assumeTrue(HibernateUtils.supportsTuples()); + + flushTestUsers(); + + List> listOfMaps = repository.findListOfMaps(); + + assertThat(listOfMaps, Matchers.> hasSize(4)); + for (Map map : listOfMaps) { + assertThat(map.entrySet(), Matchers.hasSize(2)); + } + + } + @Test(expected = DataIntegrityViolationException.class) // DATAJPA-1535 public void savingUserThrowsAnException() { // if this test fails this means deleteNewInstanceSucceedsByDoingNothing() might actually save the user without the @@ -2279,10 +2290,4 @@ public class UserRepositoryTests { assertThat(result.getTotalElements(), is(2L)); return result; } - - private static Version getHibernateVersion() { - - String hibernateVersion = org.hibernate.Version.getVersionString(); - return Version.parse(hibernateVersion.substring(0, hibernateVersion.lastIndexOf("."))); - } } diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index d018b57a1..95df6c268 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -493,6 +493,10 @@ public interface UserRepository @Query("select firstname as firstname, lastname as lastname from User u where u.firstname = 'Oliver'") Map findMapWithNullValues(); + // DATAJPA-1562 + @Query("select firstname as firstname, lastname as lastname from User u") + List> findListOfMaps(); + // DATAJPA-1334 List findByNamedQueryWithConstructorExpression();