Come chiamare una procedura memorizzata da Java e JPA


94

Sto scrivendo una semplice applicazione web per chiamare una stored procedure e recuperare alcuni dati. È un'applicazione molto semplice, che interagisce con il database del cliente. Passiamo l'ID del dipendente e l'ID dell'azienda e la procedura memorizzata restituirà i dettagli del dipendente.

L'applicazione Web non può aggiornare / eliminare i dati e utilizza SQL Server.

Sto distribuendo la mia applicazione web in Jboss AS. Devo usare JPA per accedere alla stored procedure o CallableStatement. Qualsiasi vantaggio nell'usare JPA in questo caso.

Inoltre, quale sarà l'istruzione sql per chiamare questa stored procedure. Non ho mai utilizzato procedure memorizzate prima e sto lottando con questo. Google non è stato di grande aiuto.

Ecco la procedura memorizzata:

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

Aggiornare:

Per chiunque altro abbia problemi a chiamare la procedura memorizzata utilizzando JPA .

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

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

Cose che ho notato:

  1. I nomi dei parametri non hanno funzionato per me, quindi prova a utilizzare l'indice dei parametri.
  2. Correggere l'istruzione sql {call sp_name(?,?)}invece di call sp_name(?,?)
  3. Se la stored procedure restituisce un set di risultati, anche se sai con una sola riga, getSingleResultnon funzionerà
  4. Passa un resultSetMappingnome o dettagli sulla classe di risultati

2
Non è possibile utilizzare parametri denominati nelle query native . I parametri denominati sono supportati solo per le query JPQL. (Se preferisci parametri con nome, potresti scrivere la tua classe per tradurre parametri con nome in parametri numerati.)
Viliam Búr

Ho sempre usato parametri con nome con createNativeQueries e non ho mai avuto problemi. Ho appena dato un'occhiata al sistema attuale su cui ho lavorato e ci sono tonnellate di query native con parametri denominati. Puoi fornirci qualche riferimento per la tua affermazione? Il nostro set è JPA 2 e Hibernate 4+.
Jaumzera

Risposte:


58

JPA 2.1 ora supporta la Stored Procedure, leggi il documento Java qui .

Esempio:

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");

Vedi esempio dettagliato qui .


23

Sto distribuendo la mia applicazione web in Jboss AS. Devo usare JPA per accedere alla stored procedure o CallableStatement. Qualsiasi vantaggio nell'usare JPA in questo caso.

Non è realmente supportato da JPA ma è fattibile . Tuttavia non andrei in questo modo:

  • usare JPA solo per mappare il risultato di una chiamata di procedura memorizzata in alcuni bean è davvero eccessivo,
  • soprattutto dato che JPA non è realmente appropriato per chiamare stored procedure (la sintassi sarà piuttosto prolissa).

Preferirei quindi considerare l'utilizzo di supporto Spring per l'accesso ai dati JDBC , o un mappatore dati come MyBatis o, data la semplicità della tua applicazione, JDBC grezzo e CallableStatement. In realtà, JDBC sarebbe probabilmente la mia scelta. Ecco un esempio di kickoff di base:

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

Riferimento


Come indicato nella risposta di seguito , è supportato - potresti voler modificare
Mr_and_Mrs_D

10

È necessario passare i parametri alla stored procedure.

Dovrebbe funzionare in questo modo:

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

Aggiornare:

O forse non dovrebbe.

Nel libro EJB3 in azione , a pagina 383 si dice che JPA non supporta le procedure memorizzate (la pagina è solo un'anteprima, non si ottiene il testo completo, l'intero libro è disponibile come download in diversi posti, incluso questo , Non so se sia legale però).

Comunque, il testo è questo:

Procedure memorizzate per JPA e database

Se sei un grande fan di SQL, potresti essere disposto a sfruttare la potenza delle stored procedure del database. Sfortunatamente, JPA non supporta le stored procedure e devi dipendere da una funzione proprietaria del tuo provider di persistenza. Tuttavia, è possibile utilizzare semplici funzioni memorizzate (senza parametri out) con una query SQL nativa.


Ho provato a ricevere questo messaggio di errore: java.sql.SQLException: sintassi non corretta vicino a "@ P0".
user431514

3
Dovrebbe essere "{call getEmployeeDetails (: employeeId,: companyId)}", per il server SQL deve avere le parentesi graffe.
Vedran

@Vedran vero. Ero interessato solo alla parte dell'impostazione dei parametri
Sean Patrick Floyd

9

Come recuperare il parametro di output della stored procedure utilizzando JPA (2.0 richiede le importazioni di EclipseLink e 2.1 no)

Anche se questa risposta è elaborata sulla restituzione di un recordset da una procedura memorizzata, sto postando qui, perché mi ci sono voluti anni per capirlo e questo thread mi ha aiutato.

La mia applicazione utilizzava Eclipselink-2.3.1, ma forzerò un aggiornamento a Eclipselink-2.5.0, poiché JPA 2.1 ha un supporto molto migliore per le procedure memorizzate.

Utilizzo di EclipseLink-2.3.1 / JPA-2.0: dipendente dall'implementazione

Questo metodo richiede l'importazione di classi EclipseLink da "org.eclipse.persistence", quindi è specifico per l'implementazione di Eclipselink.

L'ho trovato in " 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

Utilizzo di EclipseLink-2.5.0 / JPA-2.1: indipendente dall'implementazione (documentato già in questo thread)

Questo metodo è indipendente dall'implementazione (non è necessario importare 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"));

8
Aah, i miei occhi fanno male. Questo non è molto meglio di JDBC, vero?
Lukas Eder

Haha, sì, punto preso. Tuttavia il vantaggio nell'usare queste cose è che non devi digitare un carico di codice per ottenere la classe dell'oggetto dati e non devi fare la parte in cui trasferisci tutti i dati dal recordSet alla tua classe dati . C'è ancora un oggetto dati (Entità), ma la procedura guidata di Eclipse lo genera per te.
Malcolm Boekhoff

1
Sì, potresti. Ma lo dico come sviluppatore di jOOQ , dove tutto viene generato. L'unica cosa che resta da fare è chiamare effettivamente la procedura / funzione.
Lukas Eder

Hai effettivamente provato l'esempio in basso (indipendente dall'implementazione)? L'ho provato con la differenza che la procedura era definita in un xmlfile e non ha funzionato. Non riesco a leggere il OUTparametro.
Roland

6

Per me, solo quanto segue ha funzionato con Oracle 11g e Glassfish 2.1 (Toplink):

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

La variante con le parentesi graffe ha prodotto ORA-00900.


1
Funziona per me su Oracle 11g, provider JPA di ibernazione.
David Mann

1
Questo ci ha tirato fuori da un problema enorme. Stavamo usando java6, oracle11g, Jboss6, Hibernate. Grazie @Chornyi.
Abdullah Khan


6
  1. Per una semplice procedura memorizzata che utilizza parametri IN / OUT come questo

    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;

    Puoi chiamarlo da JPA come segue:

    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. Per una procedura memorizzata che utilizza un SYS_REFCURSORparametro 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;

    Puoi chiamarlo come segue:

    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. Per una funzione SQL che ha il seguente aspetto:

    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;

    Puoi chiamarlo così:

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

    Almeno quando si utilizza Hibernate 4.x e 5.x perché JPA StoredProcedureQuerynon funziona per le FUNZIONI SQL.

Per ulteriori dettagli su come chiamare stored procedure e funzioni quando si utilizza JPA e Hibernate, consultare i seguenti articoli


Continuavo a ricevere il messaggio di errore "numero o tipi di argomenti errati nella chiamata a ...". Ho capito che stavo chiamando createNativeQuery. Sono passato a createStoredProcedureQuery. Quindi, voilà!
Ahmet


2

Potrebbe non essere lo stesso per Sql Srver ma per le persone che usano oracle ed eclipslink funziona per me

es: una procedura che ha un parametro IN (tipo CHAR) e due parametri OUT (NUMBER e VARCHAR)

nel persistence.xml dichiarare l'unità di persistenza:

<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>

e dichiarare la struttura del proc in 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>

nel codice devi solo chiamare il tuo proc in questo modo:

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);
    }

per ottenere i due parametri di output:

Integer myInt = (Integer) resultQuery[0];
String myStr =  (String) resultQuery[1];

2

Questo ha funzionato per me.

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

Nota: in futuro se decidi di utilizzare la versione predefinita di findOne, commenta semplicemente l'annotazione NamedNativeQueries e JPA passerà all'impostazione predefinita


Se voglio chiamare procedure all'interno del pacchetto specifico, devo chiamare in questo modo: call {package}. {Procedure}?
Raju yourPepe

1

Questa risposta potrebbe essere utile se hai un gestore entità

Avevo una procedura memorizzata per creare il numero successivo e sul lato server ho seam framework.

Dalla parte del cliente

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

Lato database (server SQL) Ho una procedura memorizzata denominata getNextNmber


executeUpdate () return int. Sei sicuro di un output di ricezione di sproc?
Constantine Gladky

1

JPA 2.0 non supporta i valori RETURN, solo le chiamate.

La mia soluzione era. Creare una FUNZIONE chiamando PROCEDURA.

Quindi, all'interno del codice JAVA esegui una NATIVE QUERY chiamando l'oracolo FUNCTION.


0

Per chiamare la procedura memorizzata possiamo utilizzare l'istruzione Callable nel pacchetto java.sql.


Grazie per la tua risposta. Quindi lo sql per l'istruzione callable sarà {? = chiama getEmployeeDetails (?,?)} o è necessario specificare tutti i parametri di output
user431514

0

Prova questo codice:

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

0

Puoi usare @Query(value = "{call PROC_TEST()}", nativeQuery = true)nel tuo repository. Questo ha funzionato per me.

Attenzione: usa "{" e "}" altrimenti non funzionerà.


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();
    }

4
per favore, non esitare ad aggiungere qualsiasi commento alla tua risposta (diverso dal puro codice).
ivan.mylyanyk

0

Da JPA 2.1, JPA supporta la chiamata a stored procedure utilizzando StoredProcedureQuery dinamico e @NamedStoredProcedureQuery dichiarativo.


-2

La mia soluzione era. Creare una FUNZIONE chiamando PROCEDURA.

Quindi, all'interno del codice JAVA esegui una NATIVE QUERY chiamando l'oracolo FUNCTION.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.