From 880354f1db59f45d6ee9292c265b2dfe0cc9f372 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 23 Oct 2017 17:39:00 +0200 Subject: [PATCH] DATAJPA-1209 - Re-instantiate compatibility with Hibernate < 5.2.11 for projection on native queries. We now explicitly check for the presence of Hibernate 5.2.11 or better for native query projections using Tuple during query execution. Doing so has the advantage of properly producing a mapped result so that projection method invocations can use the map keys to route the calls to the right value. With versions before 5.2.11 we rely on the select expression listing the columns to return in the order matching the declaration order of the methods in the projection interface (which is theoretically unstable die to the reflection API not guaranteeing a stable order). That said, it's strongly recommended to use both Hibernate in 5.2.11 or better in combination with explicitly aliased columns in select clauses for native queries using projections. Related ticket: DATAJPA-980. --- .../data/jpa/provider/HibernateUtils.java | 18 ++++++++- .../jpa/repository/query/NativeJpaQuery.java | 9 ++++- .../jpa/provider/HibernateUtilsUnitTests.java | 37 +++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/springframework/data/jpa/provider/HibernateUtilsUnitTests.java 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 d8baf0d48..1b8124bac 100644 --- a/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java +++ b/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-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. @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.List; import org.hibernate.Query; +import org.springframework.data.util.Version; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -39,6 +40,7 @@ public abstract class HibernateUtils { private static final Class HIBERNATE_QUERY_INTERFACE; private static final Method QUERY_STRING_METHOD; + private static final Version HIBERNATE_VERSION; private HibernateUtils() {} @@ -66,6 +68,10 @@ public abstract class HibernateUtils { HIBERNATE_QUERY_INTERFACE = queryInterface == null ? type : queryInterface; QUERY_STRING_METHOD = HIBERNATE_QUERY_INTERFACE == null ? null : ReflectionUtils.findMethod(HIBERNATE_QUERY_INTERFACE, "getQueryString"); + + String versionSource = org.hibernate.Version.getVersionString(); + + HIBERNATE_VERSION = Version.parse(versionSource.substring(0, versionSource.lastIndexOf('.'))); } /** @@ -87,4 +93,14 @@ public abstract class HibernateUtils { return ((Query) ReflectionUtils.invokeMethod(GET_HIBERNATE_QUERY, query)).getQueryString(); } + + /** + * Returns whether the currently used version of Hibernate is equal to or newer than the given one. + * + * @param version must not be {@literal null}. + * @return + */ + public static boolean isVersionOrBetter(Version version) { + return HIBERNATE_VERSION.isGreaterThanOrEqualTo(version); + } } 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 d025d4a0d..4ed902638 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-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. @@ -19,11 +19,13 @@ import javax.persistence.EntityManager; import javax.persistence.Query; import javax.persistence.Tuple; +import org.springframework.data.jpa.provider.HibernateUtils; import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.Parameters; 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.expression.spel.standard.SpelExpressionParser; /** @@ -36,6 +38,8 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; */ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { + private static final Version HIBERNATE_VERSION_SUPPORTING_TUPLES = new Version(5, 2, 11); + private final Class resultType; /** @@ -88,7 +92,8 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { return result; } - return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) ? Tuple.class + return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // + ? HibernateUtils.isVersionOrBetter(HIBERNATE_VERSION_SUPPORTING_TUPLES) ? Tuple.class : null // : result; } } diff --git a/src/test/java/org/springframework/data/jpa/provider/HibernateUtilsUnitTests.java b/src/test/java/org/springframework/data/jpa/provider/HibernateUtilsUnitTests.java new file mode 100644 index 000000000..3f07a99fe --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/provider/HibernateUtilsUnitTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 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.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.util.Version; + +/** + * Unit tests for {@link HibernateUtils}. + * + * @author Oliver Gierke + */ +public class HibernateUtilsUnitTests { + + static final Version HIBERNATE_BASELINE = new Version(3, 6, 10); + + @Test + public void looksUpHibernateVersion() { + assertThat(HibernateUtils.isVersionOrBetter(HIBERNATE_BASELINE), is(true)); + } +}