Spring Data JPA à partir de la version 1.11.1 ne prend pas en charge les SP renvoyant des ensembles de résultats. J'ai déposé un défaut correspondant avec les données de printemps.
La solution consiste à descendre d'un niveau d'API et à utiliser simplement JPA. Voici une classe générique que j'ai écrite et qui fonctionne avec les SP MS SQL.
import com.google.common.base.Strings;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.ParameterMode;
import javax.persistence.Query;
import javax.persistence.StoredProcedureQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class StoredProcRepository {
//region Injected beans (via a RequiredArgsConstructor)
private final EntityManager em;
//endregion
/**
* Calls a stored procedure via JPA and retrieves a single implicit result set (in DBs that
* support them e.g. MS SQL or MySQL). The call is not dependent on a DB dialect. Be
* aware that large result sets should be paginated and not entirely read to memory. Recreates
* StoredProcedureQuery instance and its parameters on each call.
* To execute MS SQL SPs performing multiple queries, SET NOCOUNT ON.
*
* @param procedureName stored procedure name, optionally qualified per DB syntax
* @param resultClass converts (maps) each result set row into instances of resultClass via JPA
* @param spArgs stored procedure arguments, supplied positionally (optional SP arguments at the
* end of the list could be omitted)
* @param <T> class of row instances converted per JPA
* @return the entire result set
*/
public <T> List<T> queryViaStoredProc(String procedureName, Class<T> resultClass,
Object... spArgs) {
StoredProcedureQuery spq = em.createStoredProcedureQuery(procedureName, resultClass);
int pos = 0;
for (Object arg : spArgs) {
spq.registerStoredProcedureParameter(++pos, arg.getClass(), ParameterMode.IN);
spq.setParameter(pos, arg);
}
return spq.getResultList();
}
/**
* Calls a stored procedure via JPA and retrieves only the top row of a single implicit result
* set (in DBs that support them e.g. MS SQL or MySQL).
* Assumes that result set has at least one row.
* The call is not dependent on a DB dialect.
* Be aware that large result sets should be paginated and not entirely read to memory.
* Recreates StoredProcedureQuery instance and its parameters on each call.
* To execute MS SQL SPs performing multiple queries, SET NOCOUNT ON.
*
* @param procedureName stored procedure name, optionally qualified per DB syntax
* @param resultClass converts (maps) each result set row into instances of resultClass via JPA
* @param spArgs stored procedure arguments, supplied positionally (optional SP arguments at the
* end of the list could be omitted)
* @param <T> class of row instances converted per JPA
* @return the entire result set
*/
public <T> T queryTopRowViaStoredProc(String procedureName, Class<T> resultClass,
Object... spArgs) {
return queryViaStoredProc(procedureName, resultClass, spArgs).get(0);
}
}
Pour les SP MS SQL, l'exigence supplémentaire est d'avoir SET NOCOUNT ON
pour tous les SP exécutant plus d'une requête. Cela peut être défini de l'une des trois manières suivantes :
- Dans un wrapper Java générique utilisant JPA (voir le code ci-dessous). Cette approche ne fonctionne qu'avec le pilote jTDS JDBC. Un problème correspondant a été déposé avec le projet de pilote MS JDBC.
- Au début de chaque SP.
- Globalement dans votre base de données .
Voici le code pour #1 :méthodes correspondantes pour le même StoredProcRepository
classe.
/**
* Calls an MS SQL stored procedure via JPA and retrieves a single implicit result set.
* Protects against lack of SET NOCOUNT in stored procedures.
* This works with jTDS JDBC driver, but not with MS JDBC driver.
* Be aware that large result sets should be paginated and not entirely read to memory.
*
* @param procedureName stored procedure name, optionally qualified per DB syntax
* @param resultClass converts (maps) each result set row into instances of resultClass via JPA
* @param spArgs stored procedure arguments, supplied positionally (optional SP arguments at the
* end of the list could be omitted)
* @param <T> class of row instances converted per JPA
* @return the entire result set
*/
public <T> List<T> queryViaMsSqlStoredProc(String procedureName, Class<T> resultClass,
Object... spArgs) {
String spBindParams = (spArgs.length == 0) ? "" : "?" + Strings.repeat(",?", spArgs.length - 1);
// The following works with jTDS driver, but not with MS driver
String spQuery = String.format("EXEC %s %s", procedureName, spBindParams);
// The following works with jTDS driver, but not with MS driver
/*
String spQuery = String.format("{call %s(%s)}", procedureName, spBindParams);
Query q = em.createNativeQuery("SET NOCOUNT ON; " + spQuery, resultClass)
.setHint("org.hibernate.readOnly", true);
*/
Query q = em.createNativeQuery(spQuery, resultClass);
int pos = 0;
for (Object arg : spArgs) {
q.setParameter(++pos, arg);
}
return q.getResultList();
}
/**
* Calls an MS SQL stored procedure via JPA and retrieves only the top row of a single implicit
* result set.
* Assumes that result set has at least one row.
* The call sets the "NOCOUNT ON" MS SQL batch option.
* Be aware that large result sets should be paginated and not entirely read to memory.
*
* @param procedureName stored procedure name, optionally qualified per DB syntax
* @param resultClass converts (maps) each result set row into instances of resultClass via JPA
* @param spArgs stored procedure arguments, supplied positionally (optional SP arguments at the
* end of the list could be omitted)
* @param <T> class of row instances converted per JPA
* @return the entire result set
*/
public <T> T queryTopRowViaMsSqlStoredProc(String procedureName, Class<T> resultClass,
Object... spArgs) {
return queryViaMsSqlStoredProc(procedureName, resultClass, spArgs).get(0);
}