Skip to content

DATAJPA-1657 - @Procedure annotation doesn't work with cursors (NULL when using REF_CURSOR) and ResultSets that don't come from cursors #409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
* @author Christoph Strobl
* @author Nicolas Cirigliano
* @author Jens Schauder
* @author Gabriel Basilio
*/
public abstract class JpaQueryExecution {

Expand Down Expand Up @@ -300,6 +301,8 @@ protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccesso
*/
static class ProcedureExecution extends JpaQueryExecution {

private static final String NO_SURROUNDING_TRANSACTION = "You're trying to execute a @Procedure method without a surrounding transaction that keeps the connection open so that the ResultSet can actually be consumed. Make sure the consumer code uses @Transactional or any other way of declaring a (read-only) transaction.";

/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, java.lang.Object[])
Expand All @@ -311,7 +314,24 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAcce

StoredProcedureJpaQuery storedProcedureJpaQuery = (StoredProcedureJpaQuery) jpaQuery;
StoredProcedureQuery storedProcedure = storedProcedureJpaQuery.createQuery(accessor);
storedProcedure.execute();

boolean returnsResultSet = storedProcedure.execute();

if (returnsResultSet) {
if (!SurroundingTransactionDetectorMethodInterceptor.INSTANCE.isSurroundingTransactionActive())
throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION);

List<?> result = storedProcedure.getResultList();

if (!storedProcedureJpaQuery.getQueryMethod().isCollectionQuery()) {
if (result.isEmpty())
return null;
if (result.size() == 1)
return result.get(0);
}

return result;
}

return storedProcedureJpaQuery.extractOutputValue(storedProcedure);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
* @author Gabriel Basilio
* @since 1.6
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
Expand All @@ -51,4 +52,9 @@
* The name of the outputParameter, defaults to {@code ""}.
*/
String outputParameterName() default "";

/**
* Whether the procedure returns a Ref Cursor from the database {@code false}.
*/
boolean refCursor() default false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2014-2020 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
*
* https://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.repository.query;

import org.springframework.lang.Nullable;

import javax.persistence.ParameterMode;

