Skip to content

Commit 61ca9c9

Browse files
committed
DATAJPA-1657 - Return ResultSets from @procedure with or without cursor
javax.persistence.StoredProcedureQuery.execute method tells us if a ResultSet is being returned or not. This is the key to finally decide what to return: Output parameter values or javax.persistence.StoredProcedureQuery.getResultList(). I added a logic to return single entities from it if the method annotated with @procedure does not have a Collection return type. When registering the procedure output parameters I needed to find a way to predict if the user is intending to return a ResultSet or not. I ended up doing it with this condition: If the method annotated with @procedure has a collection return type or it doesn't have a collection but it has an entity return type then I know for sure that a ResultSet is intended to be returned because regular output parameters are NEVER returned in collections but in arrays (Object[]) and also regular output parameters don't return entities, but primitive types.
1 parent aebd5b9 commit 61ca9c9

File tree

2 files changed

+65
-40
lines changed

2 files changed

+65
-40
lines changed

src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
* @author Christoph Strobl
5454
* @author Nicolas Cirigliano
5555
* @author Jens Schauder
56+
* @author Gabriel Basilio
5657
*/
5758
public abstract class JpaQueryExecution {
5859

@@ -313,7 +314,24 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAcce
313314

314315
StoredProcedureJpaQuery storedProcedureJpaQuery = (StoredProcedureJpaQuery) jpaQuery;
315316
StoredProcedureQuery storedProcedure = storedProcedureJpaQuery.createQuery(accessor);
316-
storedProcedure.execute();
317+
318+
boolean returnsResultSet = storedProcedure.execute();
319+
320+
if (returnsResultSet) {
321+
if (!SurroundingTransactionDetectorMethodInterceptor.INSTANCE.isSurroundingTransactionActive())
322+
throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION);
323+
324+
List<?> result = storedProcedure.getResultList();
325+
326+
if (!storedProcedureJpaQuery.getQueryMethod().isCollectionQuery()) {
327+
if (result.isEmpty())
328+
return null;
329+
if (result.size() == 1)
330+
return result.get(0);
331+
}
332+
333+
return result;
334+
}
317335

318336
return storedProcedureJpaQuery.extractOutputValue(storedProcedure);
319337
}

src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.persistence.EntityManager;
2424
import javax.persistence.NamedStoredProcedureQuery;
2525
import javax.persistence.ParameterMode;
26+
import javax.persistence.StoredProcedureParameter;
2627
import javax.persistence.StoredProcedureQuery;
2728
import javax.persistence.TypedQuery;
2829

@@ -132,42 +133,32 @@ Object extractOutputValue(StoredProcedureQuery storedProcedureQuery) {
132133
return null;
133134
}
134135

135-
Map<String, Object> outputValues = new HashMap<>();
136-
List<String> parameterNames = procedureAttributes.getOutputParameterNames();
137-
138-
for (int i = 0; i < parameterNames.size(); i++) {
136+
List<ProcedureParameter> outputParameters = procedureAttributes.getOutputProcedureParameters();
139137

140-
String name = parameterNames.get(i);
141-
outputValues.put(name, extractOutputParameter(storedProcedureQuery, i));
138+
if (outputParameters.size() == 1) {
139+
return extractOutputParameterValue(outputParameters.get(0), 0, storedProcedureQuery);
142140
}
143141

144-
return outputValues.size() == 1 ? outputValues.values().iterator().next() : outputValues;
145-
}
146-
147-
private Object extractOutputParameter(StoredProcedureQuery storedProcedureQuery, Integer index) {
142+
Map<String, Object> outputValues = new HashMap<>();
148143

149-
String outputParameterName = procedureAttributes.getOutputParameterNames().get(index);
150-
JpaParameters parameters = getQueryMethod().getParameters();
144+
for (int i = 0; i < outputParameters.size(); i++) {
145+
ProcedureParameter outputParameter = outputParameters.get(i);
146+
outputValues.put(outputParameter.getName(), extractOutputParameterValue(outputParameter, i, storedProcedureQuery));
147+
}
151148

152-
return extractOutputParameterValue(storedProcedureQuery, outputParameterName, index,
153-
parameters.getNumberOfParameters());
149+
return outputValues;
154150
}
155151

