From 883cb814209ab350331596a7f91c63bc4b818622 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Sun, 5 Jun 2016 13:02:43 +0200 Subject: [PATCH] DATAJPA-911 - More reflection to adapt to changes in Hibernate 5.2. We now workaround a JPA specification violation introduced in 5.2 [0] in parameter binding. This involves removing a workaround for a bug in older Hibernate variants that returned the parameter index as String for parameters that shouldn't return a name at all. We now solve that by explicitly not considering parameter names that are purely numeric as named parameters in the first place. This also avoids producing an exception in the case of the usage of named parameters in the first place. org.hibernate.Query was deprecated in Hibernate 5.2 but at the same time a couple of methods on it were changed in a binary incompatible way. We now explicitly detect that change in the execution of stream queries and fall back to reflection based invocation of the methods we need to call. [0] https://hibernate.atlassian.net/browse/HHH-10803 --- .../jpa/provider/PersistenceProvider.java | 21 +++++++++++++++++-- .../data/jpa/repository/query/QueryUtils.java | 7 ++++++- .../query/StringQueryParameterBinder.java | 18 ++++++++-------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java b/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java index 1c9eb844b..ac181bbf4 100644 --- a/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java +++ b/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java @@ -18,6 +18,7 @@ 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.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -43,7 +44,9 @@ import org.hibernate.proxy.HibernateProxy; import org.springframework.data.util.CloseableIterator; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; +import org.springframework.util.ReflectionUtils; /** * Enumeration representing persistence providers to be used. @@ -401,6 +404,11 @@ public enum PersistenceProvider implements QueryExtractor,ProxyIdAccessor { */ private static class HibernateScrollableResultsIterator implements CloseableIterator { + private static final Method READ_ONLY_METHOD = ClassUtils.getMethod(org.hibernate.Query.class, "setReadOnly", + boolean.class); + private static final Method SCROLL_METHOD = ClassUtils.getMethod(READ_ONLY_METHOD.getReturnType(), "scroll", + ScrollMode.class); + private final ScrollableResults scrollableResults; /** @@ -411,9 +419,18 @@ public enum PersistenceProvider implements QueryExtractor,ProxyIdAccessor { public HibernateScrollableResultsIterator(Query jpaQuery) { org.hibernate.Query query = jpaQuery.unwrap(org.hibernate.Query.class); + boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); - this.scrollableResults = query.setReadOnly(TransactionSynchronizationManager.isCurrentTransactionReadOnly()) - .scroll(ScrollMode.FORWARD_ONLY); + if (READ_ONLY_METHOD.getReturnType().equals(org.hibernate.Query.class)) { + + this.scrollableResults = query.setReadOnly(isReadOnly).scroll(ScrollMode.FORWARD_ONLY); + + } else { + + Object intermediate = ReflectionUtils.invokeMethod(READ_ONLY_METHOD, jpaQuery, isReadOnly); + this.scrollableResults = (ScrollableResults) ReflectionUtils.invokeMethod(SCROLL_METHOD, intermediate, + ScrollMode.FORWARD_ONLY); + } } /* diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index 4ccfa569c..298356184 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -81,6 +81,7 @@ public abstract class QueryUtils { private static final Pattern ALIAS_MATCH; private static final Pattern COUNT_MATCH; + private static final Pattern NO_DIGITS = Pattern.compile("\\D+"); private static final String IDENTIFIER = "[\\p{Lu}\\P{InBASIC_LATIN}\\p{Alnum}._$]+"; private static final String IDENTIFIER_GROUP = String.format("(%s)", IDENTIFIER); @@ -401,7 +402,11 @@ public abstract class QueryUtils { public static boolean hasNamedParameter(Query query) { for (Parameter parameter : query.getParameters()) { - if (parameter.getName() != null) { + + String name = parameter.getName(); + + // Hibernate 3 specific hack as it returns the index as String for the name. + if (name != null && NO_DIGITS.matcher(name).find()) { return true; } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java index b792d311a..0a27b6e81 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java @@ -71,6 +71,10 @@ public class StringQueryParameterBinder extends ParameterBinder { */ private ParameterBinding getBindingFor(Query jpaQuery, int position, Parameter methodParameter) { + if (hasNamedParameter(jpaQuery)) { + return query.getBindingFor(methodParameter.getName()); + } + try { jpaQuery.getParameter(position); @@ -78,15 +82,11 @@ public class StringQueryParameterBinder extends ParameterBinder { } catch (IllegalArgumentException o_O) { - if (hasNamedParameter(jpaQuery)) { - return query.getBindingFor(methodParameter.getName()); - } + // We should actually reject parameters unavailable, but as EclipseLink doesn't implement ….getParameter(int) for + // native queries correctly we need to fall back to an indexed parameter + // @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=427892 + + return new ParameterBinding(position); } - - // We should actually reject parameters unavailable, but as EclipseLink doesn't implement ….getParameter(int) for - // native queries correctly we need to fall back to an indexed parameter - // @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=427892 - - return new ParameterBinding(position); } }