/**
* This class represents a Stored Procedure Parameter
* and an instance of the annotation {@link javax.persistence.StoredProcedureParameter}.
*
* @author Gabriel Basilio
*/
public class ProcedureParameter {

private final String name;
private final ParameterMode mode;
private final Class<?> type;

public ProcedureParameter(@Nullable String name, ParameterMode mode, Class<?> type) {
this.name = name;
this.mode = mode;
this.type = type;
}

public String getName() {
return name;
}

public ParameterMode getMode() {
return mode;
}

public Class<?> getType() {
return type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import javax.persistence.NamedStoredProcedureQueries;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.ParameterMode;
import javax.persistence.StoredProcedureParameter;

import org.springframework.core.annotation.AnnotatedElementUtils;
Expand All @@ -38,6 +39,7 @@
* @author Mark Paluch
* @author Diego Diez
* @author Jeff Sheets
* @author Gabriel Basilio
* @since 1.6
*/
enum StoredProcedureAttributeSource {
Expand Down Expand Up @@ -72,7 +74,7 @@ public StoredProcedureAttributes createFrom(Method method, JpaEntityMetadata<?>
+ method);
}

return new StoredProcedureAttributes(procedureName, procedure.outputParameterName(), method.getReturnType());
return new StoredProcedureAttributes(procedureName, createOutputProcedureParameterFrom(method, procedure));
}

/**
Expand Down Expand Up @@ -102,28 +104,29 @@ private String deriveProcedureNameFrom(Method method, Procedure procedure) {
private StoredProcedureAttributes newProcedureAttributesFrom(Method method,
NamedStoredProcedureQuery namedStoredProc, Procedure procedure) {

List<String> outputParameterNames = new ArrayList<>();
List<Class<?>> outputParameterTypes = new ArrayList<>();
List<ProcedureParameter> outputParameters = new ArrayList<>();

if (!procedure.outputParameterName().isEmpty()) {
// we give the output parameter definition from the @Procedure annotation precedence
outputParameterNames.add(procedure.outputParameterName());
outputParameters.add(createOutputProcedureParameterFrom(method, procedure));
} else {

// try to discover the output parameter
List<StoredProcedureParameter> outputParameters = extractOutputParametersFrom(namedStoredProc);
List<StoredProcedureParameter> namedProcedureOutputParameters = extractOutputParametersFrom(namedStoredProc);

for (StoredProcedureParameter outputParameter : outputParameters) {
outputParameterNames.add(outputParameter.name());
outputParameterTypes.add(outputParameter.type());
for (StoredProcedureParameter outputParameter : namedProcedureOutputParameters) {
outputParameters.add(new ProcedureParameter(
outputParameter.name(), outputParameter.mode(), outputParameter.type()));
}
}

if (outputParameterTypes.isEmpty()) {
outputParameterTypes.add(method.getReturnType());
}
return new StoredProcedureAttributes(namedStoredProc.name(), outputParameters, true);
}

return new StoredProcedureAttributes(namedStoredProc.name(), outputParameterNames, outputParameterTypes, true);
private ProcedureParameter createOutputProcedureParameterFrom(Method method, Procedure procedure) {
return new ProcedureParameter(procedure.outputParameterName(),
procedure.refCursor() ? ParameterMode.REF_CURSOR : ParameterMode.OUT,
method.getReturnType());
}

private List<StoredProcedureParameter> extractOutputParametersFrom(NamedStoredProcedureQuery namedStoredProc) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
* @author Mark Paluch
* @author Jeff Sheets
* @author Jens Schauder
* @author Gabriel Basilio
* @since 1.6
*/
class StoredProcedureAttributes {
Expand All @@ -42,52 +43,51 @@ class StoredProcedureAttributes {

private final boolean namedStoredProcedure;
private final String procedureName;
private final List<String> outputParameterNames;
private final List<Class<?>> outputParameterTypes;
private final List<ProcedureParameter> outputProcedureParameters;

/**
* Creates a new {@link StoredProcedureAttributes}.
*
* @param procedureName must not be {@literal null}.
* @param outputParameterName may be {@literal null}.
* @param outputParameterType must not be {@literal null}.
*/
StoredProcedureAttributes(String procedureName, @Nullable String outputParameterName,
Class<?> outputParameterType) {
this(procedureName, Collections.singletonList(outputParameterName), Collections.singletonList(outputParameterType), false);
StoredProcedureAttributes(String procedureName, ProcedureParameter parameter) {
this(procedureName, Collections.singletonList(parameter), false);
}

/**
* Creates a new {@link StoredProcedureAttributes}.
*
* @param procedureName must not be {@literal null}.
* @param outputParameterNames may be empty, but not {@literal null}.
* @param outputParameterTypes must not be empty, and cannot be a single element of {@literal null}.
* @param namedStoredProcedure flag signaling if the stored procedure has a name.
*/
StoredProcedureAttributes(String procedureName, List<String> outputParameterNames,
List<Class<?>> outputParameterTypes, boolean namedStoredProcedure) {
StoredProcedureAttributes(String procedureName, List<ProcedureParameter> outputProcedureParameters, boolean namedStoredProcedure) {

Assert.notNull(procedureName, "ProcedureName must not be null!");
Assert.notNull(outputParameterNames, "OutputParameterNames must not be null!");
Assert.notEmpty(outputParameterTypes, "OutputParameterTypes must not be empty!");
Assert.isTrue(outputParameterTypes.size() != 1 || outputParameterTypes.get(0) != null, "OutputParameterTypes must not have size 1 with a null value");
Assert.notNull(outputProcedureParameters, "OutputProcedureParameters must not be null!");
Assert.isTrue(outputProcedureParameters.size() != 1 || outputProcedureParameters.get(0) != null, "ProcedureParameters must not have size 1 with a null value");

this.procedureName = procedureName;
this.outputParameterNames = namedStoredProcedure
? outputParameterNames
: completeOutputParameterNames(outputParameterNames);
this.outputParameterTypes = outputParameterTypes;
this.namedStoredProcedure = namedStoredProcedure;
}

private List<String> completeOutputParameterNames(List<String> outputParameterNames) {
if (namedStoredProcedure) {
this.outputProcedureParameters = outputProcedureParameters;
} else {
this.outputProcedureParameters = getParametersWithCompletedNames(outputProcedureParameters);
}
}

return IntStream.range(0, outputParameterNames.size()) //
.mapToObj(i -> completeOutputParameterName(i, outputParameterNames.get(i))) //
private List<ProcedureParameter> getParametersWithCompletedNames(List<ProcedureParameter> procedureParameters) {
return IntStream.range(0, procedureParameters.size())
.mapToObj(i -> getParameterWithCompletedName(procedureParameters.get(i), i))
.collect(Collectors.toList());
}

private ProcedureParameter getParameterWithCompletedName(ProcedureParameter parameter, int i) {
return new ProcedureParameter(
completeOutputParameterName(i, parameter.getName()),
parameter.getMode(), parameter.getType());
}

private String completeOutputParameterName(int i, String paramName) {

return StringUtils.hasText(paramName) //
Expand All @@ -113,26 +113,15 @@ public String getProcedureName() {
*
* @return
*/
public List<String> getOutputParameterNames() {
return outputParameterNames;
}

/**
* Returns the types of the output parameters.
*
* @return
*/
public List<Class<?>> getOutputParameterTypes() {
return outputParameterTypes;
public boolean isNamedStoredProcedure() {
return namedStoredProcedure;
}

/**
* Returns whether the stored procedure is a named one.
*
* @return
* @return Returns the stored procedure output parameter list
*/
public boolean isNamedStoredProcedure() {
return namedStoredProcedure;
public List<ProcedureParameter> getOutputProcedureParameters() {
return outputProcedureParameters;
}

/**
Expand All @@ -141,6 +130,10 @@ public boolean isNamedStoredProcedure() {
* @return
*/
public boolean hasReturnValue() {
return !(outputParameterTypes.size() == 1 && (void.class.equals(outputParameterTypes.get(0)) || Void.class.equals(outputParameterTypes.get(0))));
if (getOutputProcedureParameters().isEmpty())
return false;

Class<?> outputType = getOutputProcedureParameters().get(0).getType();
return !(void.class.equals(outputType) || Void.class.equals(outputType));
}
}
Loading