Support for deferred access to StoredProcedureQuery output parameters

Issue: SPR-17115
This commit is contained in:
Juergen Hoeller
2018-08-02 14:30:55 +02:00
parent fa97aab8be
commit c46dacc209
2 changed files with 200 additions and 7 deletions

View File

@@ -24,11 +24,14 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.ParameterMode;
import javax.persistence.Query;
import javax.persistence.StoredProcedureQuery;
import javax.persistence.TransactionRequiredException;
import org.apache.commons.logging.Log;
@@ -339,17 +342,23 @@ public abstract class SharedEntityManagerCreator {
/**
* Invocation handler that handles deferred Query objects created by
* non-transactional createQuery invocations on a shared EntityManager.
* <p>Includes deferred output parameter access for JPA 2.1 StoredProcedureQuery,
* retrieving the corresponding values for all registered parameters on query
* termination and returning the locally cached values for subsequent access.
*/
private static class DeferredQueryInvocationHandler implements InvocationHandler {
private final Query target;
@Nullable
private EntityManager em;
private EntityManager entityManager;
public DeferredQueryInvocationHandler(Query target, EntityManager em) {
@Nullable
private Map<Object, Object> outputParameters;
public DeferredQueryInvocationHandler(Query target, EntityManager entityManager) {
this.target = target;
this.em = em;
this.entityManager = entityManager;
}
@Override
@@ -374,10 +383,30 @@ public abstract class SharedEntityManagerCreator {
return proxy;
}
}
else if (method.getName().equals("getOutputParameterValue")) {
if (this.entityManager == null) {
Object key = args[0];
if (this.outputParameters == null || !this.outputParameters.containsKey(key)) {
throw new IllegalArgumentException("OUT/INOUT parameter not available: " + key);
}
Object value = this.outputParameters.get(key);
if (value instanceof IllegalArgumentException) {
throw (IllegalArgumentException) value;
}
return value;
}
}
// Invoke method on actual Query object.
try {
Object retVal = method.invoke(this.target, args);
if (method.getName().equals("registerStoredProcedureParameter") && args.length == 3 &&
(args[2] == ParameterMode.OUT || args[2] == ParameterMode.INOUT)) {
if (this.outputParameters == null) {
this.outputParameters = new LinkedHashMap<>();
}
this.outputParameters.put(args[0], null);
}
return (retVal == this.target ? proxy : retVal);
}
catch (InvocationTargetException ex) {
@@ -387,8 +416,25 @@ public abstract class SharedEntityManagerCreator {
if (queryTerminatingMethods.contains(method.getName())) {
// Actual execution of the query: close the EntityManager right
// afterwards, since that was the only reason we kept it open.
EntityManagerFactoryUtils.closeEntityManager(this.em);
this.em = null;
if (this.outputParameters != null && this.target instanceof StoredProcedureQuery) {
StoredProcedureQuery storedProc = (StoredProcedureQuery) this.target;
for (Map.Entry<Object, Object> entry : this.outputParameters.entrySet()) {
try {
Object key = entry.getKey();
if (key instanceof Integer) {
entry.setValue(storedProc.getOutputParameterValue((Integer) key));
}
else {
entry.setValue(storedProc.getOutputParameterValue(key.toString()));
}
}
catch (IllegalArgumentException ex) {
entry.setValue(ex);
}
}
}
EntityManagerFactoryUtils.closeEntityManager(this.entityManager);
this.entityManager = null;
}
}
}