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();