Support for deferred access to StoredProcedureQuery output parameters
Issue: SPR-17115
This commit is contained in:
parent
fa97aab8be
commit
c46dacc209
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
|
|
@ -18,19 +18,30 @@ package org.springframework.orm.jpa;
|
|||
|
||||
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.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.BDDMockito.*;
|
||||
import static org.mockito.BDDMockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SharedEntityManagerCreator}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class SharedEntityManagerCreatorTests {
|
||||
|
||||
@Test
|
||||
|
|
@ -83,4 +94,140 @@ public class SharedEntityManagerCreatorTests {
|
|||
em.refresh(new Object());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deferredQueryWithUpdate() {
|
||||
EntityManagerFactory emf = mock(EntityManagerFactory.class);
|
||||
EntityManager targetEm = mock(EntityManager.class);
|
||||
Query query = mock(Query.class);
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createQuery("x")).willReturn(query);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
em.createQuery("x").executeUpdate();
|
||||
|
||||
verify(query).executeUpdate();
|
||||
verify(targetEm).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deferredQueryWithSingleResult() {
|
||||
EntityManagerFactory emf = mock(EntityManagerFactory.class);
|
||||
EntityManager targetEm = mock(EntityManager.class);
|
||||
Query query = mock(Query.class);
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createQuery("x")).willReturn(query);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
em.createQuery("x").getSingleResult();
|
||||
|
||||
verify(query).getSingleResult();
|
||||
verify(targetEm).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deferredQueryWithResultList() {
|
||||
EntityManagerFactory emf = mock(EntityManagerFactory.class);
|
||||
EntityManager targetEm = mock(EntityManager.class);
|
||||
Query query = mock(Query.class);
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createQuery("x")).willReturn(query);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
em.createQuery("x").getResultList();
|
||||
|
||||
verify(query).getResultList();
|
||||
verify(targetEm).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deferredQueryWithResultStream() {
|
||||
EntityManagerFactory emf = mock(EntityManagerFactory.class);
|
||||
EntityManager targetEm = mock(EntityManager.class);
|
||||
Query query = mock(Query.class);
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createQuery("x")).willReturn(query);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
em.createQuery("x").getResultStream();
|
||||
|
||||
verify(query).getResultStream();
|
||||
verify(targetEm).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deferredStoredProcedureQueryWithIndexedParameters() {
|
||||
EntityManagerFactory emf = mock(EntityManagerFactory.class);
|
||||
EntityManager targetEm = mock(EntityManager.class);
|
||||
StoredProcedureQuery query = mock(StoredProcedureQuery.class);
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createStoredProcedureQuery("x")).willReturn(query);
|
||||
willReturn("y").given(query).getOutputParameterValue(0);
|
||||
willReturn("z").given(query).getOutputParameterValue(2);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
StoredProcedureQuery spq = em.createStoredProcedureQuery("x");
|
||||
spq.registerStoredProcedureParameter(0, String.class, ParameterMode.OUT);
|
||||
spq.registerStoredProcedureParameter(1, Number.class, ParameterMode.IN);
|
||||
spq.registerStoredProcedureParameter(2, Object.class, ParameterMode.INOUT);
|
||||
spq.execute();
|
||||
assertEquals("y", spq.getOutputParameterValue(0));
|
||||
try {
|
||||
spq.getOutputParameterValue(1);
|
||||
fail("Should have thrown IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
assertEquals("z", spq.getOutputParameterValue(2));
|
||||
|
||||
verify(query).registerStoredProcedureParameter(0, String.class, ParameterMode.OUT);
|
||||
verify(query).registerStoredProcedureParameter(1, Number.class, ParameterMode.IN);
|
||||
verify(query).registerStoredProcedureParameter(2, Object.class, ParameterMode.INOUT);
|
||||
verify(query).execute();
|
||||
verify(targetEm).close();
|
||||
verifyNoMoreInteractions(query);
|
||||
verifyNoMoreInteractions(targetEm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deferredStoredProcedureQueryWithNamedParameters() {
|
||||
EntityManagerFactory emf = mock(EntityManagerFactory.class);
|
||||
EntityManager targetEm = mock(EntityManager.class);
|
||||
StoredProcedureQuery query = mock(StoredProcedureQuery.class);
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createStoredProcedureQuery("x")).willReturn(query);
|
||||
willReturn("y").given(query).getOutputParameterValue("a");
|
||||
willReturn("z").given(query).getOutputParameterValue("c");
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
StoredProcedureQuery spq = em.createStoredProcedureQuery("x");
|
||||
spq.registerStoredProcedureParameter("a", String.class, ParameterMode.OUT);
|
||||
spq.registerStoredProcedureParameter("b", Number.class, ParameterMode.IN);
|
||||
spq.registerStoredProcedureParameter("c", Object.class, ParameterMode.INOUT);
|
||||
spq.execute();
|
||||
assertEquals("y", spq.getOutputParameterValue("a"));
|
||||
try {
|
||||
spq.getOutputParameterValue("b");
|
||||
fail("Should have thrown IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
assertEquals("z", spq.getOutputParameterValue("c"));
|
||||
|
||||
verify(query).registerStoredProcedureParameter("a", String.class, ParameterMode.OUT);
|
||||
verify(query).registerStoredProcedureParameter("b", Number.class, ParameterMode.IN);
|
||||
verify(query).registerStoredProcedureParameter("c", Object.class, ParameterMode.INOUT);
|
||||
verify(query).execute();
|
||||
verify(targetEm).close();
|
||||
verifyNoMoreInteractions(query);
|
||||
verifyNoMoreInteractions(targetEm);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue