Comment appeler une procédure stockée depuis Java et JPA

95

J'écris une application Web simple pour appeler une procédure stockée et récupérer des données. C'est une application très simple, qui interagit avec la base de données du client. Nous transmettons l'identifiant de l'employé et l'identifiant de l'entreprise et la procédure stockée renverra les détails de l'employé.

L'application Web ne peut pas mettre à jour / supprimer les données et utilise SQL Server.

Je déploie mon application Web dans Jboss AS. Dois-je utiliser JPA pour accéder à la procédure stockée ou CallableStatement. Tout avantage de l'utilisation de JPA dans ce cas.

Quelle sera également l'instruction sql pour appeler cette procédure stockée. Je n'ai jamais utilisé de procédures stockées auparavant et j'ai du mal avec celle-ci. Google n'a pas été d'une grande aide.

Voici la procédure stockée:

CREATE procedure getEmployeeDetails (@employeeId int, @companyId int)
as
begin
    select firstName, 
           lastName, 
           gender, 
           address
      from employee et
     where et.employeeId = @employeeId
       and et.companyId = @companyId
end

Mettre à jour:

Pour toute personne ayant des problèmes pour appeler une procédure stockée à l'aide de JPA .

Query query = em.createNativeQuery("{call getEmployeeDetails(?,?)}",
                                   EmployeeDetails.class)           
                                   .setParameter(1, employeeId)
                                   .setParameter(2, companyId);

List<EmployeeDetails> result = query.getResultList();

Choses que j'ai remarquées:

  1. Les noms de paramètres ne fonctionnaient pas pour moi, alors essayez d'utiliser l'index des paramètres.
  2. Corriger l'instruction SQL {call sp_name(?,?)}au lieu de call sp_name(?,?)
  3. Si la procédure stockée renvoie un jeu de résultats, même si vous savez avec une seule ligne, getSingleResultne fonctionnera pas
  4. Passer un resultSetMappingnom ou des détails de classe de résultat
utilisateur431514
la source
2
Vous ne pouvez pas utiliser de paramètres nommés dans les requêtes natives . Les paramètres nommés ne sont pris en charge que pour les requêtes JPQL. (Si vous préférez les paramètres nommés, vous pouvez écrire votre propre classe pour traduire les paramètres nommés en paramètres numérotés.)
Viliam Búr
J'ai toujours utilisé des paramètres nommés avec createNativeQueries et je n'ai jamais eu de problème. Je viens de jeter un œil au système actuel sur lequel je travaille et il y a des tonnes de requêtes natives avec des paramètres nommés. Pouvez-vous nous fournir des références pour votre affirmation? Notre ensemble est JPA 2 et Hibernate 4+.
Jaumzera

Réponses:

58

JPA 2.1 prend désormais en charge la procédure stockée, lisez la documentation Java ici .

Exemple:

StoredProcedureQuery storedProcedure = em.createStoredProcedureQuery("sales_tax");
// set parameters
storedProcedure.registerStoredProcedureParameter("subtotal", Double.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("tax", Double.class, ParameterMode.OUT);
storedProcedure.setParameter("subtotal", 1f);
// execute SP
storedProcedure.execute();
// get result
Double tax = (Double)storedProcedure.getOutputParameterValue("tax");

Voir l'exemple détaillé ici .

Pau Kiat Wee
la source
23

Je déploie mon application Web dans Jboss AS. Dois-je utiliser JPA pour accéder à la procédure stockée ou CallableStatement. Tout avantage de l'utilisation de JPA dans ce cas.

Ce n'est pas vraiment pris en charge par JPA mais c'est faisable . Pourtant, je n'irais pas par là:

  • utiliser JPA uniquement pour mapper le résultat d'un appel de procédure stockée dans certains beans est vraiment exagéré,
  • d'autant plus que JPA n'est pas vraiment approprié pour appeler une procédure stockée (la syntaxe sera assez verbeuse).

Je préférerais donc utiliser le support Spring pour l'accès aux données JDBC , ou un mappeur de données comme MyBatis ou, étant donné la simplicité de votre application, JDBC brut et CallableStatement. En fait, JDBC serait probablement mon choix. Voici un exemple de lancement de base:

CallableStatement cstmt = con.prepareCall("{call getEmployeeDetails(?, ?)}");
cstmt.setInt("employeeId", 123);
cstmt.setInt("companyId", 456);
ResultSet rs = cstmt.executeQuery();

Référence

Pascal Thivent
la source
Comme indiqué dans la réponse ci-dessous, il est pris en charge - vous voudrez peut-être modifier
Mr_and_Mrs_D
10

Vous devez transmettre les paramètres à la procédure stockée.

Cela devrait fonctionner comme ceci:

    List result = em
      .createNativeQuery("call getEmployeeDetails(:employeeId,:companyId)")
      .setParameter("emplyoyeeId", 123L)
      .setParameter("companyId", 456L)
      .getResultList();

Mettre à jour:

Ou peut-être que ça ne devrait pas.

Dans le livre EJB3 en action , il est dit à la page 383, que JPA ne prend pas en charge les procédures stockées (la page n'est qu'un aperçu, vous n'obtenez pas le texte intégral, le livre entier est disponible en téléchargement à plusieurs endroits dont celui-ci , Je ne sais pas si c'est légal).

Quoi qu'il en soit, le texte est le suivant:

Procédures stockées JPA et base de données

Si vous êtes un grand fan de SQL, vous voudrez peut-être exploiter la puissance des procédures stockées dans la base de données. Malheureusement, JPA ne prend pas en charge les procédures stockées et vous devez dépendre d'une fonctionnalité propriétaire de votre fournisseur de persistance. Cependant, vous pouvez utiliser des fonctions stockées simples (sans paramètres out) avec une requête SQL native.

Sean Patrick Floyd
la source
J'ai essayé et j'ai obtenu ce message d'erreur: java.sql.SQLException: syntaxe incorrecte près de «@ P0».
user431514
3
Il doit être "{call getEmployeeDetails (: employeeId,: companyId)}", pour le serveur SQL, il doit avoir des accolades.
Vedran
@Vedran vrai. Je n'étais intéressé que par la partie paramétrage
Sean Patrick Floyd
9

Comment récupérer le paramètre de sortie de procédure stockée à l'aide de JPA (2.0 nécessite des importations EclipseLink et 2.1 pas)

Même si cette réponse élabore sur le retour d'un jeu d'enregistrements à partir d'une procédure stockée, je poste ici, car il m'a fallu des siècles pour le comprendre et ce fil de discussion m'a aidé.

Mon application utilisait Eclipselink-2.3.1, mais je vais forcer une mise à jour vers Eclipselink-2.5.0, car JPA 2.1 a un bien meilleur support pour les procédures stockées.

Utilisation d'EclipseLink-2.3.1 / JPA-2.0: dépendant de l'implémentation

Cette méthode nécessite des importations de classes EclipseLink à partir de "org.eclipse.persistence", elle est donc spécifique à l'implémentation d'Eclipselink.

Je l'ai trouvé sur " http://www.yenlo.nl/en/calling-oracle-stored-procedures-from-eclipselink-with-multiple-out-parameters ".

StoredProcedureCall storedProcedureCall = new StoredProcedureCall();
storedProcedureCall.setProcedureName("mypackage.myprocedure");
storedProcedureCall.addNamedArgument("i_input_1"); // Add input argument name.
storedProcedureCall.addNamedOutputArgument("o_output_1"); // Add output parameter name.
DataReadQuery query = new DataReadQuery();
query.setCall(storedProcedureCall);
query.addArgument("i_input_1"); // Add input argument names (again);
List<Object> argumentValues = new ArrayList<Object>();
argumentValues.add("valueOf_i_input_1"); // Add input argument values.
JpaEntityManager jpaEntityManager = (JpaEntityManager) getEntityManager();
Session session = jpaEntityManager.getActiveSession();
List<?> results = (List<?>) session.executeQuery(query, argumentValues);
DatabaseRecord record = (DatabaseRecord) results.get(0);
String result = String.valueOf(record.get("o_output_1")); // Get output parameter

Utilisation d'EclipseLink-2.5.0 / JPA-2.1: Indépendant de l'implémentation (déjà documenté dans ce fil)

Cette méthode est indépendante de l'implémentation (pas besoin d'importations Eclipslink).

StoredProcedureQuery query = getEntityManager().createStoredProcedureQuery("mypackage.myprocedure");
query.registerStoredProcedureParameter("i_input_1", String.class, ParameterMode.IN);
query.registerStoredProcedureParameter("o_output_1", String.class, ParameterMode.OUT);
query.setParameter("i_input_1", "valueOf_i_input_1");
boolean queryResult = query.execute();
String result = String.valueOf(query.getOutputParameterValue("o_output_1"));
Malcolm Boekhoff
la source
8
Aah, j'ai mal aux yeux. Ce n'est pas vraiment mieux que JDBC, n'est-ce pas?
Lukas Eder
Haha, oui point pris. Cependant, l'avantage d'utiliser ces choses est que vous n'avez pas à taper une charge de code pour obtenir la classe d'objet de données et vous n'avez pas à faire le bit où vous transférez toutes les données de recordSet dans votre classe de données . Il existe toujours un objet de données (Entity), mais l'assistant Eclipse le génère pour vous.
Malcolm Boekhoff
1
Oui vous pourriez. Mais je dis cela en tant que développeur de jOOQ , où tout est généré. Il ne reste plus qu'à appeler la procédure / fonction.
Lukas Eder
Avez-vous réellement essayé l'exemple du bas (indépendant de la mise en œuvre)? Je l'ai essayé avec la différence que la procédure a été définie dans un xmlfichier et que cela n'a pas fonctionné. Je ne peux pas lire le OUTparamètre.
Roland le
D'une manière ou d'une autre, pour l'implémentation JPA-2.1, les paramètres nommés ne fonctionnent pas pour moi. Au lieu de cela, j'ai dû passer leur index de position dans les procédures stockées et j'ai réussi à obtenir le résultat pour le paramètre de sortie. C'était le cas lorsque j'ai stocké une procédure renvoyant plusieurs ensembles de résultats. Pour le 1 ResultSet, j'ai simplement utilisé @Query
Radhesh Khanna
7
  1. Pour une procédure stockée simple qui utilise des paramètres IN / OUT comme celui-ci

    CREATE OR REPLACE PROCEDURE count_comments (  
       postId IN NUMBER,  
       commentCount OUT NUMBER )  
    AS 
    BEGIN 
        SELECT COUNT(*) INTO commentCount  
        FROM post_comment  
        WHERE post_id = postId; 
    END;
    

    Vous pouvez l'appeler depuis JPA comme suit:

    StoredProcedureQuery query = entityManager
        .createStoredProcedureQuery("count_comments")
        .registerStoredProcedureParameter(1, Long.class, 
            ParameterMode.IN)
        .registerStoredProcedureParameter(2, Long.class, 
            ParameterMode.OUT)
        .setParameter(1, 1L);
    
    query.execute();
    
    Long commentCount = (Long) query.getOutputParameterValue(2);
    
  2. Pour une procédure stockée qui utilise un SYS_REFCURSORparamètre OUT:

    CREATE OR REPLACE PROCEDURE post_comments ( 
       postId IN NUMBER, 
       postComments OUT SYS_REFCURSOR ) 
    AS 
    BEGIN
        OPEN postComments FOR
        SELECT *
        FROM post_comment 
        WHERE post_id = postId; 
    END;

    Vous pouvez l'appeler comme suit:

    StoredProcedureQuery query = entityManager
        .createStoredProcedureQuery("post_comments")
        .registerStoredProcedureParameter(1, Long.class, 
             ParameterMode.IN)
        .registerStoredProcedureParameter(2, Class.class, 
             ParameterMode.REF_CURSOR)
        .setParameter(1, 1L);
    
    query.execute();
    
    List<Object[]> postComments = query.getResultList();
  3. Pour une fonction SQL qui ressemble à ceci:

    CREATE OR REPLACE FUNCTION fn_count_comments ( 
        postId IN NUMBER ) 
        RETURN NUMBER 
    IS
        commentCount NUMBER; 
    BEGIN
        SELECT COUNT(*) INTO commentCount 
        FROM post_comment 
        WHERE post_id = postId; 
        RETURN( commentCount ); 
    END;

    Vous pouvez l'appeler comme ceci:

    BigDecimal commentCount = (BigDecimal) entityManager
    .createNativeQuery(
        "SELECT fn_count_comments(:postId) FROM DUAL"
    )
    .setParameter("postId", 1L)
    .getSingleResult();

    Au moins lors de l'utilisation d'Hibernate 4.x et 5.x car le JPA StoredProcedureQueryne fonctionne pas pour les FONCTIONS SQL.

Pour plus de détails sur la façon d'appeler des procédures et des fonctions stockées lors de l'utilisation de JPA et Hibernate, consultez les articles suivants

Vlad Mihalcea
la source
J'ai continué à recevoir le message d'erreur "nombre ou types d'arguments incorrects dans l'appel à ...". J'ai réalisé que j'appelais createNativeQuery. Je suis passé à createStoredProcedureQuery. Alors voilà!
Ahmet le
6

Pour moi, seuls les éléments suivants ont fonctionné avec Oracle 11g et Glassfish 2.1 (Toplink):

Query query = entityManager.createNativeQuery("BEGIN PROCEDURE_NAME(); END;");
query.executeUpdate();

La variante avec des accolades bouclées a donné ORA-00900.

Dmitry Chornyi
la source
1
Fonctionne pour moi sur Oracle 11g, fournisseur JPA d'hibernation.
David Mann
1
Cela nous a permis de nous sortir d'un très gros problème. Nous utilisions java6, oracle11g, Jboss6, Hibernate. Merci @Chornyi.
Abdullah Khan
6

Si vous utilisez EclipseLink, vous pouvez utiliser @NamedStoredProcedureQuery ou StoreProcedureCall pour exécuter n'importe quelle procédure stockée, y compris celles avec des paramètres de sortie ou des curseurs out. La prise en charge des fonctions stockées et des types de données PLSQL est également disponible.

Voir, http://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Stored_Procedures

James
la source
1
Quelle version d'EclipseLink possède EntityManager.createNamedStoredProcedureQuery ()?
Mircea Ion
3

Ce qui suit fonctionne pour moi:

Query query = em.createNativeQuery("BEGIN VALIDACIONES_QPAI.RECALC_COMP_ASSEMBLY('X','X','X',0); END;");
query.executeUpdate();
Roberto Quevedo
la source
Les paramètres OUT et INOUT ne fonctionneront pas avec cette API. Voir en.wikibooks.org/wiki/Java_Persistence/…
Valentin Jacquemin
2

Peut-être que ce n'est pas la même chose pour Sql Srver mais pour les personnes utilisant oracle et eclipslink, cela fonctionne pour moi

ex: une procédure qui a un paramètre IN (type CHAR) et deux paramètres OUT (NUMBER & VARCHAR)

dans persistence.xml, déclarez l'unité de persistance:

<persistence-unit name="presistanceNameOfProc" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/DataSourceName</jta-data-source>
    <mapping-file>META-INF/eclipselink-orm.xml</mapping-file>
    <properties>
        <property name="eclipselink.logging.level" value="FINEST"/>
        <property name="eclipselink.logging.logger" value="DefaultLogger"/>
        <property name="eclipselink.weaving" value="static"/>
        <property name="eclipselink.ddl.table-creation-suffix" value="JPA_STORED_PROC" />
    </properties>
</persistence-unit>

et déclarez la structure du proc dans le fichier eclipselink-orm.xml

<?xml version="1.0" encoding="UTF-8"?><entity-mappings version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd">
<named-stored-procedure-query name="PERSIST_PROC_NAME" procedure-name="name_of_proc" returns-result-set="false">
    <parameter direction="IN" name="in_param_char" query-parameter="in_param_char" type="Character"/>
    <parameter direction="OUT" name="out_param_int" query-parameter="out_param_int" type="Integer"/>
    <parameter direction="OUT" name="out_param_varchar" query-parameter="out_param_varchar" type="String"/>
</named-stored-procedure-query>

dans le code, il vous suffit d'appeler votre proc comme ceci:

try {
        final Query query = this.entityManager
                .createNamedQuery("PERSIST_PROC_NAME");
        query.setParameter("in_param_char", 'V'); 
        resultQuery = (Object[]) query.getSingleResult();

    } catch (final Exception ex) {
        LOGGER.log(ex);
        throw new TechnicalException(ex);
    }

pour obtenir les deux paramètres de sortie:

Integer myInt = (Integer) resultQuery[0];
String myStr =  (String) resultQuery[1];
Réda
la source
2

Cela a fonctionné pour moi.

@Entity
@Table(name="acct")
@NamedNativeQueries({
 @NamedNativeQuery(callable=true, name="Account.findOne", query="call sp_get_acct(?), resultClass=Account.class)})
public class Account{
 // Code 
}

Remarque: à l'avenir, si vous décidez d'utiliser la version par défaut de findOne, il vous suffit de commenter l'annotation NamedNativeQueries et JPA passera par défaut

Amit
la source
Si je veux appeler la procédure dans le package spécifique, dois-je appeler de cette manière: call {package}. {Procedure}?
Raju yourPepe
1

Cette réponse peut être utile si vous disposez d'un gestionnaire d'entités

J'avais une procédure stockée pour créer le numéro suivant et du côté serveur, j'ai un cadre de couture.

Côté client

 Object on = entityManager.createNativeQuery("EXEC getNextNmber").executeUpdate();
        log.info("New order id: " + on.toString());

Côté base de données (serveur SQL), j'ai une procédure stockée nommée getNextNmber

tosha Shah
la source
executeUpdate () retourne int. Êtes-vous sûr que vous avez une sortie de réception de sproc?
Constantine Gladky
1

Vous pouvez utiliser @Query(value = "{call PROC_TEST()}", nativeQuery = true)dans votre référentiel. Cela a fonctionné pour moi.

Attention: utilisez '{' et '}' sinon cela ne fonctionnera pas.

Everton Oliveira
la source
1

JPA 2.0 ne prend pas en charge les valeurs RETURN, uniquement les appels.

Ma solution était. Créez une FONCTION appelant PROCEDURE.

Ainsi, dans le code JAVA, vous exécutez une requête NATIVE en appelant la fonction oracle.

Musashi Miyamoto
la source
0

Pour appeler une procédure stockée, nous pouvons utiliser l'instruction Callable dans le package java.sql.

vinoth
la source
Merci pour votre réponse. Ainsi, le SQL pour l'instruction appelable sera {? = appeler getEmployeeDetails (?,?)} ou besoin de spécifier tous les paramètres de sortie
user431514
0

Essayez ce code:

return em.createNativeQuery("{call getEmployeeDetails(?,?)}",
                               EmployeeDetails.class)           
                               .setParameter(1, employeeId)
                               .setParameter(2, companyId).getResultList();
Patricio Dominguez
la source
0

persistence.xml

 <persistence-unit name="PU2" transaction-type="RESOURCE_LOCAL">
<non-jta-data-source>jndi_ws2</non-jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties/>

codigo java

  String PERSISTENCE_UNIT_NAME = "PU2";
    EntityManagerFactory factory2;
    factory2 = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);

    EntityManager em2 = factory2.createEntityManager();
    boolean committed = false;
    try {

        try {
            StoredProcedureQuery storedProcedure = em2.createStoredProcedureQuery("PKCREATURNO.INSERTATURNO");
            // set parameters
            storedProcedure.registerStoredProcedureParameter("inuPKEMPRESA", BigDecimal.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("inuPKSERVICIO", BigDecimal.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("inuPKAREA", BigDecimal.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("isbCHSIGLA", String.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("INUSINCALIFICACION", BigInteger.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("INUTIMBRAR", BigInteger.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("INUTRANSFERIDO", BigInteger.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("INTESTADO", BigInteger.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("inuContador", BigInteger.class, ParameterMode.OUT);

            BigDecimal inuPKEMPRESA = BigDecimal.valueOf(1);
            BigDecimal inuPKSERVICIO = BigDecimal.valueOf(5);
            BigDecimal inuPKAREA = BigDecimal.valueOf(23);
            String isbCHSIGLA = "";
            BigInteger INUSINCALIFICACION = BigInteger.ZERO;
            BigInteger INUTIMBRAR = BigInteger.ZERO;
            BigInteger INUTRANSFERIDO = BigInteger.ZERO;
            BigInteger INTESTADO = BigInteger.ZERO;
            BigInteger inuContador = BigInteger.ZERO;

            storedProcedure.setParameter("inuPKEMPRESA", inuPKEMPRESA);
            storedProcedure.setParameter("inuPKSERVICIO", inuPKSERVICIO);
            storedProcedure.setParameter("inuPKAREA", inuPKAREA);
            storedProcedure.setParameter("isbCHSIGLA", isbCHSIGLA);
            storedProcedure.setParameter("INUSINCALIFICACION", INUSINCALIFICACION);
            storedProcedure.setParameter("INUTIMBRAR", INUTIMBRAR);
            storedProcedure.setParameter("INUTRANSFERIDO", INUTRANSFERIDO);
            storedProcedure.setParameter("INTESTADO", INTESTADO);
            storedProcedure.setParameter("inuContador", inuContador);

            // execute SP
            storedProcedure.execute();
            // get result

            try {
                long _inuContador = (long) storedProcedure.getOutputParameterValue("inuContador");
                varCon = _inuContador + "";
            } catch (Exception e) {
            } 
        } finally {

        }
    } finally {
        em2.close();
    }
Elohim Julio Cesar C
la source
4
N'hésitez pas à ajouter un commentaire à votre réponse (autre que du code pur).
ivan.mylyanyk
0

À partir de JPA 2.1, JPA prend en charge l'appel de procédures stockées à l'aide du StoredProcedureQuery dynamique et du déclaratif @NamedStoredProcedureQuery.

H.Ostwal
la source
-2

Ma solution était. Créez une FONCTION appelant PROCEDURE.

Ainsi, dans le code JAVA, vous exécutez une requête NATIVE en appelant la fonction oracle.

Musashi Miyamoto
la source