156152
/**
157-
* extract the value of an output parameter either by name or by index.
158-
*
159-
* @param storedProcedureQuery the query object of the stored procedure.
160-
* @param name the name of the output parameter
161-
* @param index index of the output parameter
162-
* @param offset for index based access the index after which to find the output parameter values
163-
* @return the value
153+
* @return The value of an output parameter either by name or by index.
164154
*/
165-
private Object extractOutputParameterValue(StoredProcedureQuery storedProcedureQuery, String name, Integer index,
166-
int offset) {
155+
private Object extractOutputParameterValue(ProcedureParameter outputParameter, Integer index, StoredProcedureQuery storedProcedureQuery) {
156+
157+
JpaParameters methodParameters = getQueryMethod().getParameters();
167158

168-
return useNamedParameters && StringUtils.hasText(name) ? //
169-
storedProcedureQuery.getOutputParameterValue(name)
170-
: storedProcedureQuery.getOutputParameterValue(offset + index + 1);
159+
return useNamedParameters && StringUtils.hasText(outputParameter.getName()) ?
160+
storedProcedureQuery.getOutputParameterValue(outputParameter.getName())
161+
: storedProcedureQuery.getOutputParameterValue(methodParameters.getNumberOfParameters() + index + 1);
171162
}
172163

173164
/**
@@ -192,9 +183,7 @@ private StoredProcedureQuery newNamedStoredProcedureQuery() {
192183
private StoredProcedureQuery newAdhocStoredProcedureQuery() {
193184

194185
JpaParameters params = getQueryMethod().getParameters();
195-
String procedureName = procedureAttributes.getProcedureName();
196-
197-
StoredProcedureQuery procedureQuery = getEntityManager().createStoredProcedureQuery(procedureName);
186+
StoredProcedureQuery procedureQuery = createAdhocStoredProcedureQuery();
198187

199188
for (JpaParameter param : params) {
200189

@@ -214,23 +203,41 @@ private StoredProcedureQuery newAdhocStoredProcedureQuery() {
214203

215204
if (procedureAttributes.hasReturnValue()) {
216205

217-
ParameterMode mode = ParameterMode.OUT;
206+
ProcedureParameter procedureOutput = procedureAttributes.getOutputProcedureParameters().get(0);
218207

219-
IntStream.range(0, procedureAttributes.getOutputParameterTypes().size()).forEach(i -> {
220-
Class<?> outputParameterType = procedureAttributes.getOutputParameterTypes().get(i);
208+
/* If the stored procedure returns a ResultSet without using REF_CURSOR,
209+
it is not necessary to declare an output parameter */
210+
if ((isResultSetProcedure() && procedureOutput.getMode() == ParameterMode.REF_CURSOR) || !isResultSetProcedure()) {
221211

222212
if (useNamedParameters) {
223-
224-
String outputParameterName = procedureAttributes.getOutputParameterNames().get(i);
225-
procedureQuery.registerStoredProcedureParameter(outputParameterName, outputParameterType, mode);
226-
213+
procedureQuery.registerStoredProcedureParameter(procedureOutput.getName(), procedureOutput.getType(), procedureOutput.getMode());
227214
} else {
228-
procedureQuery.registerStoredProcedureParameter(params.getNumberOfParameters() + i + 1, outputParameterType,
229-
mode);
215+
// Output parameter should be after the input parameters
216+
int outputParameterIndex = params.getNumberOfParameters() + 1;
217+
procedureQuery.registerStoredProcedureParameter(outputParameterIndex, procedureOutput.getType(),
218+
procedureOutput.getMode());
230219
}
231-
});
220+
}
232221
}
233222

234223
return procedureQuery;
235224
}
225+
226+
private StoredProcedureQuery createAdhocStoredProcedureQuery() {
227+
String procedureName = procedureAttributes.getProcedureName();
228+
229+
if (getQueryMethod().isQueryForEntity()) {
230+
return getEntityManager().createStoredProcedureQuery(procedureName,
231+
getQueryMethod().getEntityInformation().getJavaType());
232+
}
233+
234+
return getEntityManager().createStoredProcedureQuery(procedureName);
235+
}
236+
237+
/**
238+
* @return true if the stored procedure will use a ResultSet to return data and not output parameters
239+
*/
240+
private boolean isResultSetProcedure() {
241+
return getQueryMethod().isCollectionQuery() || getQueryMethod().isQueryForEntity();
242+
}
236243
}

0 commit comments

Comments
 (0)